diff --git a/applets/pager/package/contents/ui/main.qml b/applets/pager/package/contents/ui/main.qml index f8082d5cd..ddf55609c 100644 --- a/applets/pager/package/contents/ui/main.qml +++ b/applets/pager/package/contents/ui/main.qml @@ -1,546 +1,546 @@ /* * Copyright 2012 Luís Gabriel Lima * Copyright 2016 Kai Uwe Broulik * Copyright 2016 Eike Hein * * 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.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 import org.kde.kquickcontrolsaddons 2.0 as KQuickControlsAddonsComponents import org.kde.draganddrop 2.0 import org.kde.plasma.private.pager 2.0 MouseArea { id: root property bool isActivityPager: (plasmoid.pluginName == "org.kde.plasma.activitypager") property bool vertical: (plasmoid.formFactor == PlasmaCore.Types.Vertical) property var activityDataSource: null readonly property real aspectRatio: (((pagerModel.pagerItemSize.width * pagerItemGrid.effectiveColumns) + ((pagerItemGrid.effectiveColumns * pagerItemGrid.spacing) - pagerItemGrid.spacing)) / ((pagerModel.pagerItemSize.height * pagerItemGrid.effectiveRows) + ((pagerItemGrid.effectiveRows * pagerItemGrid.spacing) - pagerItemGrid.spacing))) Layout.minimumWidth: !root.vertical ? Math.floor(height * aspectRatio) : 1 Layout.minimumHeight: root.vertical ? Math.floor(width / aspectRatio) : 1 Layout.maximumWidth: !root.vertical ? Math.floor(height * aspectRatio) : Infinity Layout.maximumHeight: root.vertical ? Math.floor(width / aspectRatio) : Infinity Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Plasmoid.status: pagerModel.shouldShowPager ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.HiddenStatus Layout.fillWidth: root.vertical Layout.fillHeight: !root.vertical property bool dragging: false - property int dragId + property string dragId - property int dragSwitchDesktopId: -1 + property string dragSwitchDesktopId property int wheelDelta: 0 anchors.fill: parent acceptedButtons: Qt.NoButton hoverEnabled: true function colorWithAlpha(color, alpha) { return Qt.rgba(color.r, color.g, color.b, alpha) } readonly property color windowActiveOnActiveDesktopColor: colorWithAlpha(theme.textColor, 0.6) readonly property color windowInactiveOnActiveDesktopColor: colorWithAlpha(theme.textColor, 0.35) readonly property color windowActiveColor: colorWithAlpha(theme.textColor, 0.5) readonly property color windowActiveBorderColor: theme.textColor readonly property color windowInactiveColor: colorWithAlpha(theme.textColor, 0.17) readonly property color windowInactiveBorderColor: colorWithAlpha(theme.textColor, 0.5) function action_addDesktop() { pagerModel.addDesktop(); } function action_removeDesktop() { pagerModel.removeDesktop(); } function action_openKCM() { KQuickControlsAddonsComponents.KCMShell.open("desktop"); } function action_showActivityManager() { if (!activityDataSource) { activityDataSource = Qt.createQmlObject('import org.kde.plasma.core 2.0 as PlasmaCore; \ PlasmaCore.DataSource { id: dataSource; engine: "org.kde.activities"; \ connectedSources: ["Status"] }', root); } var service = activityDataSource.serviceForSource("Status") var operation = service.operationDescription("toggleActivityManager") service.startOperationCall(operation) } onContainsMouseChanged: { if (!containsMouse && dragging) { // Somewhat heavy-handed way to clean up after a window delegate drag // exits the window. pagerModel.refresh(); dragging = false; } } onWheel: { // Magic number 120 for common "one click, see: // http://qt-project.org/doc/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop wheelDelta += wheel.angleDelta.y || wheel.angleDelta.x; var increment = 0; while (wheelDelta >= 120) { wheelDelta -= 120; increment++; } while (wheelDelta <= -120) { wheelDelta += 120; increment--; } while (increment != 0) { if (increment < 0) { var nextPage = plasmoid.configuration.wrapPage? (pagerModel.currentPage + 1) % repeater.count : Math.min(pagerModel.currentPage + 1, repeater.count - 1); pagerModel.changePage(nextPage); } else { var previousPage = plasmoid.configuration.wrapPage ? (repeater.count + pagerModel.currentPage - 1) % repeater.count : Math.max(pagerModel.currentPage - 1, 0); pagerModel.changePage(previousPage); } increment += (increment < 0) ? 1 : -1; } } PagerModel { id: pagerModel enabled: root.visible showDesktop: (plasmoid.configuration.currentDesktopSelected == 1) showOnlyCurrentScreen: plasmoid.configuration.showOnlyCurrentScreen screenGeometry: plasmoid.screenGeometry pagerType: isActivityPager ? PagerModel.Activities : PagerModel.VirtualDesktops } Connections { target: plasmoid.configuration onShowWindowIconsChanged: { // Causes the model to reset; Component.onCompleted in the // window delegate now gets a chance to create the icon item, // which it otherwise will not do. pagerModel.refresh(); } onDisplayedTextChanged: { // Causes the model to reset; Component.onCompleted in the // desktop delegate now gets a chance to create the label item, // which it otherwise will not do. pagerModel.refresh(); } } Component { id: desktopLabelComponent PlasmaComponents.Label { anchors { fill: parent topMargin: desktopFrame.margins.top bottomMargin: desktopFrame.margins.bottom leftMargin: desktopFrame.margins.left rightMargin: desktopFrame.margins.right } property int index: 0 property var model: null property Item desktopFrame: null text: plasmoid.configuration.displayedText ? model.display : index + 1 wrapMode: Text.NoWrap elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter z: 0 // Below windows and FrameSvg } } Component { id: windowIconComponent PlasmaCore.IconItem { anchors.centerIn: parent height: Math.min(units.iconSizes.small, parent.height - (units.smallSpacing * 2)) width: Math.min(units.iconSizes.small, parent.width - (units.smallSpacing * 2)) property var model: null source: model ? model.decoration : undefined usesPlasmaTheme: false animated: false } } Timer { id: dragTimer interval: 1000 onTriggered: { if (dragSwitchDesktopId != -1 && dragSwitchDesktopId !== pagerModel.currentPage) { pagerModel.changePage(dragSwitchDesktopId); } } } Grid { id: pagerItemGrid spacing: units.devicePixelRatio rows: effectiveRows columns: effectiveColumns z: 1 readonly property int effectiveRows: { if (!pagerModel.count) { return 1; } var rows = 1; if (isActivityPager && plasmoid.configuration.pagerLayout !== 0 /*No Default*/) { if (plasmoid.configuration.pagerLayout === 1 /*Horizontal*/) { rows = 1; } else if (plasmoid.configuration.pagerLayout === 2 /*Vertical*/) { rows = pagerModel.count; } } else { var columns = Math.floor(pagerModel.count / pagerModel.layoutRows); if (pagerModel.count % pagerModel.layoutRows > 0) { columns += 1; } rows = Math.floor(pagerModel.count / columns); if (pagerModel.count % columns > 0) { rows += 1; } } return rows; } readonly property int effectiveColumns: { if (!pagerModel.count) { return 1; } return Math.ceil(pagerModel.count / effectiveRows); } readonly property real pagerItemSizeRatio: pagerModel.pagerItemSize.width / pagerModel.pagerItemSize.height readonly property real widthScaleFactor: columnWidth / pagerModel.pagerItemSize.width readonly property real heightScaleFactor: rowHeight / pagerModel.pagerItemSize.height states: [ State { name: "vertical" when: vertical PropertyChanges { target: pagerItemGrid innerSpacing: effectiveColumns rowHeight: Math.floor(columnWidth / pagerItemSizeRatio) columnWidth: Math.floor((root.width - innerSpacing) / effectiveColumns) } } ] property int innerSpacing: (effectiveRows - 1) * spacing property int rowHeight: Math.floor((root.height - innerSpacing) / effectiveRows) property int columnWidth: Math.floor(rowHeight * pagerItemSizeRatio) Repeater { id: repeater model: pagerModel PlasmaCore.ToolTipArea { id: desktop - property int desktopId: index + property string desktopId: isActivityPager ? model.TasksModel.activity : model.TasksModel.virtualDesktop property bool active: (index == pagerModel.currentPage) mainText: model.display // our ToolTip has maximumLineCount of 8 which doesn't fit but QML doesn't // respect that in RichText so we effectively can put in as much as we like :) // it also gives us more flexibility when it comes to styling the
  • textFormat: Text.RichText function updateSubText() { var generateWindowList = function windowList(windows) { // if we have 5 windows, we would show "4 and another one" with the // hint that there's 1 more taking the same amount of space than just showing it var maximum = windows.length === 5 ? 5 : 4 var text = "
    • " + windows.slice(0, maximum).join("
    • ") + "
    " if (windows.length > maximum) { text += i18np("...and %1 other window", "...and %1 other windows", windows.length - maximum) } return text } var text = "" var visibleWindows = [] var minimizedWindows = [] for (var i = 0, length = windowRectRepeater.count; i < length; ++i) { var window = windowRectRepeater.itemAt(i) if (window) { if (window.minimized) { minimizedWindows.push(window.visibleName) } else { visibleWindows.push(window.visibleName) } } } if (visibleWindows.length === 1) { text += visibleWindows[0] } else if (visibleWindows.length > 1) { text += i18np("%1 Window:", "%1 Windows:", visibleWindows.length) + generateWindowList(visibleWindows) } if (visibleWindows.length && minimizedWindows.length) { text += "
    " } if (minimizedWindows.length > 0) { text += i18np("%1 Minimized Window:", "%1 Minimized Windows:", minimizedWindows.length) + generateWindowList(minimizedWindows) } if (text.length) { // Get rid of the spacing
      would cause text = "" + text } subText = text } width: pagerItemGrid.columnWidth height: pagerItemGrid.rowHeight PlasmaCore.FrameSvgItem { id: desktopFrame anchors.fill: parent z: 2 // Above optional label item and windows imagePath: "widgets/pager" prefix: (desktopMouseArea.enabled && desktopMouseArea.containsMouse) || (root.dragging && root.dragId == desktopId) ? "hover" : (desktop.active ? "active" : "normal") } DropArea { id: droparea anchors.fill: parent preventStealing: true onDragEnter: { root.dragSwitchDesktopId = desktop.desktopId; dragTimer.start(); } onDragLeave: { - root.dragSwitchDesktopId = -1; + root.dragSwitchDesktopId = ""; dragTimer.stop(); } onDrop: { pagerModel.drop(event.mimeData, desktop.desktopId); root.dragSwitchDesktopId = -1; dragTimer.stop(); } } MouseArea { id: desktopMouseArea anchors.fill: parent hoverEnabled : true - onClicked: pagerModel.changePage(desktopId); + onClicked: pagerModel.changePage(index); } Item { id: clipRect x: Math.round(units.devicePixelRatio) y: Math.round(units.devicePixelRatio) width: desktop.width - 2 * x height: desktop.height - 2 * y z: 1 // Between optional label item and FrameSvg Repeater { id: windowRectRepeater model: TasksModel onCountChanged: desktop.updateSubText() Rectangle { id: windowRect z: 1 + model.StackingOrder property rect geometry: model.Geometry - property int windowId: model.LegacyWinIdList[0] + property int windowId: model.WinIdList[0] property string visibleName: model.display property bool minimized: (model.IsMinimized === true) onMinimizedChanged: desktop.updateSubText() onVisibleNameChanged: desktop.updateSubText() /* since we move clipRect with 1, move it back */ x: (geometry.x * pagerItemGrid.widthScaleFactor) - Math.round(units.devicePixelRatio) y: (geometry.y * pagerItemGrid.heightScaleFactor) - Math.round(units.devicePixelRatio) width: geometry.width * pagerItemGrid.widthScaleFactor height: geometry.height * pagerItemGrid.heightScaleFactor visible: model.IsMinimized !== true color: { if (desktop.active) { if (model.IsActive === true) return windowActiveOnActiveDesktopColor; else return windowInactiveOnActiveDesktopColor; } else { if (model.IsActive === true) return windowActiveColor; else return windowInactiveColor; } } border.width: Math.round(units.devicePixelRatio) border.color: (model.IsActive === true) ? windowActiveBorderColor : windowInactiveBorderColor MouseArea { id: windowMouseArea anchors.fill: parent drag.target: windowRect drag.axis: Drag.XandYAxis drag.minimumX: -windowRect.width/2 drag.maximumX: root.width - windowRect.width/2 drag.minimumY: -windowRect.height/2 drag.maximumY: root.height - windowRect.height/2 drag.onActiveChanged: { root.dragging = drag.active; root.dragId = desktop.desktopId; desktopMouseArea.enabled = !drag.active; if (drag.active) { // Reparent to allow drags outside of this desktop. var value = root.mapFromItem(clipRect, windowRect.x, windowRect.y); windowRect.parent = root; windowRect.x = value.x; windowRect.y = value.y } } onReleased: { if (root.dragging) { windowRect.visible = false; var windowCenter = Qt.point(windowRect.x + windowRect.width / 2, windowRect.y + windowRect.height / 2); var pagerItem = pagerItemGrid.childAt(windowCenter.x, windowCenter.y); if (pagerItem) { var relativeTopLeft = root.mapToItem(pagerItem, windowRect.x, windowRect.y); pagerModel.moveWindow(windowRect.windowId, relativeTopLeft.x, relativeTopLeft.y, pagerItem.desktopId, root.dragId, pagerItemGrid.widthScaleFactor, pagerItemGrid.heightScaleFactor); } // Will reset the model, destroying the reparented drag delegate that // is no longer bound to model.Geometry. root.dragging = false; pagerModel.refresh(); } else { // When there is no dragging (just a click), the event is passed // to the desktop MouseArea. desktopMouseArea.clicked(mouse); } } } Component.onCompleted: { if (plasmoid.configuration.showWindowIcons) { windowIconComponent.createObject(windowRect, {"model": model}); } } } } } Component.onCompleted: { if (plasmoid.configuration.displayedText < 2) { desktopLabelComponent.createObject(desktop, {"index": index, "model": model, "desktopFrame": desktopFrame}); } } } } } Component.onCompleted: { if (isActivityPager) { plasmoid.setAction("showActivityManager", i18n("Show Activity Manager..."), "preferences-activities"); } else { if (KQuickControlsAddonsComponents.KCMShell.authorize("desktop.desktop").length > 0) { plasmoid.setAction("addDesktop", i18n("Add Virtual Desktop"), "list-add"); plasmoid.setAction("removeDesktop", i18n("Remove Virtual Desktop"), "list-remove"); plasmoid.action("removeDesktop").enabled = Qt.binding(function() { return repeater.count > 1; }); plasmoid.setAction("openKCM", i18n("Configure Desktops..."), "configure"); } } } } diff --git a/applets/pager/plugin/pagermodel.cpp b/applets/pager/plugin/pagermodel.cpp index 0b75afd82..1485e69d2 100644 --- a/applets/pager/plugin/pagermodel.cpp +++ b/applets/pager/plugin/pagermodel.cpp @@ -1,647 +1,678 @@ /******************************************************************** Copyright 2007 Daniel Laidig Copyright 2012 Luís Gabriel Lima Copyright 2016 Eike Hein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *********************************************************************/ #include "pagermodel.h" #include "windowmodel.h" #include #include #include #include +#include #include #include #include #include #include #include #include #include using namespace TaskManager; class PagerModel::Private { public: Private(PagerModel *q); ~Private(); static int instanceCount; bool componentComplete = false; PagerType pagerType = VirtualDesktops; bool enabled = false; bool showDesktop = false; bool showOnlyCurrentScreen = false; QRect screenGeometry; WindowTasksModel *tasksModel = nullptr; static ActivityInfo *activityInfo; QMetaObject::Connection activityNumberConn; QMetaObject::Connection activityNamesConn; static VirtualDesktopInfo *virtualDesktopInfo; QMetaObject::Connection virtualDesktopNumberConn; QMetaObject::Connection virtualDesktopNamesConn; QDesktopWidget *desktopWidget = QApplication::desktop(); QList windowModels; #if HAVE_X11 QList cachedStackingOrder = KWindowSystem::stackingOrder(); #endif void refreshDataSource(); private: PagerModel *q; }; int PagerModel::Private::instanceCount = 0; ActivityInfo *PagerModel::Private::activityInfo = nullptr; VirtualDesktopInfo *PagerModel::Private::virtualDesktopInfo = nullptr; PagerModel::Private::Private(PagerModel *q) : q(q) { ++instanceCount; if (!activityInfo) { activityInfo = new ActivityInfo(); } QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, &PagerModel::shouldShowPagerChanged); if (!virtualDesktopInfo) { virtualDesktopInfo = new VirtualDesktopInfo(); } QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, &PagerModel::shouldShowPagerChanged); QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, [this]() { if (pagerType == VirtualDesktops && windowModels.count()) { for (auto windowModel : windowModels) { windowModel->setActivity(activityInfo->currentActivity()); } } } ); QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopLayoutRowsChanged, q, &PagerModel::layoutRowsChanged); QObject::connect(desktopWidget, &QDesktopWidget::screenCountChanged, q, &PagerModel::pagerItemSizeChanged); QObject::connect(desktopWidget, &QDesktopWidget::resized, q, &PagerModel::pagerItemSizeChanged); #if HAVE_X11 QObject::connect(KWindowSystem::self(), &KWindowSystem::stackingOrderChanged, q, [this]() { cachedStackingOrder = KWindowSystem::stackingOrder(); for (auto windowModel : windowModels) { windowModel->refreshStackingOrder(); } } ); #endif } PagerModel::Private::~Private() { --instanceCount; if (!instanceCount) { delete activityInfo; activityInfo = nullptr; delete virtualDesktopInfo; virtualDesktopInfo = nullptr; } } void PagerModel::Private::refreshDataSource() { if (pagerType == VirtualDesktops) { QObject::disconnect(virtualDesktopNumberConn); virtualDesktopNumberConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, [this]() { q->refresh(); }); QObject::disconnect(virtualDesktopNamesConn); virtualDesktopNamesConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopNamesChanged, q, [this]() { if (q->rowCount()) { emit q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QVector{Qt::DisplayRole}); } } ); QObject::disconnect(activityNumberConn); QObject::disconnect(activityNamesConn); QObject::disconnect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged); QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); } else { QObject::disconnect(activityNumberConn); activityNumberConn = QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, [this]() { q->refresh(); }); QObject::disconnect(activityNamesConn); activityNamesConn = QObject::connect(activityInfo, &ActivityInfo::namesOfRunningActivitiesChanged, q, [this]() { q->refresh(); }); QObject::disconnect(virtualDesktopNumberConn); QObject::disconnect(virtualDesktopNamesConn); QObject::disconnect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged); QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); } emit q->currentPageChanged(); } PagerModel::PagerModel(QObject *parent) : QAbstractListModel(parent) , d(new Private(this)) { d->tasksModel = new WindowTasksModel(this); } PagerModel::~PagerModel() { } QHash PagerModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } int PagerModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->windowModels.count(); } QVariant PagerModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= d->windowModels.count()) { return QVariant(); } if (role == Qt::DisplayRole) { if (d->pagerType == VirtualDesktops) { return d->virtualDesktopInfo->desktopNames().at(index.row()); } else { QString activityId = d->activityInfo->runningActivities().at(index.row()); return d->activityInfo->activityName(activityId); } } else if (role == TasksModel) { return QVariant::fromValue(d->windowModels.at(index.row())); } return QVariant(); } PagerModel::PagerType PagerModel::pagerType() const { return d->pagerType; } void PagerModel::setPagerType(PagerType type) { if (d->pagerType != type) { d->pagerType = type; refresh(); emit pagerTypeChanged(); emit shouldShowPagerChanged(); } } bool PagerModel::enabled() const { return d->enabled; } void PagerModel::setEnabled(bool enabled) { if (enabled && !d->enabled) { refresh(); d->enabled = true; emit enabledChanged(); } else if (!enabled && d->enabled) { beginResetModel(); disconnect(d->activityNumberConn); disconnect(d->activityNamesConn); disconnect(d->virtualDesktopNumberConn); disconnect(d->virtualDesktopNamesConn); qDeleteAll(d->windowModels); d->windowModels.clear(); endResetModel(); d->enabled = false; emit enabledChanged(); emit countChanged(); } } bool PagerModel::shouldShowPager() const { return (d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() > 1 : d->activityInfo->numberOfRunningActivities() > 1; } bool PagerModel::showDesktop() const { return d->showDesktop; } void PagerModel::setShowDesktop(bool show) { if (d->showDesktop != show) { d->showDesktop = show; emit showDesktopChanged(); } } bool PagerModel::showOnlyCurrentScreen() const { return d->showOnlyCurrentScreen; } void PagerModel::setShowOnlyCurrentScreen(bool show) { if (d->showOnlyCurrentScreen != show) { d->showOnlyCurrentScreen = show; if (d->screenGeometry.isValid()) { emit pagerItemSizeChanged(); refresh(); } emit showOnlyCurrentScreenChanged(); } } QRect PagerModel::screenGeometry() const { return d->screenGeometry; } void PagerModel::setScreenGeometry(const QRect &geometry) { if (d->screenGeometry != geometry) { d->screenGeometry = geometry; if (d->showOnlyCurrentScreen) { emit pagerItemSizeChanged(); refresh(); } emit showOnlyCurrentScreenChanged(); } } int PagerModel::currentPage() const { if (d->pagerType == VirtualDesktops) { - return d->virtualDesktopInfo->currentDesktop() - 1; + return d->virtualDesktopInfo->desktopIds().indexOf(d->virtualDesktopInfo->currentDesktop()); } else { return d->activityInfo->runningActivities().indexOf(d->activityInfo->currentActivity()); } } int PagerModel::layoutRows() const { return qBound(1, d->virtualDesktopInfo->desktopLayoutRows(), d->virtualDesktopInfo->numberOfDesktops()); } QSize PagerModel::pagerItemSize() const { if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { return d->screenGeometry.size(); } QRect totalRect; for (int i = 0; i < d->desktopWidget->screenCount(); ++i) { totalRect |= d->desktopWidget->screenGeometry(i); } return totalRect.size(); } #if HAVE_X11 QList PagerModel::stackingOrder() const { return d->cachedStackingOrder; } #endif void PagerModel::refresh() { if (!d->componentComplete) { return; } beginResetModel(); d->refreshDataSource(); int modelCount = d->windowModels.count(); const int modelsNeeded = ((d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() : d->activityInfo->numberOfRunningActivities()); if (modelCount > modelsNeeded) { while (modelCount != modelsNeeded) { delete d->windowModels.takeLast(); --modelCount; } } else if (modelsNeeded > modelCount) { while (modelCount != modelsNeeded) { WindowModel *windowModel = new WindowModel(this); windowModel->setFilterSkipPager(true); windowModel->setFilterByVirtualDesktop(true); windowModel->setFilterByActivity(true); windowModel->setDemandingAttentionSkipsFilters(false); windowModel->setSourceModel(d->tasksModel); d->windowModels.append(windowModel); ++modelCount; } } if (d->pagerType == VirtualDesktops) { - int virtualDesktop = 1; + int virtualDesktop = 0; for (auto windowModel : d->windowModels) { - windowModel->setVirtualDesktop(virtualDesktop); + windowModel->setVirtualDesktop(d->virtualDesktopInfo->desktopIds().at(virtualDesktop)); ++virtualDesktop; windowModel->setActivity(d->activityInfo->currentActivity()); } } else { int activityIndex = 0; const QStringList &runningActivities = d->activityInfo->runningActivities(); for (auto windowModel : d->windowModels) { - windowModel->setVirtualDesktop(0); + windowModel->setVirtualDesktop(); windowModel->setActivity(runningActivities.at(activityIndex)); ++activityIndex; } } for (auto windowModel : d->windowModels) { if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { windowModel->setScreenGeometry(d->screenGeometry); windowModel->setFilterByScreen(true); } else { windowModel->setFilterByScreen(false); } } endResetModel(); emit countChanged(); } -void PagerModel::moveWindow(int window, double x, double y, int targetItemId, int sourceItemId, +void PagerModel::moveWindow(int window, double x, double y, const QVariant &targetItemId, const QVariant &sourceItemId, qreal widthScaleFactor, qreal heightScaleFactor) { #if HAVE_X11 - if (!KWindowSystem::isPlatformX11()) { - return; - } - - const WId windowId = (WId)window; - - QPointF dest(x / widthScaleFactor, y / heightScaleFactor); + if (KWindowSystem::isPlatformX11()) { + const WId windowId = (WId)window; - // Don't move windows to negative positions. - dest = QPointF(qMax(dest.x(), qreal(0.0)), qMax(dest.y(), qreal(0.0))); + QPointF dest(x / widthScaleFactor, y / heightScaleFactor); - // Use _NET_MOVERESIZE_WINDOW rather than plain move, so that the WM knows this is a pager request. - NETRootInfo info(QX11Info::connection(), NET::Properties()); - const int flags = (0x20 << 12) | (0x03 << 8) | 1; // From tool, x/y, northwest gravity. + // Don't move windows to negative positions. + dest = QPointF(qMax(dest.x(), qreal(0.0)), qMax(dest.y(), qreal(0.0))); - if (!KWindowSystem::mapViewport()) { - KWindowInfo windowInfo(windowId, NET::WMDesktop | NET::WMState, NET::WM2Activities); + // Use _NET_MOVERESIZE_WINDOW rather than plain move, so that the WM knows this is a pager request. + NETRootInfo info(QX11Info::connection(), NET::Properties()); + const int flags = (0x20 << 12) | (0x03 << 8) | 1; // From tool, x/y, northwest gravity. - if (d->pagerType == VirtualDesktops) { - if (!windowInfo.onAllDesktops()) { - KWindowSystem::setOnDesktop(windowId, targetItemId + 1); - } - } else { - const QStringList &runningActivities = d->activityInfo->runningActivities(); + if (!KWindowSystem::mapViewport()) { + KWindowInfo windowInfo(windowId, NET::WMDesktop | NET::WMState, NET::WM2Activities); - if (targetItemId < runningActivities.length()) { - const QString &newActivity = runningActivities.at(targetItemId); - QStringList activities = windowInfo.activities(); - - if (!activities.contains(newActivity)) { - activities.removeOne(runningActivities.at(sourceItemId)); - activities.append(newActivity); - KWindowSystem::setOnActivities(windowId, activities); + if (d->pagerType == VirtualDesktops) { + if (!windowInfo.onAllDesktops()) { + KWindowSystem::setOnDesktop(windowId, targetItemId.toInt()); + } + } else { + const QStringList &runningActivities = d->activityInfo->runningActivities(); + + if (targetItemId < runningActivities.length()) { + const QString &newActivity = targetItemId.toString(); + QStringList activities = windowInfo.activities(); + + if (!activities.contains(newActivity)) { + activities.removeOne(sourceItemId.toString()); + activities.append(newActivity); + KWindowSystem::setOnActivities(windowId, activities); + } } } - } - // Only move the window if it is not full screen and if it is kept within the same desktop. - // Moving when dropping between desktop is too annoying due to the small drop area. - if (!(windowInfo.state() & NET::FullScreen) && - (targetItemId == sourceItemId || windowInfo.onAllDesktops())) { - const QPoint &d = dest.toPoint(); + // Only move the window if it is not full screen and if it is kept within the same desktop. + // Moving when dropping between desktop is too annoying due to the small drop area. + if (!(windowInfo.state() & NET::FullScreen) && + (targetItemId == sourceItemId || windowInfo.onAllDesktops())) { + const QPoint &d = dest.toPoint(); + info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0); + } + } else { + // setOnDesktop() with viewports is also moving a window, and since it takes a moment + // for the WM to do the move, there's a race condition with figuring out how much to move, + // so do it only as one move. + dest += KWindowSystem::desktopToViewport(targetItemId.toInt(), false); + const QPoint &d = KWindowSystem::constrainViewportRelativePosition(dest.toPoint()); info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0); } - } else { - // setOnDesktop() with viewports is also moving a window, and since it takes a moment - // for the WM to do the move, there's a race condition with figuring out how much to move, - // so do it only as one move. - dest += KWindowSystem::desktopToViewport(targetItemId + 1, false); - const QPoint &d = KWindowSystem::constrainViewportRelativePosition(dest.toPoint()); - info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0); } #else Q_UNUSED(window) Q_UNUSED(x) Q_UNUSED(y) - Q_UNUSED(targetDesktop) - Q_UNUSED(sourceDesktop) + Q_UNUSED(sourceItemId) #endif -} -void PagerModel::changePage(int page) -{ + if (KWindowSystem::isPlatformWayland()) { + if (d->pagerType == VirtualDesktops) { + QAbstractItemModel *model = d->windowModels.at(0)->sourceModel(); + TaskManager::WindowTasksModel *tasksModel = static_cast(model); -#if HAVE_X11 - if (!KWindowSystem::isPlatformX11()) { - return; + for (int i = 0; i < tasksModel->rowCount(); ++i) { + const QModelIndex &idx = tasksModel->index(i, 0); + + if (idx.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { + break; + } + + const QVariantList &winIds = idx.data(TaskManager::AbstractTasksModel::WinIdList).toList(); + + if (!winIds.isEmpty() && winIds.at(0).toUInt() == window) { + tasksModel->requestVirtualDesktops(idx, QVariantList() << targetItemId.toString()); + break; + } + } + } else { + //FIXME TODO: Activities support. + } } +} +void PagerModel::changePage(int page) +{ if (currentPage() == page) { if (d->showDesktop) { QDBusConnection::sessionBus().asyncCall(QDBusMessage::createMethodCall(QLatin1String("org.kde.plasmashell"), QLatin1String("/PlasmaShell"), QLatin1String("org.kde.PlasmaShell"), QLatin1String("toggleDashboard"))); } } else { if (d->pagerType == VirtualDesktops) { - KWindowSystem::setCurrentDesktop(page + 1); + d->virtualDesktopInfo->requestActivate(d->virtualDesktopInfo->desktopIds().at(page)); } else { const QStringList &runningActivities = d->activityInfo->runningActivities(); if (page < runningActivities.length()) { KActivities::Controller activitiesController; activitiesController.setCurrentActivity(runningActivities.at(page)); } } } - -#else - Q_UNUSED(itemId) -#endif } -void PagerModel::drop(QMimeData *mimeData, int itemId) +void PagerModel::drop(QMimeData *mimeData, const QVariant &itemId) { if (!mimeData) { return; } #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { bool ok; const QList &ids = TaskManager::XWindowTasksModel::winIdsFromMimeData(mimeData, &ok); if (!ok) { return; } if (d->pagerType == VirtualDesktops) { for (const auto &id : ids) { - KWindowSystem::setOnDesktop(id, itemId + 1); + KWindowSystem::setOnDesktop(id, itemId.toInt()); } } else { QString newActivity; const QStringList &runningActivities = d->activityInfo->runningActivities(); if (itemId < runningActivities.length()) { - newActivity = runningActivities.at(itemId); + newActivity = itemId.toString(); } if (newActivity.isEmpty()) { return; } for (const auto &id : ids) { QStringList activities = KWindowInfo(id, NET::Properties(), NET::WM2Activities).activities(); if (!activities.contains(newActivity)) { KWindowSystem::setOnActivities(id, activities << newActivity); } } } + + return; } -#else - Q_UNUSED(itemId) #endif + + if (KWindowSystem::isPlatformWayland()) { + bool ok; + + const QList &ids = TaskManager::WaylandTasksModel::winIdsFromMimeData(mimeData, &ok); + + if (!ok) { + return; + } + + if (d->pagerType == VirtualDesktops) { + for (const quint32 &id : ids) { + QAbstractItemModel *model = d->windowModels.at(0)->sourceModel(); + TaskManager::WindowTasksModel *tasksModel = static_cast(model); + + for (int i = 0; i < tasksModel->rowCount(); ++i) { + const QModelIndex &idx = tasksModel->index(i, 0); + + if (idx.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { + break; + } + + const QVariantList &winIds = idx.data(TaskManager::AbstractTasksModel::WinIdList).toList(); + + if (!winIds.isEmpty() && winIds.at(0).toUInt() == id) { + tasksModel->requestVirtualDesktops(idx, QVariantList() << itemId.toString()); + break; + } + } + } + } + } } void PagerModel::addDesktop() { -#if HAVE_X11 - if (!KWindowSystem::isPlatformX11()) { - return; - } - - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); - info.setNumberOfDesktops(info.numberOfDesktops() + 1); -#endif + d->virtualDesktopInfo->requestCreateDesktop(d->virtualDesktopInfo->numberOfDesktops()); } void PagerModel::removeDesktop() { -#if HAVE_X11 - if (!KWindowSystem::isPlatformX11()) { + if (d->virtualDesktopInfo->numberOfDesktops() == 1) { return; } - NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); - - if (info.numberOfDesktops() > 1) { - info.setNumberOfDesktops(info.numberOfDesktops() - 1); - } -#endif + d->virtualDesktopInfo->requestRemoveDesktop(d->virtualDesktopInfo->numberOfDesktops() - 1); } void PagerModel::classBegin() { } void PagerModel::componentComplete() { d->componentComplete = true; if (d->enabled) { refresh(); } } #include "moc_pagermodel.cpp" diff --git a/applets/pager/plugin/pagermodel.h b/applets/pager/plugin/pagermodel.h index ae441a086..e2246d38f 100644 --- a/applets/pager/plugin/pagermodel.h +++ b/applets/pager/plugin/pagermodel.h @@ -1,130 +1,130 @@ /******************************************************************** Copyright 2016 Eike Hein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *********************************************************************/ #ifndef PAGERMODEL_H #define PAGERMODEL_H #include #if HAVE_X11 #include #include #endif #include #include #include class QMimeData; class PagerModel : public QAbstractListModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_ENUMS(PagerType) Q_ENUMS(AdditionalRoles) Q_PROPERTY(int count READ rowCount NOTIFY countChanged) Q_PROPERTY(PagerType pagerType READ pagerType WRITE setPagerType NOTIFY pagerTypeChanged) Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(bool shouldShowPager READ shouldShowPager NOTIFY shouldShowPagerChanged) Q_PROPERTY(bool showDesktop READ showDesktop WRITE setShowDesktop NOTIFY showDesktopChanged) Q_PROPERTY(bool showOnlyCurrentScreen READ showOnlyCurrentScreen WRITE setShowOnlyCurrentScreen NOTIFY showOnlyCurrentScreenChanged) Q_PROPERTY(QRect screenGeometry READ screenGeometry WRITE setScreenGeometry NOTIFY screenGeometryChanged) Q_PROPERTY(int currentPage READ currentPage NOTIFY currentPageChanged) Q_PROPERTY(int layoutRows READ layoutRows NOTIFY layoutRowsChanged) Q_PROPERTY(QSize pagerItemSize READ pagerItemSize NOTIFY pagerItemSizeChanged) public: enum PagerType { VirtualDesktops = 0, Activities }; enum AdditionalRoles { TasksModel = Qt::UserRole + 1 }; explicit PagerModel(QObject *parent = nullptr); ~PagerModel() override; QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; PagerType pagerType() const; void setPagerType(PagerType type); bool enabled() const; void setEnabled(bool enabled); bool shouldShowPager() const; bool showDesktop() const; void setShowDesktop(bool show); bool showOnlyCurrentScreen() const; void setShowOnlyCurrentScreen(bool show); QRect screenGeometry() const; void setScreenGeometry(const QRect &geometry); int currentPage() const; int layoutRows() const; QSize pagerItemSize() const; #if HAVE_X11 QList stackingOrder() const; #endif Q_INVOKABLE void refresh(); - Q_INVOKABLE void moveWindow(int window, double x, double y, int targetItemId, int sourceItemId, + Q_INVOKABLE void moveWindow(int window, double x, double y, const QVariant &targetItemId, const QVariant &sourceItemId, qreal widthScaleFactor, qreal heightScaleFactor); Q_INVOKABLE void changePage(int page); - Q_INVOKABLE void drop(QMimeData *mimeData, int itemId); + Q_INVOKABLE void drop(QMimeData *mimeData, const QVariant &itemId); Q_INVOKABLE void addDesktop(); Q_INVOKABLE void removeDesktop(); void classBegin() override; void componentComplete() override; Q_SIGNALS: void countChanged() const; void pagerTypeChanged() const; void enabledChanged() const; void shouldShowPagerChanged() const; void showDesktopChanged() const; void showOnlyCurrentScreenChanged() const; void screenGeometryChanged() const; void currentPageChanged() const; void layoutRowsChanged() const; void pagerItemSizeChanged() const; private: class Private; QScopedPointer d; }; #endif diff --git a/applets/pager/plugin/windowmodel.cpp b/applets/pager/plugin/windowmodel.cpp index 04a228452..89e75c6a2 100644 --- a/applets/pager/plugin/windowmodel.cpp +++ b/applets/pager/plugin/windowmodel.cpp @@ -1,148 +1,148 @@ /******************************************************************** Copyright 2016 Eike Hein This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *********************************************************************/ #include "windowmodel.h" #include "pagermodel.h" #include #include #include #include #include #include using namespace TaskManager; class WindowModel::Private { public: Private(WindowModel *q); PagerModel *pagerModel = nullptr; QDesktopWidget *desktopWidget = QApplication::desktop(); private: WindowModel *q; }; WindowModel::Private::Private(WindowModel *q) : q(q) { } WindowModel::WindowModel(PagerModel *parent) : TaskFilterProxyModel(parent) , d(new Private(this)) { d->pagerModel = parent; } WindowModel::~WindowModel() { } QHash WindowModel::roleNames() const { QHash roles = TaskFilterProxyModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("WindowModelRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } QVariant WindowModel::data(const QModelIndex &index, int role) const { if (role == AbstractTasksModel::Geometry) { QRect windowGeo = TaskFilterProxyModel::data(index, role).toRect(); const QRect &desktopGeo = d->desktopWidget->geometry(); if (KWindowSystem::mapViewport()) { int x = windowGeo.center().x() % desktopGeo.width(); int y = windowGeo.center().y() % desktopGeo.height(); if (x < 0) { x = x + desktopGeo.width(); } if (y < 0) { y = y + desktopGeo.height(); } const QRect mappedGeo(x - windowGeo.width() / 2, y - windowGeo.height() / 2, windowGeo.width(), windowGeo.height()); if (filterByScreen() && screenGeometry().isValid()) { const QPoint &screenOffset = screenGeometry().topLeft(); windowGeo = mappedGeo.translated(0 - screenOffset.x(), 0 - screenOffset.y()); } } else if (filterByScreen() && screenGeometry().isValid()) { const QPoint &screenOffset = screenGeometry().topLeft(); windowGeo.translate(0 - screenOffset.x(), 0 - screenOffset.y()); } // Clamp to desktop rect. // TODO: Switch from qBound to std::clamp once we use C++17. windowGeo.setX(qBound(0, windowGeo.x(), desktopGeo.width())); windowGeo.setY(qBound(0, windowGeo.y(), desktopGeo.height())); if ((windowGeo.x() + windowGeo.width()) > desktopGeo.width()) { windowGeo.setWidth(desktopGeo.width() - windowGeo.x()); } if ((windowGeo.y() + windowGeo.height()) > desktopGeo.height()) { windowGeo.setHeight(desktopGeo.height() - windowGeo.y()); } return windowGeo; } else if (role == StackingOrder) { #if HAVE_X11 - const QVariantList &winIds = TaskFilterProxyModel::data(index, AbstractTasksModel::LegacyWinIdList).toList(); + const QVariantList &winIds = TaskFilterProxyModel::data(index, AbstractTasksModel::WinIdList).toList(); if (winIds.count()) { const WId winId = winIds.at(0).toLongLong(); const int z = d->pagerModel->stackingOrder().indexOf(winId); if (z != -1) { return z; } } #endif return 0; } return TaskFilterProxyModel::data(index, role); } void WindowModel::refreshStackingOrder() { if (rowCount()) { emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector{StackingOrder}); } }