/* This is the new kwindecoration kcontrol module Copyright (c) 2009, Urs Wolfer Copyright (c) 2004, Sandro Giessl Copyright (c) 2001 Karol Szwed http://gallium.n3.net/ Supports new kwin configuration plugins, and titlebar button position modification via dnd interface. Based on original "kwintheme" (Window Borders) Copyright (C) 2001 Rik Hemsley (rikkus) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "buttons.h" #include "pixmaps.h" #include "config-kwin.h" #include #include #include #include #include #include #include #include #include #ifdef KWIN_BUILD_KAPPMENU #include #include #endif #include #define BUTTONDRAGMIMETYPE "application/x-kde_kwindecoration_buttons" namespace KWin { ButtonDrag::ButtonDrag(Button btn) : QMimeData() { QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << btn.name; stream << btn.icon; stream << btn.type; stream << (int) btn.duplicate; stream << (int) btn.supported; setData(BUTTONDRAGMIMETYPE, data); } bool ButtonDrag::canDecode(QDropEvent* e) { return e->mimeData()->hasFormat(BUTTONDRAGMIMETYPE); } bool ButtonDrag::decode(QDropEvent* e, Button& btn) { QByteArray data = e->mimeData()->data(BUTTONDRAGMIMETYPE); if (data.size()) { e->accept(); QDataStream stream(data); stream >> btn.name; stream >> btn.icon; int type; stream >> type; btn.type = KDecorationDefines::DecorationButton(type); int duplicate; stream >> duplicate; btn.duplicate = duplicate; int supported; stream >> supported; btn.supported = supported; return true; } return false; } Button::Button() { } Button::Button(const QString& n, const QBitmap& i, KDecorationDefines::DecorationButton t, bool d, bool s) : name(n), icon(i), type(t), duplicate(d), supported(s) { } Button::~Button() { } // helper function to deal with the Button's bitmaps more easily... QPixmap bitmapPixmap(const QBitmap& bm, const QColor& color) { QPixmap pm(bm.size()); pm.fill(Qt::white); QPainter p(&pm); p.setPen(color); p.drawPixmap(0, 0, bm); p.end(); pm.setMask(pm.createMaskFromColor(Qt::white)); return pm; } ButtonSource::ButtonSource(QWidget *parent) : QListWidget(parent) { setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(false); setSortingEnabled(true); } ButtonSource::~ButtonSource() { } QSize ButtonSource::sizeHint() const { // make the sizeHint height a bit smaller than the one of QListView... ensurePolished(); QSize s; if (verticalScrollBar()->isVisible()) s.setWidth(s.width() + style()->pixelMetric(QStyle::PM_ScrollBarExtent)); s += QSize(frameWidth() * 2, frameWidth() * 2); // size hint: 4 lines of text... s.setHeight(s.height() + fontMetrics().lineSpacing() * 3); return s; } void ButtonSource::hideAllButtons() { for (int i = 0; i < count(); i++) { item(i)->setHidden(true); } } void ButtonSource::showAllButtons() { for (int i = 0; i < count(); i++) { item(i)->setHidden(false); } } void ButtonSource::showButton(QChar btn) { for (int i = 0; i < count(); i++) { ButtonSourceItem *buttonItem = dynamic_cast(item(i)); if (buttonItem && buttonItem->button().type == btn) { item(i)->setHidden(false); return; } } } void ButtonSource::hideButton(QChar btn) { for (int i = 0; i < count(); i++) { ButtonSourceItem *buttonItem = dynamic_cast(item(i)); if (buttonItem && buttonItem->button().type == btn && !buttonItem->button().duplicate) { item(i)->setHidden(true); return; } } } void ButtonSource::dragMoveEvent(QDragMoveEvent *e) { e->setAccepted(ButtonDrag::canDecode(e)); } void ButtonSource::dragEnterEvent(QDragEnterEvent *e) { e->setAccepted(ButtonDrag::canDecode(e)); } void ButtonSource::dropEvent(QDropEvent *e) { if (ButtonDrag::canDecode(e)) { emit dropped(); e->accept(); } else { e->ignore(); } } void ButtonSource::mousePressEvent(QMouseEvent *e) { ButtonSourceItem *i = dynamic_cast(itemAt(e->pos())); if (i) { ButtonDrag *bd = new ButtonDrag(i->button()); QDrag *drag = new QDrag(this); drag->setMimeData(bd); drag->setPixmap(bitmapPixmap(i->button().icon, palette().color(QPalette::WindowText))); drag->exec(); } } ButtonDropSiteItem::ButtonDropSiteItem(const Button& btn) : m_button(btn) { } ButtonDropSiteItem::~ButtonDropSiteItem() { } Button ButtonDropSiteItem::button() { return m_button; } int ButtonDropSiteItem::width() { // return m_button.icon.width(); return 20; } int ButtonDropSiteItem::height() { // return m_button.icon.height(); return 20; } void ButtonDropSiteItem::draw(QPainter *p, const QPalette& cg, const QRect &r) { if (m_button.supported) p->setPen(cg.color(QPalette::WindowText)); else p->setPen(cg.color(QPalette::Disabled, QPalette::WindowText)); QBitmap &i = m_button.icon; p->drawPixmap(r.left() + (r.width() - i.width()) / 2, r.top() + (r.height() - i.height()) / 2, i); } ButtonDropSite::ButtonDropSite(QWidget* parent) : QFrame(parent), m_selected(0) { setAcceptDrops(true); setFrameShape(WinPanel); setFrameShadow(Raised); setMinimumHeight(26); setMaximumHeight(26); setMinimumWidth(250); // Ensure buttons will fit setCursor(Qt::OpenHandCursor); } ButtonDropSite::~ButtonDropSite() { clearLeft(); clearRight(); } void ButtonDropSite::clearLeft() { while (!buttonsLeft.isEmpty()) { ButtonDropSiteItem *item = buttonsLeft.first(); if (removeButton(item)) { emit buttonRemoved(item->button().type); delete item; } } } void ButtonDropSite::clearRight() { while (!buttonsRight.isEmpty()) { ButtonDropSiteItem *item = buttonsRight.first(); if (removeButton(item)) { emit buttonRemoved(item->button().type); delete item; } } } void ButtonDropSite::dragMoveEvent(QDragMoveEvent* e) { QPoint p = e->pos(); if (leftDropArea().contains(p) || rightDropArea().contains(p) || buttonAt(p)) { e->accept(); // 2 pixel wide drop visualizer... QRect r = contentsRect(); int x = -1; if (leftDropArea().contains(p)) { x = leftDropArea().left(); } else if (rightDropArea().contains(p)) { x = rightDropArea().right() + 1; } else { ButtonDropSiteItem *item = buttonAt(p); if (item) { if (p.x() < item->rect.left() + item->rect.width() / 2) { x = item->rect.left(); } else { x = item->rect.right() + 1; } } } if (x != -1) { QRect tmpRect(x, r.y(), 2, r.height()); if (tmpRect != m_oldDropVisualizer) { cleanDropVisualizer(); m_oldDropVisualizer = tmpRect; update(tmpRect); } } } else { e->ignore(); cleanDropVisualizer(); } } void ButtonDropSite::cleanDropVisualizer() { if (m_oldDropVisualizer.isValid()) { QRect rect = m_oldDropVisualizer; m_oldDropVisualizer = QRect(); // rect is invalid update(rect); } } void ButtonDropSite::dragEnterEvent(QDragEnterEvent* e) { if (ButtonDrag::canDecode(e)) e->accept(); } void ButtonDropSite::dragLeaveEvent(QDragLeaveEvent* /* e */) { cleanDropVisualizer(); } void ButtonDropSite::dropEvent(QDropEvent* e) { cleanDropVisualizer(); QPoint p = e->pos(); // collect information where to insert the dropped button ButtonList *buttonList = 0; int buttonPosition; if (leftDropArea().contains(p)) { buttonList = &buttonsLeft; buttonPosition = buttonsLeft.size(); } else if (rightDropArea().contains(p)) { buttonList = &buttonsRight; buttonPosition = 0; } else { ButtonDropSiteItem *aboveItem = buttonAt(p); if (!aboveItem) return; // invalid drop. hasn't occurred _over_ a button (or left/right dropArea), return... int pos; if (!getItemPos(aboveItem, buttonList, pos)) { // didn't find the aboveItem. unlikely to happen since buttonAt() already seems to have found // something valid. anyway... return; } // got the list and the aboveItem position. now determine if the item should be inserted // before aboveItem or after aboveItem. QRect aboveItemRect = aboveItem->rect; if (!aboveItemRect.isValid()) return; if (p.x() < aboveItemRect.left() + aboveItemRect.width() / 2) { // insert before the item buttonPosition = pos; } else { buttonPosition = pos + 1; } } // know where to insert the button. now see if we can use an existing item (drag within the widget = move) // orneed to create a new one ButtonDropSiteItem *buttonItem = 0; if (e->source() == this && m_selected) { ButtonList *oldList = 0; int oldPos; if (getItemPos(m_selected, oldList, oldPos)) { if (oldPos == buttonPosition && oldList == buttonList) return; // button didn't change its position during the drag... oldList->removeAt(oldPos); buttonItem = m_selected; // If we're inserting to the right of oldPos, in the same list, // better adjust the index.. if (buttonList == oldList && buttonPosition > oldPos) --buttonPosition; } else { return; // m_selected not found, return... } } else { // create new button from the drop object... Button btn; if (ButtonDrag::decode(e, btn)) { buttonItem = new ButtonDropSiteItem(btn); } else { return; // something has gone wrong while we were trying to decode the drop event } } // now the item can actually be inserted into the list! :) (*buttonList).insert(buttonPosition, buttonItem); emit buttonAdded(buttonItem->button().type); emit changed(); recalcItemGeometry(); update(); } bool ButtonDropSite::getItemPos(ButtonDropSiteItem *item, ButtonList* &list, int &pos) { if (!item) return false; pos = buttonsLeft.indexOf(item); // try the left list first... if (pos >= 0) { list = &buttonsLeft; return true; } pos = buttonsRight.indexOf(item); // try the right list... if (pos >= 0) { list = &buttonsRight; return true; } list = 0; pos = -1; return false; } QRect ButtonDropSite::leftDropArea() { // return a 10 pixel drop area... QRect r = contentsRect(); int leftButtonsWidth = calcButtonListWidth(buttonsLeft); return QRect(r.left() + leftButtonsWidth, r.top(), 10, r.height()); } QRect ButtonDropSite::rightDropArea() { // return a 10 pixel drop area... QRect r = contentsRect(); int rightButtonsWidth = calcButtonListWidth(buttonsRight); return QRect(r.right() - rightButtonsWidth - 10, r.top(), 10, r.height()); } void ButtonDropSite::mousePressEvent(QMouseEvent* e) { QDrag *drag = new QDrag(this); m_selected = buttonAt(e->pos()); if (m_selected) { ButtonDrag *bd = new ButtonDrag(m_selected->button()); drag->setMimeData(bd); drag->setPixmap(bitmapPixmap(m_selected->button().icon, palette().color(QPalette::WindowText))); drag->exec(); } } void ButtonDropSite::resizeEvent(QResizeEvent*) { recalcItemGeometry(); } void ButtonDropSite::recalcItemGeometry() { QRect r = contentsRect(); // update the geometry of the items in the left button list int offset = r.left(); for (ButtonList::const_iterator it = buttonsLeft.constBegin(); it != buttonsLeft.constEnd(); ++it) { int w = (*it)->width(); (*it)->rect = QRect(offset, r.top(), w, (*it)->height()); offset += w; } // the right button list... offset = r.right() - calcButtonListWidth(buttonsRight); for (ButtonList::const_iterator it = buttonsRight.constBegin(); it != buttonsRight.constEnd(); ++it) { int w = (*it)->width(); (*it)->rect = QRect(offset, r.top(), w, (*it)->height()); offset += w; } } ButtonDropSiteItem *ButtonDropSite::buttonAt(QPoint p) { // try to find the item in the left button list for (ButtonList::const_iterator it = buttonsLeft.constBegin(); it != buttonsLeft.constEnd(); ++it) { if ((*it)->rect.contains(p)) { return *it; } } // try to find the item in the right button list for (ButtonList::const_iterator it = buttonsRight.constBegin(); it != buttonsRight.constEnd(); ++it) { if ((*it)->rect.contains(p)) { return *it; } } return 0; } bool ButtonDropSite::removeButton(ButtonDropSiteItem *item) { if (!item) return false; // try to remove the item from the left button list if (buttonsLeft.removeAll(item) >= 1) { return true; } // try to remove the item from the right button list if (buttonsRight.removeAll(item) >= 1) { return true; } return false; } int ButtonDropSite::calcButtonListWidth(const ButtonList& btns) { int w = 0; for (ButtonList::const_iterator it = btns.constBegin(); it != btns.constEnd(); ++it) { w += (*it)->width(); } return w; } bool ButtonDropSite::removeSelectedButton() { bool succ = removeButton(m_selected); if (succ) { emit buttonRemoved(m_selected->button().type); emit changed(); delete m_selected; m_selected = 0; recalcItemGeometry(); update(); // repaint... } return succ; } void ButtonDropSite::drawButtonList(QPainter *p, const ButtonList& btns, int offset) { for (ButtonList::const_iterator it = btns.constBegin(); it != btns.constEnd(); ++it) { QRect itemRect = (*it)->rect; if (itemRect.isValid()) { (*it)->draw(p, palette(), itemRect); } offset += (*it)->width(); } } void ButtonDropSite::paintEvent(QPaintEvent* /*pe*/) { QPainter p(this); int leftoffset = calcButtonListWidth(buttonsLeft); int rightoffset = calcButtonListWidth(buttonsRight); int offset = 3; QRect r = contentsRect(); // Shrink by 1 r.translate(1 + leftoffset, 1); r.setWidth(r.width() - 2 - leftoffset - rightoffset); r.setHeight(r.height() - 2); drawButtonList(&p, buttonsLeft, offset); QColor c1(palette().color(QPalette::Mid)); p.fillRect(r, c1); p.setPen(palette().color(QPalette::WindowText)); p.setFont(KGlobalSettings::windowTitleFont()); p.drawText(r.adjusted(4, 0, -4, 0), Qt::AlignLeft | Qt::AlignVCenter, i18n("KDE")); offset = geometry().width() - 3 - rightoffset; drawButtonList(&p, buttonsRight, offset); if (m_oldDropVisualizer.isValid()) { p.fillRect(m_oldDropVisualizer, Qt::Dense4Pattern); } } ButtonSourceItem::ButtonSourceItem(QListWidget * parent, const Button& btn) : QListWidgetItem(parent), m_button(btn) { setButton(btn); } ButtonSourceItem::~ButtonSourceItem() { } void ButtonSourceItem::setButton(const Button& btn) { m_button = btn; if (btn.supported) { setText(btn.name); setIcon(bitmapPixmap(btn.icon, QApplication::palette().color(QPalette::Text))); setForeground(QApplication::palette().brush(QPalette::Text)); } else { setText(i18n("%1 (unavailable)", btn.name)); setIcon(bitmapPixmap(btn.icon, QApplication::palette().color(QPalette::Disabled, QPalette::Text))); setForeground(QApplication::palette().brush(QPalette::Disabled, QPalette::Text)); } } Button ButtonSourceItem::button() const { return m_button; } ButtonPositionWidget::ButtonPositionWidget(QWidget *parent) : QWidget(parent), m_factory(0) { QVBoxLayout *layout = new QVBoxLayout(this); layout->setMargin(0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); QLabel* label = new QLabel(this); m_dropSite = new ButtonDropSite(this); label->setWordWrap(true); label->setText(i18n("To add or remove titlebar buttons, simply drag items " "between the available item list and the titlebar preview. Similarly, " "drag items within the titlebar preview to re-position them.")); m_buttonSource = new ButtonSource(this); m_buttonSource->setObjectName(QLatin1String("button_source")); layout->addWidget(label); layout->addWidget(m_dropSite); layout->addWidget(m_buttonSource); connect(m_dropSite, SIGNAL(buttonAdded(QChar)), m_buttonSource, SLOT(hideButton(QChar))); connect(m_dropSite, SIGNAL(buttonRemoved(QChar)), m_buttonSource, SLOT(showButton(QChar))); connect(m_buttonSource, SIGNAL(dropped()), m_dropSite, SLOT(removeSelectedButton())); connect(m_dropSite, SIGNAL(changed()), SIGNAL(changed())); // insert all possible buttons into the source (backwards to keep the preferred order...) bool dummy; m_supportedButtons = "MSHIAX_FBLR"; #ifdef KWIN_BUILD_KAPPMENU KConfig config("kdeglobals", KConfig::FullConfig); KConfigGroup configGroup = config.group("Appmenu Style"); QString style = configGroup.readEntry("Style", "InApplication"); if (style == "ButtonVertical") { m_supportedButtons = "MNSHIAX_FBLR"; // support all buttons new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonApplicationMenu, dummy)); } #endif new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonResize, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonShade, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonKeepBelow, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonKeepAbove, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonClose, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonMaximizeRestore, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonMinimize, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonQuickHelp, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonOnAllDesktops, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonMenu, dummy)); new ButtonSourceItem(m_buttonSource, getButton(KDecorationDefines::DecorationButtonExplicitSpacer, dummy)); } ButtonPositionWidget::~ButtonPositionWidget() { } Button ButtonPositionWidget::getButton(KDecorationDefines::DecorationButton type, bool& success) { success = true; switch (type) { case KDecorationDefines::DecorationButtonResize: { QBitmap bmp = QBitmap::fromData(QSize(resize_width, resize_height), resize_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Resize"), bmp, type, false, m_supportedButtons.contains('R')); } case KDecorationDefines::DecorationButtonShade: { QBitmap bmp = QBitmap::fromData(QSize(shade_width, shade_height), shade_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Shade"), bmp, type, false, m_supportedButtons.contains('L')); } case KDecorationDefines::DecorationButtonKeepBelow: { QBitmap bmp = QBitmap::fromData(QSize(keepbelowothers_width, keepbelowothers_height), keepbelowothers_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Keep Below Others"), bmp, type, false, m_supportedButtons.contains('B')); } case KDecorationDefines::DecorationButtonKeepAbove: { QBitmap bmp = QBitmap::fromData(QSize(keepaboveothers_width, keepaboveothers_height), keepaboveothers_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Keep Above Others"), bmp, type, false, m_supportedButtons.contains('F')); } case KDecorationDefines::DecorationButtonClose: { QBitmap bmp = QBitmap::fromData(QSize(close_width, close_height), close_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Close"), bmp, type, false, m_supportedButtons.contains('X')); } case KDecorationDefines::DecorationButtonMaximizeRestore: { QBitmap bmp = QBitmap::fromData(QSize(maximize_width, maximize_height), maximize_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Maximize"), bmp, type, false, m_supportedButtons.contains('A')); } case KDecorationDefines::DecorationButtonMinimize: { QBitmap bmp = QBitmap::fromData(QSize(minimize_width, minimize_height), minimize_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Minimize"), bmp, type, false, m_supportedButtons.contains('I')); } case KDecorationDefines::DecorationButtonQuickHelp: { QBitmap bmp = QBitmap::fromData(QSize(help_width, help_height), help_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("Help"), bmp, type, false, m_supportedButtons.contains('H')); } case KDecorationDefines::DecorationButtonOnAllDesktops: { QBitmap bmp = QBitmap::fromData(QSize(onalldesktops_width, onalldesktops_height), onalldesktops_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("On All Desktops"), bmp, type, false, m_supportedButtons.contains('S')); } case KDecorationDefines::DecorationButtonMenu: { QBitmap bmp = QBitmap::fromData(QSize(menu_width, menu_height), menu_bits); bmp.createMaskFromColor(Qt::white); return Button(i18nc("Button showing window actions menu", "Window Menu"), bmp, type, false, m_supportedButtons.contains('M')); #ifdef KWIN_BUILD_KAPPMENU } case KDecorationDefines::DecorationButtonApplicationMenu: { QBitmap bmp = QBitmap::fromData(QSize(menu_width, menu_height), menu_bits); bmp.createMaskFromColor(Qt::white); return Button(i18nc("Button showing application menu imported from dbusmenu", "Application Menu"), bmp, type, false, m_supportedButtons.contains('N')); #endif } case KDecorationDefines::DecorationButtonExplicitSpacer: { QBitmap bmp = QBitmap::fromData(QSize(spacer_width, spacer_height), spacer_bits); bmp.createMaskFromColor(Qt::white); return Button(i18n("--- spacer ---"), bmp, type, true, m_supportedButtons.contains('_')); } default: success = false; return Button(); } } QList ButtonPositionWidget::buttonsLeft() const { QList ret; for (auto button : m_dropSite->buttonsLeft) { ret << button->button().type; } return ret; } QList ButtonPositionWidget::buttonsRight() const { QList ret; for (auto button : m_dropSite->buttonsRight) { ret << button->button().type; } return ret; } void ButtonPositionWidget::setButtonsLeft(const QList &buttons) { // to keep the button lists consistent, first remove all left buttons, then add buttons again... m_dropSite->clearLeft(); for (auto button : buttons) { bool succ = false; Button btn = getButton(button, succ); if (succ) { m_dropSite->buttonsLeft.append(new ButtonDropSiteItem(btn)); m_buttonSource->hideButton(btn.type); } } m_dropSite->recalcItemGeometry(); m_dropSite->update(); } void ButtonPositionWidget::setButtonsRight(const QList &buttons) { // to keep the button lists consistent, first remove all left buttons, then add buttons again... m_dropSite->clearRight(); for (auto button : buttons) { bool succ = false; Button btn = getButton(button, succ); if (succ) { m_dropSite->buttonsRight.append(new ButtonDropSiteItem(btn)); m_buttonSource->hideButton(btn.type); } } m_dropSite->recalcItemGeometry(); m_dropSite->update(); } } // namespace KWin #include "buttons.moc" // vim: ts=4 // kate: space-indent off; tab-width 4;