diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 48e9f27..fd02426 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,16 +1,16 @@ -set(appmenuapplet_SRCS - appmenuapplet.cpp +set(activewindowcontrolapplet_SRCS + activewindowcontrolapplet.cpp ) -add_library(plasma_applet_activewindowcontrol MODULE ${appmenuapplet_SRCS}) +add_library(plasma_applet_activewindowcontrol MODULE ${activewindowcontrolapplet_SRCS}) kcoreaddons_desktop_to_json(plasma_applet_activewindowcontrol ../package/metadata.desktop) target_link_libraries(plasma_applet_activewindowcontrol Qt5::Widgets Qt5::Quick Qt5::DBus KF5::Plasma KF5::WindowSystem) install(TARGETS plasma_applet_activewindowcontrol DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/applets) diff --git a/lib/appmenuapplet.cpp b/lib/activewindowcontrolapplet.cpp similarity index 79% rename from lib/appmenuapplet.cpp rename to lib/activewindowcontrolapplet.cpp index 7b5b880..51883c2 100644 --- a/lib/appmenuapplet.cpp +++ b/lib/activewindowcontrolapplet.cpp @@ -1,389 +1,396 @@ /* * Copyright 2016 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ -#include "appmenuapplet.h" +#include "activewindowcontrolapplet.h" #include "../plugin/appmenumodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -int AppMenuApplet::s_refs = 0; +int ActiveWindowControlApplet::s_refs = 0; -static const QString s_viewService(QStringLiteral("org.kde.kappmenuview")); +namespace { +QString viewService() { return QStringLiteral("org.kde.kappmenuview"); } +} -AppMenuApplet::AppMenuApplet(QObject *parent, const QVariantList &data) +ActiveWindowControlApplet::ActiveWindowControlApplet(QObject *parent, const QVariantList &data) : Plasma::Applet(parent, data) { /*it registers or unregisters the service when the destroyed value of the applet change, and not in the dtor, because: when we "delete" an applet, it just hides it for about a minute setting its status to destroyed, in order to be able to do a clean undo: if we undo, there will be another destroyedchanged and destroyed will be false. When this happens, if we are the only appmenu applet existing, the dbus interface will have to be registered again*/ connect(this, &Applet::destroyedChanged, this, [this](bool destroyed) { if (destroyed) { unregisterService(); } else { registerService(); } }); // get current aurorae decoration theme if there is one set refreshAuroraeTheme(); } -AppMenuApplet::~AppMenuApplet() = default; +ActiveWindowControlApplet::~ActiveWindowControlApplet() = default; -void AppMenuApplet::init() +void ActiveWindowControlApplet::init() { } -AppMenuModel *AppMenuApplet::model() const +AppMenuModel *ActiveWindowControlApplet::model() const { return m_model; } -void AppMenuApplet::refreshAuroraeTheme() +void ActiveWindowControlApplet::refreshAuroraeTheme() { const KConfig kwinConfig(QString("kwinrc"), KConfig::OpenFlag::SimpleConfig); const QByteArray decorationGroupName = QString("org.kde.kdecoration2").toUtf8(); if (!kwinConfig.hasGroup(decorationGroupName)) { return; } const KConfigGroup decorationGroup = kwinConfig.group(decorationGroupName); const QString decorationLibrary = decorationGroup.readEntry(QString("library"), QString()); if (decorationLibrary == QString("org.kde.kwin.aurorae")) { const QString decorationTheme = decorationGroup.readEntry(QString("theme"), QString()); if (decorationTheme.startsWith(QString("__aurorae__"))) { const QString separator("__"); const QString themeName = decorationTheme.section(separator, -1, -1); const QString themeType = decorationTheme.section(separator, -2, -2); QString themePath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("aurorae/themes/") + themeName, QStandardPaths::LocateDirectory); if (!themePath.isEmpty()) { m_auroraeDecorationPath = themePath; m_auroraeDecorationType = themeType; emit auroraeThemePathChanged(); emit auroraeThemeTypeChanged(); return; } } } m_auroraeDecorationPath = QString(); emit auroraeThemePathChanged(); } -QString AppMenuApplet::auroraeThemePath() const +QString ActiveWindowControlApplet::auroraeThemePath() const { return m_auroraeDecorationPath; } -QString AppMenuApplet::auroraeThemeType() const +QString ActiveWindowControlApplet::auroraeThemeType() const { return m_auroraeDecorationType; } -QString AppMenuApplet::extensionForTheme(const QString &themeDirectoryPath) +QString ActiveWindowControlApplet::extensionForTheme(const QString &themeDirectoryPath) { if (themeDirectoryPath.isEmpty()) { return QString(); } qDebug() << "determine theme extension from path " << themeDirectoryPath; QDir themeDir(themeDirectoryPath); QStringList nameFilters(QString("close.svgz")); QStringList filteredFiles = themeDir.entryList(nameFilters); qDebug() << "filtered: " << filteredFiles; if (!filteredFiles.isEmpty()) { return QString("svgz"); } return QString("svg"); } -QString AppMenuApplet::translateThemePath(const QString &themeDirectoryPath) +QString ActiveWindowControlApplet::translateThemePath(const QString &themeDirectoryPath) { qDebug() << "translating path " << themeDirectoryPath; return KShell::tildeExpand(themeDirectoryPath); } -void AppMenuApplet::registerService() +void ActiveWindowControlApplet::registerService() { qDebug() << "registering appmenu service"; ++s_refs; - //if we're the first, regster the service + //if we're the first, regster the service if (s_refs == 1) { qDebug() << " -> connecting to DBus"; - QDBusConnection::sessionBus().interface()->registerService(s_viewService, - QDBusConnectionInterface::QueueService, - QDBusConnectionInterface::DontAllowReplacement); - } -} + QDBusConnection::sessionBus().interface()->registerService(viewService(), + QDBusConnectionInterface::QueueService, + QDBusConnectionInterface::DontAllowReplacement); + } + } -void AppMenuApplet::unregisterService() +void ActiveWindowControlApplet::unregisterService() { qDebug() << "unregistering from appmenu service"; //if we were the last, unregister if (--s_refs == 0) { qDebug() << " -> disconnecting from DBus"; - QDBusConnection::sessionBus().interface()->unregisterService(s_viewService); + QDBusConnection::sessionBus().interface()->unregisterService(viewService()); } if (s_refs < 0) { s_refs = 0; } } -bool AppMenuApplet::enabled() const +bool ActiveWindowControlApplet::enabled() const { return m_enabled; } -void AppMenuApplet::setEnabled(bool enabled) +void ActiveWindowControlApplet::setEnabled(bool enabled) { if (enabled == m_enabled) { return; } if (enabled) { registerService(); } else { unregisterService(); } m_enabled = enabled; } -void AppMenuApplet::setModel(AppMenuModel *model) +void ActiveWindowControlApplet::setModel(AppMenuModel *model) { if (m_model != model) { m_model = model; emit modelChanged(); } } -int AppMenuApplet::view() const +int ActiveWindowControlApplet::view() const { return m_viewType; } -void AppMenuApplet::setView(int type) +void ActiveWindowControlApplet::setView(int type) { if (m_viewType != type) { m_viewType = type; emit viewChanged(); } } -int AppMenuApplet::currentIndex() const +int ActiveWindowControlApplet::currentIndex() const { return m_currentIndex; } -void AppMenuApplet::setCurrentIndex(int currentIndex) +void ActiveWindowControlApplet::setCurrentIndex(int currentIndex) { if (m_currentIndex != currentIndex) { m_currentIndex = currentIndex; emit currentIndexChanged(); } } -QQuickItem *AppMenuApplet::buttonGrid() const +QQuickItem *ActiveWindowControlApplet::buttonGrid() const { return m_buttonGrid; } -void AppMenuApplet::setButtonGrid(QQuickItem *buttonGrid) +void ActiveWindowControlApplet::setButtonGrid(QQuickItem *buttonGrid) { if (m_buttonGrid != buttonGrid) { m_buttonGrid = buttonGrid; emit buttonGridChanged(); } } -QMenu *AppMenuApplet::createMenu(int idx) const +QMenu *ActiveWindowControlApplet::createMenu(int idx) const { QMenu *menu = nullptr; QAction *action = nullptr; if (view() == CompactView) { menu = new QMenu(); for (int i=0; irowCount(); i++) { const QModelIndex index = m_model->index(i, 0); const QVariant data = m_model->data(index, AppMenuModel::ActionRole); action = (QAction *)data.value(); menu->addAction(action); } menu->setAttribute(Qt::WA_DeleteOnClose); } else if (view() == FullView) { const QModelIndex index = m_model->index(idx, 0); const QVariant data = m_model->data(index, AppMenuModel::ActionRole); action = (QAction *)data.value(); if (action) { menu = action->menu(); } } return menu; } -void AppMenuApplet::onMenuAboutToHide() +void ActiveWindowControlApplet::onMenuAboutToHide() { setCurrentIndex(-1); } -void AppMenuApplet::trigger(QQuickItem *ctx, int idx) +void ActiveWindowControlApplet::trigger(QQuickItem *ctx, int idx) { if (m_currentIndex == idx) { return; } if (!ctx || !ctx->window() || !ctx->window()->screen()) { return; } QMenu *actionMenu = createMenu(idx); if (actionMenu) { //this is a workaround where Qt will fail to realise a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [ctx]() { if (ctx && ctx->window() && ctx->window()->mouseGrabberItem()) { // FIXME event forge thing enters press and hold move mode :/ ctx->window()->mouseGrabberItem()->ungrabMouse(); } }; QTimer::singleShot(0, ctx, ungrabMouseHack); //end workaround const auto &geo = ctx->window()->screen()->availableVirtualGeometry(); QPoint pos = ctx->window()->mapToGlobal(ctx->mapToScene(QPointF()).toPoint()); if (location() == Plasma::Types::TopEdge) { pos.setY(pos.y() + ctx->height()); } actionMenu->adjustSize(); pos = QPoint(qBound(geo.x(), pos.x(), geo.x() + geo.width() - actionMenu->width()), qBound(geo.y(), pos.y(), geo.y() + geo.height() - actionMenu->height())); if (view() == FullView) { actionMenu->installEventFilter(this); } actionMenu->winId();//create window handle actionMenu->windowHandle()->setTransientParent(ctx->window()); actionMenu->popup(pos); if (view() == FullView) { // hide the old menu only after showing the new one to avoid brief flickering // in other windows as they briefly re-gain focus QMenu *oldMenu = m_currentMenu; m_currentMenu = actionMenu; if (oldMenu && oldMenu != actionMenu) { - //! dont trigger initialization of index because there is a new menu created - disconnect(oldMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide); - + //dont initialize the currentIndex when another menu is already shown + disconnect(oldMenu, &QMenu::aboutToHide, this, &ActiveWindowControlApplet::onMenuAboutToHide); oldMenu->hide(); } } setCurrentIndex(idx); // FIXME TODO connect only once - connect(actionMenu, &QMenu::aboutToHide, this, &AppMenuApplet::onMenuAboutToHide, Qt::UniqueConnection); - return; + connect(actionMenu, &QMenu::aboutToHide, this, &ActiveWindowControlApplet::onMenuAboutToHide, Qt::UniqueConnection); + } else { // is it just an action without a menu? + const QVariant data = m_model->index(idx, 0).data(AppMenuModel::ActionRole); + QAction *action = static_cast(data.value()); + if (action) { + Q_ASSERT(!action->menu()); + action->trigger(); + } } } // FIXME TODO doesn't work on submenu -bool AppMenuApplet::eventFilter(QObject *watched, QEvent *event) +bool ActiveWindowControlApplet::eventFilter(QObject *watched, QEvent *event) { auto *menu = qobject_cast(watched); if (!menu) { return false; } if (event->type() == QEvent::KeyPress) { auto *e = static_cast(event); // TODO right to left languages if (e->key() == Qt::Key_Left) { int desiredIndex = m_currentIndex - 1; emit requestActivateIndex(desiredIndex); return true; } else if (e->key() == Qt::Key_Right) { if (menu->activeAction() && menu->activeAction()->menu()) { return false; } int desiredIndex = m_currentIndex + 1; emit requestActivateIndex(desiredIndex); return true; } } else if (event->type() == QEvent::MouseMove) { auto *e = static_cast(event); if (!m_buttonGrid || !m_buttonGrid->window()) { return false; } // FIXME the panel margin breaks Fitt's law :( const QPointF &windowLocalPos = m_buttonGrid->window()->mapFromGlobal(e->globalPos()); const QPointF &buttonGridLocalPos = m_buttonGrid->mapFromScene(windowLocalPos); auto *item = m_buttonGrid->childAt(buttonGridLocalPos.x(), buttonGridLocalPos.y()); if (!item) { return false; } bool ok; const int buttonIndex = item->property("buttonIndex").toInt(&ok); if (!ok) { return false; } emit requestActivateIndex(buttonIndex); } return false; } -K_EXPORT_PLASMA_APPLET_WITH_JSON(appmenu, AppMenuApplet, "metadata.json") +K_EXPORT_PLASMA_APPLET_WITH_JSON(activewindowcontrol, ActiveWindowControlApplet, "metadata.json") -#include "appmenuapplet.moc" +#include "activewindowcontrolapplet.moc" diff --git a/lib/appmenuapplet.h b/lib/activewindowcontrolapplet.h similarity index 94% rename from lib/appmenuapplet.h rename to lib/activewindowcontrolapplet.h index 75b9c1f..002acd2 100644 --- a/lib/appmenuapplet.h +++ b/lib/activewindowcontrolapplet.h @@ -1,114 +1,114 @@ /* * Copyright 2016 Kai Uwe Broulik * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #pragma once #include #include class KDBusMenuImporter; class QQuickItem; class QMenu; class AppMenuModel; -class AppMenuApplet : public Plasma::Applet +class ActiveWindowControlApplet : public Plasma::Applet { Q_OBJECT Q_PROPERTY(AppMenuModel* model READ model WRITE setModel NOTIFY modelChanged) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(int view READ view WRITE setView NOTIFY viewChanged) Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged) Q_PROPERTY(QQuickItem *buttonGrid READ buttonGrid WRITE setButtonGrid NOTIFY buttonGridChanged) Q_PROPERTY(QString auroraeThemePath READ auroraeThemePath NOTIFY auroraeThemePathChanged) Q_PROPERTY(QString auroraeThemeType READ auroraeThemeType NOTIFY auroraeThemeTypeChanged) public: enum ViewType { FullView, CompactView }; - explicit AppMenuApplet(QObject *parent, const QVariantList &data); - ~AppMenuApplet() override; + explicit ActiveWindowControlApplet(QObject *parent, const QVariantList &data); + ~ActiveWindowControlApplet() override; void init() override; int currentIndex() const; QQuickItem *buttonGrid() const; void setButtonGrid(QQuickItem *buttonGrid); AppMenuModel *model() const; void setModel(AppMenuModel *model); bool enabled() const; void setEnabled(bool enabled); int view() const; void setView(int type); void registerService(); void unregisterService(); QString auroraeThemePath() const; QString auroraeThemeType() const; Q_INVOKABLE QString extensionForTheme(const QString &themeDirectoryPath); Q_INVOKABLE QString translateThemePath(const QString &themeDirectoryPath); Q_INVOKABLE void refreshAuroraeTheme(); signals: void modelChanged(); void enabledChanged(); void viewChanged(); void currentIndexChanged(); void buttonGridChanged(); void requestActivateIndex(int index); void auroraeThemePathChanged(); void auroraeThemeTypeChanged(); public slots: void trigger(QQuickItem *ctx, int idx); protected: bool eventFilter(QObject *watched, QEvent *event) override; private: QMenu *createMenu(int idx) const; void setCurrentIndex(int currentIndex); void onMenuAboutToHide(); int m_currentIndex = -1; bool m_enabled = false; int m_viewType = FullView; QPointer m_currentMenu; QPointer m_buttonGrid; QPointer m_model; static int s_refs; QString m_auroraeDecorationPath = QString(); QString m_auroraeDecorationType = QString("svg"); }; diff --git a/package/contents/config/main.xml b/package/contents/config/main.xml index d1559e2..da8b9f1 100644 --- a/package/contents/config/main.xml +++ b/package/contents/config/main.xml @@ -1,275 +1,278 @@ buttons|icon|title|menu 5.0 0 true true 0 0 true true 0 0 true true 0 0 true true 0 + + false + true false false false close|maximize|minimize|pin true false false false false false false 0 false 0.5 10 false 0.2 0.0 true 0 0 0 false ^(.*)\s*[—–\-:]\s*(Mozilla )?([^—–\-:]+)$ $3 — $1 Plasma Desktop :: %activity% false 1000 true false false 5 1 false false false false false false false false true false 0 0.1 1.0 true 0 0 false 0 diff --git a/package/contents/ui/AppMenu.qml b/package/contents/ui/AppMenu.qml index eb4df62..208172e 100644 --- a/package/contents/ui/AppMenu.qml +++ b/package/contents/ui/AppMenu.qml @@ -1,179 +1,156 @@ import QtQuick 2.2 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents -Item { +MouseArea { id: appmenu - anchors.fill: parent - property bool mouseInWidget: false + property bool mouseInWidget: appmenuOpened || appmenu.containsMouse property bool appmenuEnabled: plasmoid.configuration.appmenuEnabled - property bool appmenuNextToButtons: plasmoid.configuration.appmenuNextToButtons property bool appmenuFillHeight: plasmoid.configuration.appmenuFillHeight property bool appmenuFontBold: plasmoid.configuration.appmenuFontBold - property bool appmenuDoNotHide: plasmoid.configuration.appmenuDoNotHide property bool appmenuEnabledAndNonEmpty: appmenuEnabled && appMenuModel !== null && appMenuModel.menuAvailable property bool appmenuOpened: appmenuEnabled && plasmoid.nativeInterface.currentIndex > -1 property var appMenuModel: null - property bool appmenuButtonsOffsetEnabled: !buttonsStandalone && appmenuNextToButtons && childrenRect.width > 0 - property double appmenuOffsetWidth: visible && appmenuNextToIconAndText && !appmenuSwitchSidesWithIconAndText - ? appmenu.childrenRect.width + (appmenuButtonsOffsetEnabled ? buttonsItem.width : 0) + appmenuSideMargin*2 - : 0 + visible: appmenuEnabledAndNonEmpty && !main.noWindowActive - visible: appmenuEnabledAndNonEmpty && !noWindowActive && (appmenuDoNotHide || main.mouseHover || appmenuOpened) + property bool showItem + + opacity: showItem ? 1 : 0 + + hoverEnabled: true GridLayout { id: buttonGrid Layout.minimumWidth: implicitWidth Layout.minimumHeight: implicitHeight flow: GridLayout.LeftToRight rowSpacing: 0 columnSpacing: 0 anchors.top: parent.top anchors.left: parent.left - property double placementOffsetButtons: appmenuNextToButtons && buttonsItem.visible ? buttonsItem.width + appmenuSideMargin : 0 - property double placementOffset: appmenuNextToIconAndText && appmenuSwitchSidesWithIconAndText - //? activeWindowListView.anchors.leftMargin + windowTitle.anchors.leftMargin + - ? windowTitle.anchors.leftMargin + windowTitle.contentWidthwindowTitle.contentWidth + appmenuSideMargin - : placementOffsetButtons - - anchors.leftMargin: (bp === 1 || bp === 3) ? parent.width - width - placementOffset : placementOffset - anchors.topMargin: (bp === 2 || bp === 3) ? 0 : parent.height - height - Component.onCompleted: { plasmoid.nativeInterface.buttonGrid = buttonGrid plasmoid.nativeInterface.enabled = appmenuEnabled } Connections { target: plasmoid.nativeInterface onRequestActivateIndex: { var idx = Math.max(0, Math.min(buttonRepeater.count - 1, index)) var button = buttonRepeater.itemAt(index) if (button) { button.clicked(null) } } } Repeater { id: buttonRepeater model: null MouseArea { id: appmenuButton hoverEnabled: true readonly property int buttonIndex: index property bool menuOpened: plasmoid.nativeInterface.currentIndex === index Layout.preferredWidth: appmenuButtonBackground.width Layout.preferredHeight: appmenuButtonBackground.height Rectangle { id: appmenuButtonBackground border.color: 'transparent' width: appmenuButtonTitle.implicitWidth + units.smallSpacing * 3 height: appmenuFillHeight ? appmenu.height : appmenuButtonTitle.implicitHeight + units.smallSpacing color: menuOpened ? theme.highlightColor : 'transparent' radius: units.smallSpacing / 2 } PlasmaComponents.Label { id: appmenuButtonTitle anchors.top: appmenuButtonBackground.top anchors.bottom: appmenuButtonBackground.bottom verticalAlignment: Text.AlignVCenter anchors.horizontalCenter: appmenuButtonBackground.horizontalCenter font.pixelSize: fontPixelSize * plasmoid.configuration.appmenuButtonTextSizeScale text: activeMenu.replace('&', '') font.weight: appmenuFontBold ? Font.Bold : theme.defaultFont.weight } onClicked: { plasmoid.nativeInterface.trigger(this, index) } onEntered: { appmenuButtonBackground.border.color = theme.highlightColor } onExited: { appmenuButtonBackground.border.color = 'transparent' } } } } - Rectangle { - id: separator - anchors.left: buttonGrid.left - anchors.leftMargin: appmenuSwitchSidesWithIconAndText ? - appmenuSideMargin * 0.5 : buttonGrid.width + appmenuSideMargin * 0.5 - anchors.verticalCenter: buttonGrid.verticalCenter - height: 0.8 * parent.height - width: 1 - visible: appmenuNextToIconAndText && plasmoid.configuration.appmenuSeparatorEnabled - color: theme.textColor - opacity: 0.4 - } - function initializeAppModel() { if (appMenuModel !== null) { return } print('initializing appMenuModel...') try { appMenuModel = Qt.createQmlObject( 'import QtQuick 2.2;\ import org.kde.plasma.plasmoid 2.0;\ import org.kde.private.activeWindowControl 1.0 as ActiveWindowControlPrivate;\ ActiveWindowControlPrivate.AppMenuModel {\ id: appMenuModel;\ Component.onCompleted: {\ plasmoid.nativeInterface.model = appMenuModel\ }\ }', main) } catch (e) { print('appMenuModel failed to initialize: ' + e) } print('initializing appmenu...DONE ' + appMenuModel) if (appMenuModel !== null) { resetAppmenuModel() } } function resetAppmenuModel() { if (appmenuEnabled) { initializeAppModel() if (appMenuModel === null) { return } print('setting model in QML: ' + appMenuModel) for (var key in appMenuModel) { print(' ' + key + ' -> ' + appMenuModel[key]) } plasmoid.nativeInterface.model = appMenuModel buttonRepeater.model = appMenuModel } else { plasmoid.nativeInterface.model = null buttonRepeater.model = null } } onAppmenuEnabledChanged: { appmenu.resetAppmenuModel() if (appMenuModel !== null) { plasmoid.nativeInterface.enabled = appmenuEnabled } } } \ No newline at end of file diff --git a/package/contents/ui/ControlButtons.qml b/package/contents/ui/ControlButtons.qml index 4599e53..74a9e9b 100644 --- a/package/contents/ui/ControlButtons.qml +++ b/package/contents/ui/ControlButtons.qml @@ -1,37 +1,37 @@ import QtQuick 2.2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore Item { id: controlButtons property bool mouseInWidget: false property var controlButtonsModel property double controlButtonsHeight: parent.height * plasmoid.configuration.buttonSize property bool showItem opacity: showItem ? 1 : 0 height: main.buttonsVerticalCenter ? parent.height : controlButtonsHeight - width: controlButtonsHeight + ((model.count - 1) * (controlButtonsHeight + main.controlButtonsSpacing)) + width: controlButtonsHeight + ((controlButtonsModel.count - 1) * (controlButtonsHeight + main.controlButtonsSpacing)) ListView { orientation: ListView.Horizontal spacing: main.controlButtonsSpacing visible: true model: controlButtonsModel anchors.fill: parent delegate: ControlButton { } } } diff --git a/package/contents/ui/WindowTitle.qml b/package/contents/ui/WindowTitle.qml index 5e246d1..742673d 100644 --- a/package/contents/ui/WindowTitle.qml +++ b/package/contents/ui/WindowTitle.qml @@ -1,49 +1,50 @@ import QtQuick 2.2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents PlasmaComponents.Label { id: titleItem - property double iconMargin: (plasmoid.configuration.showWindowIcon ? iconItem.width : 0) - property double properWidth: parent.width - iconMargin - iconAndTextSpacing +// property double iconMargin: (plasmoid.configuration.showWindowIcon ? iconItem.width : 0) +// property double properWidth: parent.width - iconMargin - iconAndTextSpacing property double properHeight: parent.height property bool noElide: fitText === 2 || (fitText === 1 && mouseHover) property int allowFontSizeChange: 3 property int minimumPixelSize: 8 - property bool limitTextWidth: plasmoid.configuration.limitTextWidth - property int textWidthLimit: plasmoid.configuration.textWidthLimit - property double computedWidth: (limitTextWidth ? (implicitWidth > textWidthLimit ? textWidthLimit : implicitWidth) : properWidth)// - activeWindowListView.buttonsBetweenAddition +// property bool limitTextWidth: plasmoid.configuration.limitTextWidth +// property int textWidthLimit: plasmoid.configuration.textWidthLimit +// property double computedWidth: (limitTextWidth ? (implicitWidth > textWidthLimit ? textWidthLimit : implicitWidth) : properWidth)// - activeWindowListView.buttonsBetweenAddition verticalAlignment: Text.AlignVCenter text: plasmoid.configuration.noWindowText wrapMode: Text.Wrap - width: computedWidth +// width: computedWidth + height: properHeight elide: noElide ? Text.ElideNone : Text.ElideRight visible: plasmoid.configuration.showWindowTitle font.pixelSize: fontPixelSize font.pointSize: -1 font.weight: fontBold || (appmenuBoldTitleWhenMenuDisplayed && appmenu.visible) ? Font.Bold : theme.defaultFont.weight font.family: fontFamily || theme.defaultFont.family onTextChanged: { font.pixelSize = fontPixelSize allowFontSizeChange = 3 } onNoElideChanged: { font.pixelSize = fontPixelSize allowFontSizeChange = 3 } onPaintedHeightChanged: { if (allowFontSizeChange > 0 && noElide && paintedHeight > properHeight) { var newPixelSize = (properHeight / paintedHeight) * fontPixelSize font.pixelSize = newPixelSize < minimumPixelSize ? minimumPixelSize : newPixelSize } allowFontSizeChange-- } } \ No newline at end of file diff --git a/package/contents/ui/config/ConfigLayout.qml b/package/contents/ui/config/ConfigLayout.qml index c29784c..1619a34 100644 --- a/package/contents/ui/config/ConfigLayout.qml +++ b/package/contents/ui/config/ConfigLayout.qml @@ -1,318 +1,332 @@ import QtQuick 2.2 import QtQuick.Controls 1.3 import QtQuick.Layouts 1.1 import QtQml.Models 2.1 Item { property string cfg_controlPartOrder property alias cfg_controlPartSpacing: controlPartSpacing.value // GENERATED config (start) property alias cfg_controlPartButtonsPosition: controlPartButtonsPosition.currentIndex property alias cfg_controlPartButtonsShowOnMouseIn: controlPartButtonsShowOnMouseIn.checked property alias cfg_controlPartButtonsShowOnMouseOut: controlPartButtonsShowOnMouseOut.checked property alias cfg_controlPartButtonsHorizontalAlignment: controlPartButtonsHorizontalAlignment.currentIndex property alias cfg_controlPartIconPosition: controlPartIconPosition.currentIndex property alias cfg_controlPartIconShowOnMouseIn: controlPartIconShowOnMouseIn.checked property alias cfg_controlPartIconShowOnMouseOut: controlPartIconShowOnMouseOut.checked property alias cfg_controlPartIconHorizontalAlignment: controlPartIconHorizontalAlignment.currentIndex property alias cfg_controlPartTitlePosition: controlPartTitlePosition.currentIndex property alias cfg_controlPartTitleShowOnMouseIn: controlPartTitleShowOnMouseIn.checked property alias cfg_controlPartTitleShowOnMouseOut: controlPartTitleShowOnMouseOut.checked property alias cfg_controlPartTitleHorizontalAlignment: controlPartTitleHorizontalAlignment.currentIndex property alias cfg_controlPartMenuPosition: controlPartMenuPosition.currentIndex property alias cfg_controlPartMenuShowOnMouseIn: controlPartMenuShowOnMouseIn.checked property alias cfg_controlPartMenuShowOnMouseOut: controlPartMenuShowOnMouseOut.checked property alias cfg_controlPartMenuHorizontalAlignment: controlPartMenuHorizontalAlignment.currentIndex // GENERATED config (end) + property alias cfg_controlPartMouseAreaRestrictedToWidget: controlPartMouseAreaRestrictedToWidget.checked + ListModel { id: partsToSpend ListElement { name: 'icon' text: 'Icon' } ListElement { name: 'title' text: 'Title' } ListElement { name: 'buttons' text: 'Buttons' } ListElement { name: 'menu' text: 'Menu' } } function sortPartOrder() { buttonOrder.model.clear() cfg_controlPartOrder.split('|').forEach(function (itemId, index) { var partIndex = -1 print('adding ' + itemId); if (itemId === 'icon') { partIndex = 0 } else if (itemId === 'title') { partIndex = 1 } else if (itemId === 'buttons') { partIndex = 2 } else if (itemId === 'menu') { partIndex = 3 } if (partIndex >= 0) { buttonOrder.model.append(partsToSpend.get(partIndex)); } }); print('model count: ' + buttonOrder.model.count) } GridLayout { columns: 3 Label { text: i18n("Item order:") } OrderableListView { id: buttonOrder Layout.columnSpan: 2 itemHeight: units.gridUnit * 2 itemWidth: units.gridUnit * 4 height: itemHeight width: itemWidth * 4 model: ListModel { // will be filled initially by sortButtonOrder() method } orientation: ListView.Horizontal onModelOrderChanged: { var orderStr = ''; for (var i = 0; i < model.count; i++) { var item = model.get(i) if (orderStr.length > 0) { orderStr += '|'; } orderStr += item.name; } cfg_controlPartOrder = orderStr; print('written: ' + cfg_controlPartOrder); } } Label { text: i18n("Item spacing:") } SpinBox { id: controlPartSpacing decimals: 1 stepSize: 0.5 minimumValue: 0.5 maximumValue: 300 Layout.columnSpan: 2 } // GENERATED controls (start) GridLayout { columns: 2 Item { width: 2 height: units.largeSpacing Layout.columnSpan: 2 } Label { text: i18n('Buttons') font.bold: true Layout.columnSpan: 2 } Label { text: i18n('Position:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartButtonsPosition model: [i18n('Occupy'), i18n('Floating layer'), i18n('Absolute')] } CheckBox { id: controlPartButtonsShowOnMouseIn text: i18n('Show on mouse in') Layout.columnSpan: 2 } CheckBox { id: controlPartButtonsShowOnMouseOut text: i18n('Show on mouse out') Layout.columnSpan: 2 } Label { text: i18n('Horizontal alignment:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartButtonsHorizontalAlignment model: [i18n('Left'), i18n('Right')] } Item { width: 2 height: units.largeSpacing Layout.columnSpan: 2 } Label { text: i18n('Icon') font.bold: true Layout.columnSpan: 2 } Label { text: i18n('Position:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartIconPosition model: [i18n('Occupy'), i18n('Floating layer'), i18n('Absolute')] } CheckBox { id: controlPartIconShowOnMouseIn text: i18n('Show on mouse in') Layout.columnSpan: 2 } CheckBox { id: controlPartIconShowOnMouseOut text: i18n('Show on mouse out') Layout.columnSpan: 2 } Label { text: i18n('Horizontal alignment:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartIconHorizontalAlignment model: [i18n('Left'), i18n('Right')] } } Item { width: units.largeSpacing height: units.largeSpacing } GridLayout { columns: 2 Item { width: 2 height: units.largeSpacing Layout.columnSpan: 2 } Label { text: i18n('Title') font.bold: true Layout.columnSpan: 2 } Label { text: i18n('Position:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartTitlePosition model: [i18n('Occupy'), i18n('Floating layer'), i18n('Absolute')] } CheckBox { id: controlPartTitleShowOnMouseIn text: i18n('Show on mouse in') Layout.columnSpan: 2 } CheckBox { id: controlPartTitleShowOnMouseOut text: i18n('Show on mouse out') Layout.columnSpan: 2 } Label { text: i18n('Horizontal alignment:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartTitleHorizontalAlignment model: [i18n('Left'), i18n('Right')] } Item { width: 2 height: units.largeSpacing Layout.columnSpan: 2 } Label { text: i18n('Menu') font.bold: true Layout.columnSpan: 2 } Label { text: i18n('Position:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartMenuPosition model: [i18n('Occupy'), i18n('Floating layer'), i18n('Absolute')] } CheckBox { id: controlPartMenuShowOnMouseIn text: i18n('Show on mouse in') Layout.columnSpan: 2 } CheckBox { id: controlPartMenuShowOnMouseOut text: i18n('Show on mouse out') Layout.columnSpan: 2 } Label { text: i18n('Horizontal alignment:') Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } ComboBox { id: controlPartMenuHorizontalAlignment model: [i18n('Left'), i18n('Right')] } } // GENERATED controls (end) + Item { + width: units.largeSpacing + height: units.largeSpacing + Layout.columnSpan: 3 + } + + CheckBox { + id: controlPartMouseAreaRestrictedToWidget + text: i18n('Restrict mouse area to widget') + Layout.columnSpan: 2 + } + } Component.onCompleted: { sortPartOrder() } } diff --git a/package/contents/ui/main.qml b/package/contents/ui/main.qml index abc1c3a..64a7e23 100644 --- a/package/contents/ui/main.qml +++ b/package/contents/ui/main.qml @@ -1,696 +1,676 @@ /* * Copyright 2015 Martin Kotelnik * * 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, see . */ import QtQuick 2.2 import QtGraphicalEffects 1.0 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.taskmanager 0.1 as TaskManager import org.kde.activities 0.1 as Activities Item { id: main property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) property double horizontalScreenWidthPercent: plasmoid.configuration.horizontalScreenWidthPercent // property double buttonSize: plasmoid.configuration.buttonSize property bool autoFillWidth: plasmoid.configuration.autoFillWidth property double widthForHorizontalPanel: (Screen.width * horizontalScreenWidthPercent + plasmoid.configuration.widthFineTuning) - ((!buttonsItem.visible && buttonsStandalone && plasmoid.configuration.buttonsDynamicWidth) ? buttonsItem.width : 0) anchors.fill: parent Layout.fillWidth: plasmoid.configuration.autoFillWidth Layout.preferredWidth: autoFillWidth ? -1 : (vertical ? parent.width : (widthForHorizontalPanel > 0 ? widthForHorizontalPanel : 0.0001)) Layout.minimumWidth: Layout.preferredWidth Layout.maximumWidth: Layout.preferredWidth Layout.preferredHeight: parent === null ? 0 : vertical ? Math.min(theme.defaultFont.pointSize * 4, parent.width) : parent.height Layout.minimumHeight: Layout.preferredHeight Layout.maximumHeight: Layout.preferredHeight property int textType: plasmoid.configuration.textType property int fitText: plasmoid.configuration.fitText property int tooltipTextType: plasmoid.configuration.tooltipTextType property string tooltipText: '' property bool windowIconOnTheRight: plasmoid.configuration.windowIconOnTheRight property double iconAndTextSpacing: plasmoid.configuration.iconAndTextSpacing property bool slidingIconAndText: plasmoid.configuration.slidingIconAndText property double fontPixelSize: theme.defaultFont.pixelSize * plasmoid.configuration.fontSizeScale property bool fontBold: plasmoid.configuration.boldFontWeight property string fontFamily: plasmoid.configuration.fontFamily property bool noWindowActive: true property bool currentWindowMaximized: false property bool canShowButtonsAccordingMaximized: showButtonOnlyWhenMaximized ? currentWindowMaximized : true property int controlButtonsSpacing: plasmoid.configuration.controlButtonsSpacing property int bp: plasmoid.configuration.buttonsPosition; property bool buttonsVerticalCenter: plasmoid.configuration.buttonsVerticalCenter property bool showControlButtons: plasmoid.configuration.showControlButtons property bool showButtonOnlyWhenMaximized: plasmoid.configuration.showButtonOnlyWhenMaximized property bool showMinimize: showControlButtons && plasmoid.configuration.showMinimize property bool showMaximize: showControlButtons && plasmoid.configuration.showMaximize property bool showPinToAllDesktops: showControlButtons && plasmoid.configuration.showPinToAllDesktops property string buttonOrder: plasmoid.configuration.buttonOrder property bool doubleClickMaximizes: plasmoid.configuration.doubleClickMaximizes property int leftClickAction: plasmoid.configuration.leftClickAction property string chosenLeftClickSource: leftClickAction === 1 ? shortcutDS.presentWindows : leftClickAction === 2 ? shortcutDS.presentWindowsAll : leftClickAction === 3 ? shortcutDS.presentWindowsClass : '' property bool middleClickClose: plasmoid.configuration.middleClickAction === 1 property bool middleClickFullscreen: plasmoid.configuration.middleClickAction === 2 property bool wheelUpMaximizes: plasmoid.configuration.wheelUpMaximizes property bool wheelDownMinimizes: plasmoid.configuration.wheelDownAction === 1 property bool wheelDownUnmaximizes: plasmoid.configuration.wheelDownAction === 2 property bool buttonsStandalone: showControlButtons && plasmoid.configuration.buttonsStandalone property bool buttonsBetweenIconAndText: buttonsStandalone && plasmoid.configuration.buttonsBetweenIconAndText property bool doNotHideControlButtons: showControlButtons && plasmoid.configuration.doNotHideControlButtons property bool textColorLight: ((theme.textColor.r + theme.textColor.g + theme.textColor.b) / 3) > 0.5 property bool mouseHover: false property bool isActiveWindowPinned: false property bool isActiveWindowMaximized: false - property bool appmenuNextToIconAndText: plasmoid.configuration.appmenuNextToIconAndText - property double appmenuSideMargin: plasmoid.configuration.appmenuOuterSideMargin - property bool appmenuSwitchSidesWithIconAndText: plasmoid.configuration.appmenuSwitchSidesWithIconAndText - property bool appmenuBoldTitleWhenMenuDisplayed: plasmoid.configuration.appmenuBoldTitleWhenMenuDisplayed +// property bool appmenuNextToIconAndText: plasmoid.configuration.appmenuNextToIconAndText +// property double appmenuSideMargin: plasmoid.configuration.appmenuOuterSideMargin +// property bool appmenuSwitchSidesWithIconAndText: plasmoid.configuration.appmenuSwitchSidesWithIconAndText +// property bool appmenuBoldTitleWhenMenuDisplayed: plasmoid.configuration.appmenuBoldTitleWhenMenuDisplayed + + property bool controlPartMouseAreaRestrictedToWidget: plasmoid.configuration.controlPartMouseAreaRestrictedToWidget property var activeTaskLocal: null property int activityActionCount: 0 property bool automaticButtonThemeEnabled: plasmoid.configuration.automaticButtonThemeEnabled property string manualAuroraeThemePath: plasmoid.configuration.customAuroraeThemePath property string manualAuroraeThemePathResolved: '' property string manualAuroraeThemeExtension: 'svg' property var itemPartOrder: [] Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Plasmoid.status: { if (menuItem.appmenuOpened) { return PlasmaCore.Types.NeedsAttentionStatus; } else if (!menuItem.appmenuOpened && menuItem.appmenuEnabledAndNonEmpty){ return PlasmaCore.Types.ActiveStatus; } else { return PlasmaCore.Types.PassiveStatus; } } // // MODEL // TaskManager.TasksModel { id: tasksModel sortMode: TaskManager.TasksModel.SortVirtualDesktop groupMode: TaskManager.TasksModel.GroupDisabled screenGeometry: plasmoid.screenGeometry filterByScreen: plasmoid.configuration.showForCurrentScreenOnly onActiveTaskChanged: { updateActiveWindowInfo() } onDataChanged: { updateActiveWindowInfo() } onCountChanged: { updateActiveWindowInfo() } } TaskManager.ActivityInfo { id: activityInfo onCurrentActivityChanged: { if (noWindowActive) { updateActiveWindowInfo(); } reAddActivityActions() } onNumberOfRunningActivitiesChanged: { reAddActivityActions() } } Activities.ActivityModel { id: activityModel } function activeTask() { return activeTaskLocal } function activeTaskExists() { return activeTaskLocal.display !== undefined } onTooltipTextTypeChanged: updateTooltip() function updateTooltip() { if (tooltipTextType === 1) { tooltipText = replaceTitle(activeTask().display || '') } else if (tooltipTextType === 2) { tooltipText = activeTask().AppName || '' } else { tooltipText = '' } } function composeNoWindowText() { return plasmoid.configuration.noWindowText.replace('%activity%', activityInfo.activityName(activityInfo.currentActivity)) } function updateActiveWindowInfo() { var activeTaskIndex = tasksModel.activeTask // fallback for Plasma 5.8 var abstractTasksModel = TaskManager.AbstractTasksModel || {} var isActive = abstractTasksModel.IsActive || 271 var appName = abstractTasksModel.AppName || 258 var isMaximized = abstractTasksModel.IsMaximized || 276 var virtualDesktop = abstractTasksModel.VirtualDesktop || 286 activeTaskLocal = {} if (tasksModel.data(activeTaskIndex, isActive)) { activeTaskLocal = { display: tasksModel.data(activeTaskIndex, Qt.DisplayRole), decoration: tasksModel.data(activeTaskIndex, Qt.DecorationRole), AppName: tasksModel.data(activeTaskIndex, appName), IsMaximized: tasksModel.data(activeTaskIndex, isMaximized), VirtualDesktop: tasksModel.data(activeTaskIndex, virtualDesktop) } } var actTask = activeTask() noWindowActive = !activeTaskExists() currentWindowMaximized = !noWindowActive && actTask.IsMaximized === true isActiveWindowPinned = actTask.VirtualDesktop === -1; if (noWindowActive) { titleItem.text = composeNoWindowText() iconItem.source = plasmoid.configuration.noWindowIcon } else { titleItem.text = (textType === 1 ? actTask.AppName : null) || replaceTitle(actTask.display) iconItem.source = actTask.decoration } updateTooltip() } function toggleMaximized() { tasksModel.requestToggleMaximized(tasksModel.activeTask); } function toggleMinimized() { tasksModel.requestToggleMinimized(tasksModel.activeTask); } function toggleClose() { tasksModel.requestClose(tasksModel.activeTask); } function toggleFullscreen() { tasksModel.requestToggleFullScreen(tasksModel.activeTask); } function togglePinToAllDesktops() { tasksModel.requestVirtualDesktop(tasksModel.activeTask, 0); } function setMaximized(maximized) { if ((maximized && !activeTask().IsMaximized) || (!maximized && activeTask().IsMaximized)) { print('toggle maximized') toggleMaximized() } } function setMinimized() { if (!activeTask().IsMinimized) { toggleMinimized() } } - // - // ACTIVE WINDOW INFO - // -// Item { -// id: activeWindowListView -// -// anchors.top: parent.top -// anchors.bottom: parent.bottom -// -// property double appmenuOffsetLeft: (bp === 0 || bp === 2) ? appmenu.appmenuOffsetWidth : 0 -// property double appmenuOffsetRight: (bp === 1 || bp === 3) ? appmenu.appmenuOffsetWidth : 0 -// property double controlButtonsAreaWidth: noWindowActive ? 0 : buttonsItem.width -// property bool buttonsVisible: buttonsStandalone && (!slidingIconAndText || buttonsItem.mouseInWidget || doNotHideControlButtons) && (canShowButtonsAccordingMaximized || !slidingIconAndText) -// property double buttonsBetweenAddition: buttonsVisible && buttonsBetweenIconAndText ? controlButtonsAreaWidth + iconAndTextSpacing : 0 -// -// anchors.left: parent.left -// anchors.leftMargin: buttonsVisible && (bp === 0 || bp === 2) && !buttonsBetweenIconAndText ? controlButtonsAreaWidth + iconAndTextSpacing + appmenuOffsetLeft : 0 + appmenuOffsetLeft -// anchors.right: parent.right -// anchors.rightMargin: buttonsVisible && (bp === 1 || bp === 3) && !buttonsBetweenIconAndText ? controlButtonsAreaWidth + iconAndTextSpacing + appmenuOffsetRight : 0 + appmenuOffsetRight -// -// Behavior on anchors.leftMargin { -// NumberAnimation { -// duration: 150 -// easing.type: Easing.Linear -// } -// } -// Behavior on anchors.rightMargin { -// NumberAnimation { -// duration: 150 -// easing.type: Easing.Linear -// } -// } -// -// width: parent.width - anchors.leftMargin - anchors.rightMargin -// -// opacity: appmenu.visible && !appmenuNextToIconAndText ? plasmoid.configuration.appmenuIconAndTextOpacity : 1 -// -// Item { -// width: parent.width -// height: main.height -// -// WindowIcon { -// id: iconItem -// -// anchors.left: parent.left -// anchors.leftMargin: windowIconOnTheRight ? parent.width - iconItem.width : 0 -// } -// -// WindowTitle { -// id: titleItem -// -// property double iconMarginForAnchor: noWindowActive && plasmoid.configuration.noWindowIcon === '' ? 0 : iconMargin -// -// anchors.left: parent.left -// anchors.leftMargin: windowIconOnTheRight ? 0 : iconMarginForAnchor + iconAndTextSpacing + activeWindowListView.buttonsBetweenAddition -// anchors.top: parent.top -// anchors.bottom: parent.bottom -// } -// -// } -// } - function getConfigName(itemName) { return itemName.substring(0, 1).toUpperCase() + itemName.substring(1) } + /* + * Position can be: + * 0 ... occupy + * 1 ... floating layer (second occupy) + * 2 ... absolute + */ function getPosition(itemName) { var configName = getConfigName(itemName) print('getPosition: ' + configName) print('POS: ' + plasmoid.configuration['controlPart' + configName + 'Position']) return plasmoid.configuration['controlPart' + configName + 'Position'] } - + function isRelevant(itemName) { var configName = getConfigName(itemName) print('isRelevant: ' + configName) print('1IR: ' + plasmoid.configuration['controlPart' + configName + 'ShowOnMouseIn']) print('2IR: ' + plasmoid.configuration['controlPart' + configName + 'ShowOnMouseOut']) var result = plasmoid.configuration['controlPart' + configName + 'ShowOnMouseIn'] || plasmoid.configuration['controlPart' + configName + 'ShowOnMouseOut'] print('res: ' + result) return result } function getItem(itemName) { if (itemName === 'icon') { return iconItem } if (itemName === 'title') { return titleItem } if (itemName === 'menu') { return menuItem } if (itemName === 'buttons') { return buttonsItem } return null } function getItemWidth(itemName) { - return getItem().width + return getItem(itemName).width } - function anchorsLeftMargin(itemName) { + function getLeftMargin(itemName) { var itemPosition = getPosition(itemName) print('position of ' + itemName + ' is ' + itemPosition) if (itemPosition === 2) { return 0 } var anchorSize = 0 itemPartOrder.some(function (iName, index) { print('iterating: ' + iName) if (iName === itemName) { return true } if (getPosition(iName) === itemPosition && isRelevant(iName)) { var currentItemWidth = getItemWidth(iName) print('width of ' + iName + ' is ' + currentItemWidth) anchorSize += currentItemWidth anchorSize += plasmoid.configuration.controlPartSpacing } }); print('leftMargin of ' + itemName + ' is ' + anchorSize) return anchorSize } - function refreshItemMargin() { - iconItem.anchors.leftMargin = anchorsLeftMargin('icon') - titleItem.anchors.leftMargin = anchorsLeftMargin('title') - menuItem.anchors.leftMargin = anchorsLeftMargin('menu') - buttonsItem.anchors.leftMargin = anchorsLeftMargin('buttons') + function getWidth(itemName) { + var itemPosition = getPosition(itemName) + print('getWidth(): position of ' + itemName + ' is ' + itemPosition) + if (itemPosition === 2) { + return 0 + } + var computedWidth = main.width + itemPartOrder.forEach(function (iName, index) { + print('iterating: ' + iName) + if (iName === itemName) { + return; + } + if (getPosition(iName) === itemPosition && isRelevant(iName)) { + var currentItemWidth = getItemWidth(iName) + print('width of ' + iName + ' is ' + currentItemWidth) + computedWidth -= currentItemWidth + computedWidth -= plasmoid.configuration.controlPartSpacing + } + }); + print('computedWidth of ' + itemName + ' is ' + computedWidth) + return computedWidth + } + + function refreshItemPosition() { + titleItem.width = getWidth('title') + menuItem.width = getWidth('menu') + + iconItem.x = getLeftMargin('icon') + titleItem.x = getLeftMargin('title') + menuItem.x = getLeftMargin('menu') + buttonsItem.x = getLeftMargin('buttons') + + textSeparator.x = getLeftMargin('menu') - (plasmoid.configuration.controlPartSpacing / 2) } function replaceTitle(title) { if (!plasmoid.configuration.useWindowTitleReplace) { return title } return title.replace(new RegExp(plasmoid.configuration.replaceTextRegex), plasmoid.configuration.replaceTextReplacement); } MouseArea { anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.MiddleButton onEntered: { mouseHover = true buttonsItem.mouseInWidget = showControlButtons && !noWindowActive } onExited: { mouseHover = false buttonsItem.mouseInWidget = false } onWheel: { if (wheel.angleDelta.y > 0) { if (wheelUpMaximizes) { setMaximized(true) } } else { if (wheelDownMinimizes) { setMinimized() } else if (wheelDownUnmaximizes) { setMaximized(false) } } } onDoubleClicked: { if (doubleClickMaximizes && mouse.button == Qt.LeftButton) { toggleMaximized() } } onClicked: { if (chosenLeftClickSource !== '' && !doubleClickMaximizes && mouse.button == Qt.LeftButton) { shortcutDS.connectedSources.push(chosenLeftClickSource) buttonsItem.mouseInWidget = false return } if (mouse.button == Qt.MiddleButton) { if (middleClickFullscreen) { toggleFullscreen() } else if (middleClickClose) { toggleClose() } } } PlasmaCore.ToolTipArea { anchors.fill: parent active: tooltipTextType > 0 && tooltipText !== '' interactive: true location: plasmoid.location mainItem: Row { spacing: 0 Layout.minimumWidth: fullText.width + units.largeSpacing Layout.minimumHeight: childrenRect.height Layout.maximumWidth: Layout.minimumWidth Layout.maximumHeight: Layout.minimumHeight Item { width: units.largeSpacing / 2 height: 2 } PlasmaComponents.Label { id: fullText text: tooltipText } } } } - + + property bool mouseInWidget: mouseHover || buttonsItem.mouseInWidget || menuItem.mouseInWidget + ListModel { id: controlButtonsModel } - + WindowIcon { id: iconItem - x: anchorsLeftMargin('icon') + visible: ((mouseInWidget && plasmoid.configuration.controlPartIconShowOnMouseIn) + || (!mouseInWidget && plasmoid.configuration.controlPartIconShowOnMouseOut)) - visible: ((mouseHover && plasmoid.configuration.controlPartIconShowOnMouseIn) - || (!mouseHover && plasmoid.configuration.controlPartIconShowOnMouseOut)) - - onWidthChanged: refreshItemMargin() + onWidthChanged: refreshItemPosition() } WindowTitle { id: titleItem - x: anchorsLeftMargin('title') - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - - visible: ((mouseHover && plasmoid.configuration.controlPartTitleShowOnMouseIn) - || (!mouseHover && plasmoid.configuration.controlPartTitleShowOnMouseOut)) + visible: ((mouseInWidget && plasmoid.configuration.controlPartTitleShowOnMouseIn) + || (!mouseInWidget && plasmoid.configuration.controlPartTitleShowOnMouseOut)) } AppMenu { id: menuItem - x: anchorsLeftMargin('menu') + property bool mouseIn: controlPartMouseAreaRestrictedToWidget ? menuItem.mouseInWidget : main.mouseInWidget - visible: ((mouseHover && plasmoid.configuration.controlPartMenuShowOnMouseIn) - || (!mouseHover && plasmoid.configuration.controlPartMenuShowOnMouseOut)) + height: main.height + + showItem: ((mouseIn && plasmoid.configuration.controlPartMenuShowOnMouseIn) + || (!mouseIn && plasmoid.configuration.controlPartMenuShowOnMouseOut)) } ControlButtons { id: buttonsItem - x: anchorsLeftMargin('buttons') - - visible: true + property bool mouseIn: controlPartMouseAreaRestrictedToWidget ? buttonsItem.mouseInWidget : main.mouseInWidget controlButtonsModel: controlButtonsModel - showItem: ((mouseInWidget && plasmoid.configuration.controlPartButtonsShowOnMouseIn) - || (!mouseInWidget && plasmoid.configuration.controlPartButtonsShowOnMouseOut)) + showItem: ((mouseIn && plasmoid.configuration.controlPartButtonsShowOnMouseIn) + || (!mouseIn && plasmoid.configuration.controlPartButtonsShowOnMouseOut)) + onWidthChanged: refreshItemPosition() } - property bool mouseInWidget: mouseHover || buttonsItem.mouseInWidget || menuItem.mouseInWidget + Rectangle { + id: textSeparator + x: 0 + anchors.verticalCenter: main.verticalCenter + height: 0.8 * parent.height + width: 1 + visible: plasmoid.configuration.appmenuSeparatorEnabled && menuItem.showItem + color: theme.textColor + opacity: 0.4 + } onMouseHoverChanged: { print('mouse hover changed: ' + mouseHover); } onMouseInWidgetChanged: { print('mouseInWidget: ' + mouseInWidget); } onManualAuroraeThemePathChanged: { manualAuroraeThemeExtension = plasmoid.nativeInterface.extensionForTheme(manualAuroraeThemePath); manualAuroraeThemePathResolved = plasmoid.nativeInterface.translateThemePath(manualAuroraeThemePath); print('manualAuroraeThemePath=' + manualAuroraeThemePath) print('manualAuroraeThemePathResolved=' + manualAuroraeThemePathResolved) print('manualAuroraeThemeExtension=' + manualAuroraeThemeExtension) } function addButton(preparedArray, buttonName) { if (buttonName === 'close') { preparedArray.push({ iconName: 'close', windowOperation: 'close' }); } else if (buttonName === 'maximize' && showMaximize) { preparedArray.push({ iconName: 'maximize', windowOperation: 'toggleMaximized' }); } else if (buttonName === 'minimize' && showMinimize) { preparedArray.push({ iconName: 'minimize', windowOperation: 'toggleMinimized' }); } else if ((buttonName === 'pin' || buttonName === 'alldesktops') && showPinToAllDesktops) { preparedArray.push({ iconName: 'alldesktops', windowOperation: 'togglePinToAllDesktops' }); } } function initializeControlButtonsModel() { var preparedArray = [] buttonOrder.split('|').forEach(function (buttonName) { addButton(preparedArray, buttonName); }); controlButtonsModel.clear() preparedArray.forEach(function (item) { print('adding item to buttons: ' + item.iconName); controlButtonsModel.append(item); }); } function performActiveWindowAction(windowOperation) { if (bp === 4) { return; } if (windowOperation === 'close') { toggleClose() } else if (windowOperation === 'toggleMaximized') { toggleMaximized() } else if (windowOperation === 'toggleMinimized') { toggleMinimized() } else if (windowOperation === 'togglePinToAllDesktops') { togglePinToAllDesktops() } } function action_close() { toggleClose() } function action_maximise() { toggleMaximized() } function action_minimise() { toggleMinimized() } function action_pinToAllDesktops() { togglePinToAllDesktops() } function action_reloadTheme() { plasmoid.nativeInterface.refreshAuroraeTheme(); } function actionTriggered(actionName) { if (actionName.indexOf("switchToActivity_") == 0) { var activityIndex = actionName.replace("switchToActivity_", "") var activityId = activityInfo.runningActivities()[activityIndex] activityModel.setCurrentActivity(activityId, function() {}); } } function reAddActivityActions() { plasmoid.removeAction("separator1") for (var i = 0; i < activityActionCount; i++) { plasmoid.removeAction('switchToActivity_' + i) } plasmoid.removeAction("separator2") var runningActivities = activityInfo.runningActivities() activityActionCount = runningActivities.length if (activityActionCount === 0) { return } plasmoid.setActionSeparator("separator1") activityInfo.runningActivities().forEach(function (activityId, index) { if (activityId === activityInfo.currentActivity) { return; } var activityName = activityInfo.activityName(activityId) plasmoid.setAction('switchToActivity_' + index, i18n('Switch to activity: %1', activityName), 'preferences-activities') }) plasmoid.setActionSeparator("separator2") } property string controlPartOrder: plasmoid.configuration.controlPartOrder onControlPartOrderChanged: refreshControlPartOrder() function refreshControlPartOrder() { itemPartOrder.length = 0 plasmoid.configuration.controlPartOrder.split('|').forEach(function (itemName, index) { print('itemOrder: ' + itemName) itemPartOrder.push(itemName) print('itemZ: ' + index) getItem(itemName).z = index }); print('itemPartOrder initialized: ' + itemPartOrder) + refreshItemPosition() } Component.onCompleted: { refreshControlPartOrder() - refreshItemMargin() + refreshItemPosition() initializeControlButtonsModel() updateActiveWindowInfo() plasmoid.setAction('close', i18n('Close'), 'window-close'); plasmoid.setAction('maximise', i18n('Toggle Maximise'), 'arrow-up-double'); plasmoid.setAction('minimise', i18n('Minimise'), 'draw-arrow-down'); plasmoid.setAction('pinToAllDesktops', i18n('Toggle Pin To All Desktops'), 'window-pin'); plasmoid.setActionSeparator("separator0") plasmoid.setAction('reloadTheme', i18n('Reload Theme'), 'system-reboot'); reAddActivityActions() } onShowMaximizeChanged: initializeControlButtonsModel() onShowMinimizeChanged: initializeControlButtonsModel() onShowPinToAllDesktopsChanged: initializeControlButtonsModel() onBpChanged: initializeControlButtonsModel() onButtonOrderChanged: initializeControlButtonsModel() PlasmaCore.DataSource { id: shortcutDS engine: 'executable' property string presentWindows: 'qdbus org.kde.kglobalaccel /component/kwin invokeShortcut "Expose"' property string presentWindowsAll: 'qdbus org.kde.kglobalaccel /component/kwin invokeShortcut "ExposeAll"' property string presentWindowsClass: 'qdbus org.kde.kglobalaccel /component/kwin invokeShortcut "ExposeClass"' connectedSources: [] onNewData: { connectedSources.length = 0 } } } diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 1a4efb9..009c24e 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -1,53 +1,53 @@ -set(appmenuapplet_SRCS +set(activewindowcontrolapplet_SRCS appmenumodel.cpp - appmenuplugin.cpp + activewindowcontrolplugin.cpp ) -add_library(appmenuplugin SHARED ${appmenuapplet_SRCS}) +add_library(activewindowcontrolplugin SHARED ${activewindowcontrolapplet_SRCS}) # load dbusmenuqt find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Widgets DBus) cmake_policy(SET CMP0063 NEW) add_subdirectory(libdbusmenuqt) -target_link_libraries(appmenuplugin +target_link_libraries(activewindowcontrolplugin Qt5::Core Qt5::Widgets Qt5::Quick KF5::Plasma KF5::WindowSystem dbusmenuqt) # load X11 libraries find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE OPTIONAL PURPOSE "Required for building the X11 based workspace") if(X11_FOUND) find_package(XCB MODULE REQUIRED COMPONENTS XCB RANDR) set_package_properties(XCB PROPERTIES TYPE REQUIRED) if(NOT X11_SM_FOUND) message(FATAL_ERROR "\nThe X11 Session Management (SM) development package could not be found.\nPlease install libSM.\n") endif(NOT X11_SM_FOUND) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) endif() if(X11_FOUND AND XCB_XCB_FOUND) set(HAVE_X11 1) endif() configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) if(HAVE_X11) find_package(XCB MODULE REQUIRED COMPONENTS XCB) set_package_properties(XCB PROPERTIES TYPE REQUIRED) - target_link_libraries(appmenuplugin Qt5::X11Extras XCB::XCB) + target_link_libraries(activewindowcontrolplugin Qt5::X11Extras XCB::XCB) endif() # install plugin -install(TARGETS appmenuplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/activeWindowControl) +install(TARGETS activewindowcontrolplugin DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/activeWindowControl) install(FILES qmldir DESTINATION ${KDE_INSTALL_QMLDIR}/org/kde/private/activeWindowControl) diff --git a/plugin/appmenuplugin.cpp b/plugin/activewindowcontrolplugin.cpp similarity index 92% rename from plugin/appmenuplugin.cpp rename to plugin/activewindowcontrolplugin.cpp index 76535cb..a5fe7de 100644 --- a/plugin/appmenuplugin.cpp +++ b/plugin/activewindowcontrolplugin.cpp @@ -1,32 +1,32 @@ /****************************************************************** * Copyright 2016 Chinmoy Ranjan Pradhan * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * ******************************************************************/ -#include "appmenuplugin.h" +#include "activewindowcontrolplugin.h" #include "appmenumodel.h" #include #include -void AppmenuPlugin::registerTypes(const char *uri) +void ActiveWindowControlPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.private.activeWindowControl")); qmlRegisterType(uri, 1, 0, "AppMenuModel"); } diff --git a/plugin/appmenuplugin.h b/plugin/activewindowcontrolplugin.h similarity index 90% rename from plugin/appmenuplugin.h rename to plugin/activewindowcontrolplugin.h index 583cec8..6c587e0 100644 --- a/plugin/appmenuplugin.h +++ b/plugin/activewindowcontrolplugin.h @@ -1,37 +1,37 @@ /****************************************************************** * Copyright 2016 Chinmoy Ranjan Pradhan * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * ******************************************************************/ -#ifndef APPMENUPLUGIN_H -#define APPMENUPLUGIN_H +#ifndef ACTIVEWINDOWCONTROLPLUGIN_H +#define ACTIVEWINDOWCONTROLPLUGIN_H #include -class AppmenuPlugin : public QQmlExtensionPlugin +class ActiveWindowControlPlugin : public QQmlExtensionPlugin { Q_OBJECT Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") public: void registerTypes(const char *uri) override; }; #endif diff --git a/plugin/appmenumodel.cpp b/plugin/appmenumodel.cpp index 3b41ec6..1b5dc7b 100644 --- a/plugin/appmenumodel.cpp +++ b/plugin/appmenumodel.cpp @@ -1,325 +1,343 @@ /****************************************************************** * Copyright 2016 Kai Uwe Broulik * Copyright 2016 Chinmoy Ranjan Pradhan * * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * ******************************************************************/ #include "appmenumodel.h" #include #if HAVE_X11 #include #include #endif #include -#include #include -#include #include #include #include +#include #include static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); #if HAVE_X11 static QHash s_atoms; #endif class KDBusMenuImporter : public DBusMenuImporter { public: KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) : DBusMenuImporter(service, path, parent) { } protected: QIcon iconForName(const QString &name) override { return QIcon::fromTheme(name); } }; AppMenuModel::AppMenuModel(QObject *parent) : QAbstractListModel(parent), m_serviceWatcher(new QDBusServiceWatcher(this)) { connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &AppMenuModel::onActiveWindowChanged); - connect(this, &AppMenuModel::modelNeedsUpdate, this, &AppMenuModel::update, Qt::UniqueConnection); + connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { + if (!m_updatePending) { + m_updatePending = true; + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + } + }); onActiveWindowChanged(KWindowSystem::activeWindow()); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); //if our current DBus connection gets lost, close the menu //we'll select the new menu when the focus changes connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString &serviceName) { if (serviceName == m_serviceName) { setMenuAvailable(false); emit modelNeedsUpdate(); } }); } AppMenuModel::~AppMenuModel() = default; bool AppMenuModel::menuAvailable() const { return m_menuAvailable; } void AppMenuModel::setMenuAvailable(bool set) { if (m_menuAvailable != set) { m_menuAvailable = set; emit menuAvailableChanged(); } } int AppMenuModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return m_activeMenu.count(); + if (!m_menuAvailable || !m_menu) { + return 0; + } + + return m_menu->actions().count(); } void AppMenuModel::update() { beginResetModel(); - if (!m_activeMenu.isEmpty() && !m_activeActions.isEmpty()) { - m_activeMenu.clear(); - m_activeActions.clear(); - } - - if (m_menu && m_menuAvailable) { - const auto &actions = m_menu->actions(); - for (QAction *action : actions) { - m_activeActions.append(action); - m_activeMenu.append(action->text()); - } - } - endResetModel(); + m_updatePending = false; } void AppMenuModel::onActiveWindowChanged(WId id) { qApp->removeNativeEventFilter(this); if (!id) { setMenuAvailable(false); emit modelNeedsUpdate(); return; } #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { auto *c = QX11Info::connection(); auto getWindowPropertyString = [c, this](WId id, const QByteArray &name) -> QByteArray { QByteArray value; if (!s_atoms.contains(name)) { const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); - QScopedPointer atomReply(xcb_intern_atom_reply(c, atomCookie, Q_NULLPTR)); + QScopedPointer atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr)); if (atomReply.isNull()) { return value; } s_atoms[name] = atomReply->atom; if (s_atoms[name] == XCB_ATOM_NONE) { return value; } } static const long MAX_PROP_SIZE = 10000; auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); - QScopedPointer propertyReply(xcb_get_property_reply(c, propertyCookie, NULL)); + QScopedPointer propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr)); if (propertyReply.isNull()) { return value; } if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) { const char *data = (const char *) xcb_get_property_value(propertyReply.data()); int len = propertyReply->value_len; if (data) { value = QByteArray(data, data[len - 1] ? len : len - 1); } } return value; }; auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { updateApplicationMenu(serviceName, menuObjectPath); return true; } return false; }; KWindowInfo info(id, NET::WMState | NET::WMWindowType, NET::WM2TransientFor); if (info.hasState(NET::SkipTaskbar) || info.windowType(NET::UtilityMask) == NET::Utility || info.windowType(NET::DesktopMask) == NET::Desktop) { return; } WId transientId = info.transientFor(); // lok at transient windows first while (transientId) { if (updateMenuFromWindowIfHasMenu(transientId)) { return; } - transientId = KWindowInfo(transientId, 0, NET::WM2TransientFor).transientFor(); + transientId = KWindowInfo(transientId, nullptr, NET::WM2TransientFor).transientFor(); } if (updateMenuFromWindowIfHasMenu(id)) { return; } // monitor whether an app menu becomes available later // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) qApp->installNativeEventFilter(this); m_currentWindowId = id; //no menu found, set it to unavailable setMenuAvailable(false); emit modelNeedsUpdate(); } #endif } QHash AppMenuModel::roleNames() const { QHash roleNames; - roleNames[MenuRole] = "activeMenu"; - roleNames[ActionRole] = "activeActions"; + roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); + roleNames[ActionRole] = QByteArrayLiteral("activeActions"); return roleNames; } QVariant AppMenuModel::data(const QModelIndex &index, int role) const { - int row = index.row(); - if (row < 0 ) { + const int row = index.row(); + if (row < 0 || !m_menuAvailable || !m_menu) { return QVariant(); } - if (role == MenuRole) { - return m_activeMenu.at(row); - } else if(role == ActionRole) { - const QVariant data = qVariantFromValue((void *) m_activeActions.at(row)); - return data; + const auto actions = m_menu->actions(); + if (row >= actions.count()) { + return QVariant(); + } + + if (role == MenuRole) { // TODO this should be Qt::DisplayRole + return actions.at(row)->text(); + } else if (role == ActionRole) { + return qVariantFromValue((void *) actions.at(row)); } return QVariant(); } void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) { if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { if (m_importer) { QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); } return; } m_serviceName = serviceName; m_serviceWatcher->setWatchedServices(QStringList({m_serviceName})); m_menuObjectPath = menuObjectPath; if (m_importer) { m_importer->deleteLater(); } m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { m_menu = m_importer->menu(); if (m_menu.isNull() || menu != m_menu) { return; } //cache first layer of sub menus, which we'll be popping up for(QAction *a: m_menu->actions()) { + // signal dataChanged when the action changes + connect(a, &QAction::changed, this, [this, a] { + if (m_menuAvailable && m_menu) { + const int actionIdx = m_menu->actions().indexOf(a); + if (actionIdx > -1) { + const QModelIndex modelIdx = index(actionIdx, 0); + emit dataChanged(modelIdx, modelIdx); + } + } + }); + + connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); + if (a->menu()) { m_importer->updateMenu(a->menu()); } } setMenuAvailable(true); emit modelNeedsUpdate(); }); connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { // TODO submenus - auto it = std::find(m_activeActions.constBegin(), m_activeActions.constEnd(), action); - if (it != m_activeActions.constEnd()) { - requestActivateIndex(it - m_activeActions.constBegin()); + if (!m_menuAvailable || !m_menu) { + return; + } + + const auto actions = m_menu->actions(); + auto it = std::find(actions.begin(), actions.end(), action); + if (it != actions.end()) { + requestActivateIndex(it - actions.begin()); } }); } bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { Q_UNUSED(result); if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") { return false; } #if HAVE_X11 auto e = static_cast(message); const uint8_t type = e->response_type & ~0x80; if (type == XCB_PROPERTY_NOTIFY) { auto *event = reinterpret_cast(e); if (event->window == m_currentWindowId) { auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen if (event->atom == serviceNameAtom || event->atom == objectPathAtom) { // see if we now have a menu onActiveWindowChanged(KWindowSystem::activeWindow()); } } } } #else Q_UNUSED(message); #endif return false; } diff --git a/plugin/appmenumodel.h b/plugin/appmenumodel.h index 8d7ef92..7bd35fc 100644 --- a/plugin/appmenumodel.h +++ b/plugin/appmenumodel.h @@ -1,87 +1,86 @@ /****************************************************************** * Copyright 2016 Chinmoy Ranjan Pradhan * * 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) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * ******************************************************************/ #include #include #include #include #include class QMenu; class QAction; class QModelIndex; class QDBusServiceWatcher; class KDBusMenuImporter; class AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter { Q_OBJECT Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged) public: - explicit AppMenuModel(QObject *parent = 0); - ~AppMenuModel(); + explicit AppMenuModel(QObject *parent = nullptr); + ~AppMenuModel() override; enum AppMenuRole { - MenuRole = Qt::UserRole+1, + MenuRole = Qt::UserRole+1, // TODO this should be Qt::DisplayRole ActionRole }; - QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; - QHash roleNames() const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QHash roleNames() const override; void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath); bool menuAvailable() const; void setMenuAvailable(bool set); signals: void requestActivateIndex(int index); protected: bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override; private Q_SLOTS: void onActiveWindowChanged(WId id); void update(); signals: void menuAvailableChanged(); void modelNeedsUpdate(); private: bool m_menuAvailable; + bool m_updatePending = false; WId m_currentWindowId = 0; QPointer m_menu; - QStringList m_activeMenu; - QList m_activeActions; QDBusServiceWatcher *m_serviceWatcher; QString m_serviceName; QString m_menuObjectPath; QPointer m_importer; }; diff --git a/plugin/libdbusmenuqt/com.canonical.dbusmenu.xml b/plugin/libdbusmenuqt/com.canonical.dbusmenu.xml index 9f69400..c90776a 100644 --- a/plugin/libdbusmenuqt/com.canonical.dbusmenu.xml +++ b/plugin/libdbusmenuqt/com.canonical.dbusmenu.xml @@ -1,49 +1,49 @@ - - + + diff --git a/plugin/libdbusmenuqt/dbusmenuimporter.cpp b/plugin/libdbusmenuqt/dbusmenuimporter.cpp index 707764f..702840f 100644 --- a/plugin/libdbusmenuqt/dbusmenuimporter.cpp +++ b/plugin/libdbusmenuqt/dbusmenuimporter.cpp @@ -1,546 +1,544 @@ /* This file is part of the dbusmenu-qt library Copyright 2009 Canonical Author: Aurelien Gateau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dbusmenuimporter.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "dbusmenutypes_p.h" #include "dbusmenushortcut_p.h" #include "utils_p.h" // Generated #include "dbusmenu_interface.h" //#define BENCHMARK #ifdef BENCHMARK #include static QTime sChrono; #endif #define DMRETURN_IF_FAIL(cond) if (!(cond)) { \ qWarning() << "Condition failed: " #cond; \ return; \ } static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id"; static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name"; static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash"; static QAction *createKdeTitle(QAction *action, QWidget *parent) { - QToolButton *titleWidget = new QToolButton(0); + QToolButton *titleWidget = new QToolButton(nullptr); QFont font = titleWidget->font(); font.setBold(true); titleWidget->setFont(font); titleWidget->setIcon(action->icon()); titleWidget->setText(action->text()); titleWidget->setDown(true); titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); QWidgetAction *titleAction = new QWidgetAction(parent); titleAction->setDefaultWidget(titleWidget); return titleAction; } class DBusMenuImporterPrivate { public: DBusMenuImporter *q; DBusMenuInterface *m_interface; QMenu *m_menu; using ActionForId = QMap; ActionForId m_actionForId; QTimer *m_pendingLayoutUpdateTimer; QSet m_idsRefreshedByAboutToShow; QSet m_pendingLayoutUpdates; QDBusPendingCallWatcher *refresh(int id) { auto call = m_interface->GetLayout(id, 1, QStringList()); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q); watcher->setProperty(DBUSMENU_PROPERTY_ID, id); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, &DBusMenuImporter::slotGetLayoutFinished); return watcher; } QMenu *createMenu(QWidget *parent) { QMenu *menu = q->createMenu(parent); return menu; } /** * Init all the immutable action properties here * TODO: Document immutable properties? * * Note: we remove properties we handle from the map (using QMap::take() * instead of QMap::value()) to avoid warnings about these properties in * updateAction() */ QAction *createAction(int id, const QVariantMap &_map, QWidget *parent) { QVariantMap map = _map; QAction *action = new QAction(parent); action->setProperty(DBUSMENU_PROPERTY_ID, id); QString type = map.take(QStringLiteral("type")).toString(); if (type == QLatin1String("separator")) { action->setSeparator(true); } if (map.take(QStringLiteral("children-display")).toString() == QLatin1String("submenu")) { QMenu *menu = createMenu(parent); action->setMenu(menu); } QString toggleType = map.take(QStringLiteral("toggle-type")).toString(); if (!toggleType.isEmpty()) { action->setCheckable(true); if (toggleType == QLatin1String("radio")) { QActionGroup *group = new QActionGroup(action); group->addAction(action); } } bool isKdeTitle = map.take(QStringLiteral("x-kde-title")).toBool(); updateAction(action, map, map.keys()); if (isKdeTitle) { action = createKdeTitle(action, parent); } return action; } /** * Update mutable properties of an action. A property may be listed in * requestedProperties but not in map, this means we should use the default value * for this property. * * @param action the action to update * @param map holds the property values * @param requestedProperties which properties has been requested */ void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties) { Q_FOREACH(const QString &key, requestedProperties) { updateActionProperty(action, key, map.value(key)); } } void updateActionProperty(QAction *action, const QString &key, const QVariant &value) { if (key == QLatin1String("label")) { updateActionLabel(action, value); } else if (key == QLatin1String("enabled")) { updateActionEnabled(action, value); } else if (key == QLatin1String("toggle-state")) { updateActionChecked(action, value); } else if (key == QLatin1String("icon-name")) { updateActionIconByName(action, value); } else if (key == QLatin1String("icon-data")) { updateActionIconByData(action, value); } else if (key == QLatin1String("visible")) { updateActionVisible(action, value); } else if (key == QLatin1String("shortcut")) { updateActionShortcut(action, value); } else { qWarning() << "Unhandled property update" << key; } } void updateActionLabel(QAction *action, const QVariant &value) { QString text = swapMnemonicChar(value.toString(), '_', '&'); action->setText(text); } void updateActionEnabled(QAction *action, const QVariant &value) { action->setEnabled(value.isValid() ? value.toBool(): true); } void updateActionChecked(QAction *action, const QVariant &value) { if (action->isCheckable() && value.isValid()) { action->setChecked(value.toInt() == 1); } } void updateActionIconByName(QAction *action, const QVariant &value) { const QString iconName = value.toString(); const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString(); if (previous == iconName) { return; } action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName); if (iconName.isEmpty()) { action->setIcon(QIcon()); return; } action->setIcon(q->iconForName(iconName)); } void updateActionIconByData(QAction *action, const QVariant &value) { const QByteArray data = value.toByteArray(); uint dataHash = qHash(data); uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt(); if (previousDataHash == dataHash) { return; } action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash); QPixmap pix; if (!pix.loadFromData(data)) { qWarning() << "Failed to decode icon-data property for action" << action->text(); action->setIcon(QIcon()); return; } action->setIcon(QIcon(pix)); } void updateActionVisible(QAction *action, const QVariant &value) { action->setVisible(value.isValid() ? value.toBool() : true); } void updateActionShortcut(QAction *action, const QVariant &value) { QDBusArgument arg = value.value(); DBusMenuShortcut dmShortcut; arg >> dmShortcut; QKeySequence keySequence = dmShortcut.toKeySequence(); action->setShortcut(keySequence); } QMenu *menuForId(int id) const { if (id == 0) { return q->menu(); } QAction *action = m_actionForId.value(id); if (!action) { - return 0; + return nullptr; } return action->menu(); } void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); void sendEvent(int id, const QString &eventId) { m_interface->Event(id, eventId, QDBusVariant(QString()), 0u); } }; DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent) : QObject(parent) , d(new DBusMenuImporterPrivate) { DBusMenuTypes_register(); d->q = this; d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this); - d->m_menu = 0; + d->m_menu = nullptr; d->m_pendingLayoutUpdateTimer = new QTimer(this); d->m_pendingLayoutUpdateTimer->setSingleShot(true); connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates); connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated); connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested); connect(d->m_interface, &DBusMenuInterface::ItemsPropertiesUpdated, this, [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { d->slotItemsPropertiesUpdated(updatedList, removedList); }); d->refresh(0); } DBusMenuImporter::~DBusMenuImporter() { // Do not use "delete d->m_menu": even if we are being deleted we should // leave enough time for the menu to finish what it was doing, for example // if it was being displayed. d->m_menu->deleteLater(); delete d; } void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId) { Q_UNUSED(revision) if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { return; } d->m_pendingLayoutUpdates << parentId; if (!d->m_pendingLayoutUpdateTimer->isActive()) { d->m_pendingLayoutUpdateTimer->start(); } } void DBusMenuImporter::processPendingLayoutUpdates() { QSet ids = d->m_pendingLayoutUpdates; d->m_pendingLayoutUpdates.clear(); Q_FOREACH(int id, ids) { d->refresh(id); } } QMenu *DBusMenuImporter::menu() const { if (!d->m_menu) { - d->m_menu = d->createMenu(0); + d->m_menu = d->createMenu(nullptr); } return d->m_menu; } void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { Q_FOREACH(const DBusMenuItem &item, updatedList) { QAction *action = m_actionForId.value(item.id); if (!action) { // We don't know this action. It probably is in a menu we haven't fetched yet. continue; } QVariantMap::ConstIterator it = item.properties.constBegin(), end = item.properties.constEnd(); for(; it != end; ++it) { updateActionProperty(action, it.key(), it.value()); } } Q_FOREACH(const DBusMenuItemKeys &item, removedList) { QAction *action = m_actionForId.value(item.id); if (!action) { // We don't know this action. It probably is in a menu we haven't fetched yet. continue; } Q_FOREACH(const QString &key, item.properties) { updateActionProperty(action, key, QVariant()); } } } QAction *DBusMenuImporter::actionForId(int id) const { return d->m_actionForId.value(id); } void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/) { QAction *action = d->m_actionForId.value(id); DMRETURN_IF_FAIL(action); actionActivationRequested(action); } void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher) { int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); watcher->deleteLater(); QMenu *menu = d->menuForId(parentId); QDBusPendingReply reply = *watcher; if (!reply.isValid()) { qWarning() << reply.error().message(); if (menu) { emit menuUpdated(menu); } return; } #ifdef BENCHMARK DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; #endif DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); if (!menu) { qWarning() << "No menu for id" << parentId; return; } //remove outdated actions QSet newDBusMenuItemIds; newDBusMenuItemIds.reserve(rootItem.children.count()); for (const DBusMenuLayoutItem &item: rootItem.children) { newDBusMenuItemIds << item.id; } for (QAction *action: menu->actions()) { int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); if (! newDBusMenuItemIds.contains(id)) { + menu->removeAction(action); action->deleteLater(); d->m_actionForId.remove(id); } } //insert or update new actions into our menu for (const DBusMenuLayoutItem &dbusMenuItem: rootItem.children) { DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id); QAction *action = nullptr; if (it == d->m_actionForId.end()) { int id = dbusMenuItem.id; action = d->createAction(id, dbusMenuItem.properties, menu); d->m_actionForId.insert(id, action); connect(action, &QObject::destroyed, this, [this, id]() { d->m_actionForId.remove(id); }); connect(action, &QAction::triggered, this, [action, id, this]() { sendClickedEvent(id); }); if (QMenu *menuAction = action->menu()) { connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection); } connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection); menu->addAction(action); } else { action = *it; QStringList filteredKeys = dbusMenuItem.properties.keys(); filteredKeys.removeOne("type"); filteredKeys.removeOne("toggle-type"); filteredKeys.removeOne("children-display"); d->updateAction(*it, dbusMenuItem.properties, filteredKeys); // Move the action to the tail so we can keep the order same as the dbus request. menu->removeAction(action); menu->addAction(action); } } emit menuUpdated(menu); } void DBusMenuImporter::sendClickedEvent(int id) { d->sendEvent(id, QStringLiteral("clicked")); } void DBusMenuImporter::updateMenu() { updateMenu(DBusMenuImporter::menu()); } void DBusMenuImporter::updateMenu(QMenu * menu) { Q_ASSERT(menu); QAction *action = menu->menuAction(); Q_ASSERT(action); int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); auto call = d->m_interface->AboutToShow(id); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); watcher->setProperty(DBUSMENU_PROPERTY_ID, id); connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusMenuImporter::slotAboutToShowDBusCallFinished); + + // Firefox deliberately ignores "aboutToShow" whereas Qt ignores" opened", so we'll just send both all the time... + d->sendEvent(id, QStringLiteral("opened")); } void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) { int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); watcher->deleteLater(); QMenu *menu = d->menuForId(id); if (!menu) { return; } QDBusPendingReply reply = *watcher; if (reply.isError()) { qWarning() << "Call to AboutToShow() failed:" << reply.error().message(); menuUpdated(menu); return; } //Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before //this returns, which equates to the same thing bool needRefresh = reply.argumentAt<0>(); if (needRefresh || menu->actions().isEmpty()) { d->m_idsRefreshedByAboutToShow << id; d->refresh(id); } else if (menu) { menuUpdated(menu); } } void DBusMenuImporter::slotMenuAboutToHide() { QMenu *menu = qobject_cast(sender()); Q_ASSERT(menu); QAction *action = menu->menuAction(); Q_ASSERT(action); int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); d->sendEvent(id, QStringLiteral("closed")); } void DBusMenuImporter::slotMenuAboutToShow() { QMenu *menu = qobject_cast(sender()); Q_ASSERT(menu); updateMenu(menu); - - QAction *action = menu->menuAction(); - Q_ASSERT(action); - - int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); - d->sendEvent(id, QStringLiteral("opened")); } QMenu *DBusMenuImporter::createMenu(QWidget *parent) { return new QMenu(parent); } QIcon DBusMenuImporter::iconForName(const QString &/*name*/) { return QIcon(); } #include "moc_dbusmenuimporter.cpp" diff --git a/plugin/libdbusmenuqt/dbusmenuimporter.h b/plugin/libdbusmenuqt/dbusmenuimporter.h index 6f91ee9..6f40501 100644 --- a/plugin/libdbusmenuqt/dbusmenuimporter.h +++ b/plugin/libdbusmenuqt/dbusmenuimporter.h @@ -1,113 +1,113 @@ /* This file is part of the dbusmenu-qt library Copyright 2009 Canonical Author: Aurelien Gateau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DBUSMENUIMPORTER_H #define DBUSMENUIMPORTER_H // Qt -#include +#include class QAction; class QDBusPendingCallWatcher; class QDBusVariant; class QIcon; class QMenu; class DBusMenuImporterPrivate; /** * A DBusMenuImporter instance can recreate a menu serialized over DBus by * DBusMenuExporter */ class DBusMenuImporter : public QObject { Q_OBJECT public: /** * Creates a DBusMenuImporter listening over DBus on service, path */ - DBusMenuImporter(const QString &service, const QString &path, QObject *parent = 0); + DBusMenuImporter(const QString &service, const QString &path, QObject *parent = nullptr); ~DBusMenuImporter() override; QAction *actionForId(int id) const; /** * The menu created from listening to the DBusMenuExporter over DBus */ QMenu *menu() const; public Q_SLOTS: /** * Load the menu * * Will emit menuUpdated() when complete. * This should be done before showing a menu */ void updateMenu(); void updateMenu(QMenu *menu); Q_SIGNALS: /** * Emitted after a call to updateMenu(). * @see updateMenu() */ void menuUpdated(QMenu *); /** * Emitted when the exporter was asked to activate an action */ void actionActivationRequested(QAction *); protected: /** * Must create a menu, may be customized to fit host appearance. * Default implementation creates a simple QMenu. */ virtual QMenu *createMenu(QWidget *parent); /** * Must convert a name into an icon. * Default implementation returns a null icon. */ virtual QIcon iconForName(const QString &); private Q_SLOTS: void sendClickedEvent(int); void slotMenuAboutToShow(); void slotMenuAboutToHide(); void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *); void slotItemActivationRequested(int id, uint timestamp); void processPendingLayoutUpdates(); void slotLayoutUpdated(uint revision, int parentId); void slotGetLayoutFinished(QDBusPendingCallWatcher *); private: Q_DISABLE_COPY(DBusMenuImporter) DBusMenuImporterPrivate *const d; friend class DBusMenuImporterPrivate; // Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)) }; #endif /* DBUSMENUIMPORTER_H */ diff --git a/plugin/libdbusmenuqt/dbusmenushortcut_p.cpp b/plugin/libdbusmenuqt/dbusmenushortcut_p.cpp index 81dc882..a053472 100644 --- a/plugin/libdbusmenuqt/dbusmenushortcut_p.cpp +++ b/plugin/libdbusmenuqt/dbusmenushortcut_p.cpp @@ -1,82 +1,82 @@ /* This file is part of the dbusmenu-qt library Copyright 2009 Canonical Author: Aurelien Gateau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "dbusmenushortcut_p.h" // Qt -#include +#include static const int QT_COLUMN = 0; static const int DM_COLUMN = 1; static void processKeyTokens(QStringList* tokens, int srcCol, int dstCol) { struct Row { const char* zero; const char* one; const char* operator[](int col) const { return col == 0 ? zero : one; } }; static const Row table[] = { {"Meta", "Super"}, {"Ctrl", "Control"}, // Special cases for compatibility with libdbusmenu-glib which uses // "plus" for "+" and "minus" for "-". // cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565 {"+", "plus"}, {"-", "minus"}, - {0, 0} + {nullptr, nullptr} }; const Row* ptr = table; - for (; ptr->zero != 0; ++ptr) { + for (; ptr->zero != nullptr; ++ptr) { const char* from = (*ptr)[srcCol]; const char* to = (*ptr)[dstCol]; tokens->replaceInStrings(from, to); } } DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence& sequence) { QString string = sequence.toString(); DBusMenuShortcut shortcut; QStringList tokens = string.split(QStringLiteral(", ")); Q_FOREACH(QString token, tokens) { // Hack: Qt::CTRL | Qt::Key_Plus is turned into the string "Ctrl++", // but we don't want the call to token.split() to consider the // second '+' as a separator so we replace it with its final value. token.replace(QLatin1String("++"), QLatin1String("+plus")); QStringList keyTokens = token.split('+'); processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN); shortcut << keyTokens; } return shortcut; } QKeySequence DBusMenuShortcut::toKeySequence() const { QStringList tmp; Q_FOREACH(const QStringList& keyTokens_, *this) { QStringList keyTokens = keyTokens_; processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN); tmp << keyTokens.join(QLatin1String("+")); } QString string = tmp.join(QLatin1String(", ")); return QKeySequence::fromString(string); } diff --git a/plugin/libdbusmenuqt/dbusmenushortcut_p.h b/plugin/libdbusmenuqt/dbusmenushortcut_p.h index 480bacf..126b836 100644 --- a/plugin/libdbusmenuqt/dbusmenushortcut_p.h +++ b/plugin/libdbusmenuqt/dbusmenushortcut_p.h @@ -1,39 +1,39 @@ /* This file is part of the dbusmenu-qt library Copyright 2009 Canonical Author: Aurelien Gateau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DBUSMENUSHORTCUT_H #define DBUSMENUSHORTCUT_H // Qt -#include -#include +#include +#include class QKeySequence; class DBusMenuShortcut : public QList { public: QKeySequence toKeySequence() const; static DBusMenuShortcut fromKeySequence(const QKeySequence&); }; Q_DECLARE_METATYPE(DBusMenuShortcut) #endif /* DBUSMENUSHORTCUT_H */ diff --git a/plugin/libdbusmenuqt/dbusmenutypes_p.h b/plugin/libdbusmenuqt/dbusmenutypes_p.h index dd2193f..e454243 100644 --- a/plugin/libdbusmenuqt/dbusmenutypes_p.h +++ b/plugin/libdbusmenuqt/dbusmenutypes_p.h @@ -1,100 +1,100 @@ /* This file is part of the dbusmenu-qt library Copyright 2009 Canonical Author: Aurelien Gateau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef DBUSMENUTYPES_P_H #define DBUSMENUTYPES_P_H // Qt -#include -#include -#include +#include +#include +#include class QDBusArgument; //// DBusMenuItem /** * Internal struct used to communicate on DBus */ struct DBusMenuItem { int id; QVariantMap properties; }; Q_DECLARE_METATYPE(DBusMenuItem) QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item); const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item); typedef QList DBusMenuItemList; Q_DECLARE_METATYPE(DBusMenuItemList) //// DBusMenuItemKeys /** * Represents a list of keys for a menu item */ struct DBusMenuItemKeys { int id; QStringList properties; }; Q_DECLARE_METATYPE(DBusMenuItemKeys) QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &); const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &); typedef QList DBusMenuItemKeysList; Q_DECLARE_METATYPE(DBusMenuItemKeysList) //// DBusMenuLayoutItem /** * Represents an item with its children. GetLayout() returns a * DBusMenuLayoutItemList. */ struct DBusMenuLayoutItem; struct DBusMenuLayoutItem { int id; QVariantMap properties; QList children; }; Q_DECLARE_METATYPE(DBusMenuLayoutItem) QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &); const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &); typedef QList DBusMenuLayoutItemList; Q_DECLARE_METATYPE(DBusMenuLayoutItemList) //// DBusMenuShortcut class DBusMenuShortcut; QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &); const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &); void DBusMenuTypes_register(); #endif /* DBUSMENUTYPES_P_H */ diff --git a/plugin/qmldir b/plugin/qmldir index e85fbc8..e09e45e 100644 --- a/plugin/qmldir +++ b/plugin/qmldir @@ -1,3 +1,3 @@ module org.kde.private.activeWindowControl -plugin appmenuplugin +plugin activewindowcontrolplugin