diff --git a/applets/kicker/package/contents/ui/ItemListDialog.qml b/applets/kicker/package/contents/ui/ItemListDialog.qml index 760ecfc40..7e3d89675 100644 --- a/applets/kicker/package/contents/ui/ItemListDialog.qml +++ b/applets/kicker/package/contents/ui/ItemListDialog.qml @@ -1,84 +1,90 @@ /*************************************************************************** * Copyright (C) 2013-2014 by 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.private.kicker 0.1 as Kicker Kicker.SubMenu { id: itemDialog property alias focusParent: itemListView.focusParent property alias model: funnelModel.sourceModel + property bool aboutToBeDestroyed: false + visible: false hideOnWindowDeactivate: plasmoid.hideOnWindowDeactivate location: PlasmaCore.Types.Floating offset: units.smallSpacing onWindowDeactivated: { - plasmoid.expanded = false; + if (!aboutToBeDestroyed) { + plasmoid.expanded = false; + } } mainItem: ItemListView { id: itemListView height: model != undefined ? Math.min(((Math.floor((itemDialog.availableScreenRectForItem(itemListView).height - itemDialog.margins.top - itemDialog.margins.bottom) / itemHeight) - 1) * itemHeight) - (model.separatorCount * itemHeight) + (model.separatorCount * separatorHeight), ((model.count - model.separatorCount) * itemHeight) + (model.separatorCount * separatorHeight)) : 0 iconsEnabled: true dialog: itemDialog model: funnelModel Kicker.FunnelModel { id: funnelModel Component.onCompleted: { kicker.reset.connect(funnelModel.reset); } onCountChanged: { if (sourceModel && count == 0) { itemDialog.delayedDestroy(); } } onSourceModelChanged: { itemListView.currentIndex = -1; } } } function delayedDestroy() { + aboutToBeDestroyed = true; + plasmoid.hideOnWindowDeactivate = false; + visible = false; + Qt.callLater(function() { itemDialog.destroy(); }); } - - } diff --git a/applets/kicker/package/contents/ui/ItemListView.qml b/applets/kicker/package/contents/ui/ItemListView.qml index 40e1a87ba..28ea43a59 100644 --- a/applets/kicker/package/contents/ui/ItemListView.qml +++ b/applets/kicker/package/contents/ui/ItemListView.qml @@ -1,263 +1,258 @@ /*************************************************************************** * Copyright (C) 2013-2014 by 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 FocusScope { id: itemList width: units.gridUnit * 14 height: listView.contentHeight signal exited signal keyNavigationAtListEnd signal appendSearchText(string text) property Item focusParent: null property QtObject dialog: null property QtObject childDialog: null property bool iconsEnabled: false property int itemHeight: Math.ceil((Math.max(theme.mSize(theme.defaultFont).height, units.iconSizes.small) + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2 property int separatorHeight: lineSvg.horLineHeight + (2 * units.smallSpacing) property alias currentIndex: listView.currentIndex property alias currentItem: listView.currentItem property alias keyNavigationWraps: listView.keyNavigationWraps property alias showChildDialogs: listView.showChildDialogs property alias model: listView.model property alias containsMouse: listener.containsMouse property alias resetOnExitDelay: resetIndexTimer.interval onFocusParentChanged: { appendSearchText.connect(focusParent.appendSearchText); } Timer { id: dialogSpawnTimer property bool focusOnSpawn: false interval: 70 repeat: false onTriggered: { if (!plasmoid.expanded || model == undefined || currentIndex == -1) { return; } if (childDialog != null) { - childDialog.visible = false; childDialog.delayedDestroy(); } - windowSystem.monitorWindowFocus(itemList); - - // Gets reenabled after the dialog spawn causes a focus out on this window. - // This avoids Kicker closing due to unreliable timing making Dialog::focusOutEvent() - // unable to tell focus moved to a child window. + // Gets reenabled after the dialog spawn causes a focus-in on the dialog window. plasmoid.hideOnWindowDeactivate = false; childDialog = itemListDialogComponent.createObject(itemList); childDialog.focusParent = itemList; childDialog.visualParent = listView.currentItem; childDialog.model = model.modelForRow(listView.currentIndex); childDialog.visible = true; windowSystem.forceActive(childDialog.mainItem); childDialog.mainItem.focus = true; if (focusOnSpawn) { childDialog.mainItem.showChildDialogs = false; childDialog.mainItem.currentIndex = 0; childDialog.mainItem.showChildDialogs = true; } } } Timer { id: resetIndexTimer interval: (dialog != null) ? 50 : 150 repeat: false onTriggered: { if (focus && (!childDialog || !childDialog.mainItem.containsMouse)) { currentIndex = -1; itemList.exited(); } } } MouseEventListener { id: listener anchors.fill: parent hoverEnabled: true onContainsMouseChanged: { listView.eligibleWidth = listView.width; if (containsMouse) { resetIndexTimer.stop(); } else if ((!childDialog || !dialog) && (!currentItem || !currentItem.menu.opened)) { resetIndexTimer.start(); } } PlasmaExtras.ScrollArea { anchors.fill: parent focus: true ListView { id: listView property bool showChildDialogs: true property int eligibleWidth: width currentIndex: -1 boundsBehavior: Flickable.StopAtBounds snapMode: ListView.SnapToItem spacing: 0 keyNavigationWraps: (dialog != null) delegate: ItemListDelegate {} highlight: PlasmaComponents.Highlight { anchors.fill: listView.currentItem; visible: listView.currentItem && !listView.currentItem.isSeparator } onCountChanged: { currentIndex = -1; } onCurrentIndexChanged: { if (currentIndex != -1) { itemList.forceActiveFocus(); if (childDialog) { if (currentItem && currentItem.hasChildren) { childDialog.model = model.modelForRow(currentIndex); childDialog.visualParent = listView.currentItem; } else { - childDialog.visible = false; childDialog.delayedDestroy(); } return; } if (currentItem == null || !currentItem.hasChildren || !plasmoid.expanded) { dialogSpawnTimer.stop(); return; } if (showChildDialogs) { dialogSpawnTimer.focusOnSpawn = false; dialogSpawnTimer.restart(); } } else if (childDialog != null) { - childDialog.visible = false; childDialog.delayedDestroy(); childDialog = null; } } onCurrentItemChanged: { if (currentItem) { currentItem.menu.closed.connect(resetIndexTimer.restart); } } Keys.onPressed: { if (event.key == Qt.Key_Up) { event.accepted = true; if (!keyNavigationWraps && currentIndex == 0) { itemList.keyNavigationAtListEnd(); return; } showChildDialogs = false; decrementCurrentIndex(); if (currentItem.isSeparator) { decrementCurrentIndex(); } showChildDialogs = true; } else if (event.key == Qt.Key_Down) { event.accepted = true; if (!keyNavigationWraps && currentIndex == count - 1) { itemList.keyNavigationAtListEnd(); return; } showChildDialogs = false; incrementCurrentIndex(); if (currentItem.isSeparator) { incrementCurrentIndex(); } showChildDialogs = true; } else if ((event.key == Qt.Key_Right || event.key == Qt.Key_Return || event.key == Qt.Key_Enter) && childDialog != null) { windowSystem.forceActive(childDialog.mainItem); childDialog.mainItem.focus = true; childDialog.mainItem.currentIndex = 0; } else if ((event.key == Qt.Key_Right || event.key == Qt.Key_Return || event.key == Qt.Key_Enter) && childDialog == null && currentItem != null && currentItem.hasChildren) { dialogSpawnTimer.focusOnSpawn = true; dialogSpawnTimer.restart(); } else if (event.key == Qt.Key_Left && dialog != null) { dialog.destroy(); } else if (event.key == Qt.Key_Escape) { plasmoid.expanded = false; } else if (event.key == Qt.Key_Tab) { //do nothing, and skip appending text } else if (event.text != "") { appendSearchText(event.text); } } } } } Component.onCompleted: { + windowSystem.monitorWindowFocus(itemList); + if (dialog == null) { appendSearchText.connect(root.appendSearchText); } } } diff --git a/applets/kicker/package/contents/ui/main.qml b/applets/kicker/package/contents/ui/main.qml index d87571295..105ddb3bb 100644 --- a/applets/kicker/package/contents/ui/main.qml +++ b/applets/kicker/package/contents/ui/main.qml @@ -1,279 +1,280 @@ /*************************************************************************** * Copyright (C) 2014-2015 by 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 . * ***************************************************************************/ 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.plasma.private.kicker 0.1 as Kicker Item { id: kicker anchors.fill: parent signal reset property bool isDash: (plasmoid.pluginName == "org.kde.plasma.kickerdash") Plasmoid.switchWidth: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumWidth Plasmoid.switchHeight: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumHeight // this is a bit of a hack to prevent Plasma from spawning a dialog on its own when we're Dash Plasmoid.preferredRepresentation: isDash ? Plasmoid.fullRepresentation : null Plasmoid.compactRepresentation: isDash ? null : compactRepresentation Plasmoid.fullRepresentation: isDash ? compactRepresentation : menuRepresentation property QtObject itemListDialogComponent: Qt.createComponent("ItemListDialog.qml"); property Item dragSource: null property QtObject globalFavorites: rootModel.favoritesModel property QtObject systemFavorites: rootModel.systemFavoritesModel Plasmoid.icon: plasmoid.configuration.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon onSystemFavoritesChanged: { systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; } function action_menuedit() { processRunner.runMenuEditor(); } function updateSvgMetrics() { lineSvg.horLineHeight = lineSvg.elementSize("horizontal-line").height; lineSvg.vertLineWidth = lineSvg.elementSize("vertical-line").width; } Component { id: compactRepresentation CompactRepresentation {} } Component { id: menuRepresentation MenuRepresentation {} } Kicker.RootModel { id: rootModel autoPopulate: false appNameFormat: plasmoid.configuration.appNameFormat flat: isDash ? true : plasmoid.configuration.limitDepth sorted: plasmoid.configuration.alphaSort showSeparators: !isDash appletInterface: plasmoid showAllApps: isDash showRecentApps: plasmoid.configuration.showRecentApps showRecentDocs: plasmoid.configuration.showRecentDocs showRecentContacts: plasmoid.configuration.showRecentContacts recentOrdering: plasmoid.configuration.recentOrdering onShowRecentAppsChanged: { plasmoid.configuration.showRecentApps = showRecentApps; } onShowRecentDocsChanged: { plasmoid.configuration.showRecentDocs = showRecentDocs; } onShowRecentContactsChanged: { plasmoid.configuration.showRecentContacts = showRecentContacts; } onRecentOrderingChanged: { plasmoid.configuration.recentOrdering = recentOrdering; } Component.onCompleted: { favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id) if (!plasmoid.configuration.favoritesPortedToKAstats) { favoritesModel.portOldFavorites(plasmoid.configuration.favoriteApps); plasmoid.configuration.favoritesPortedToKAstats = true; } rootModel.refresh(); } } Connections { target: globalFavorites onFavoritesChanged: { plasmoid.configuration.favoriteApps = target.favorites; } } Connections { target: systemFavorites onFavoritesChanged: { plasmoid.configuration.favoriteSystemActions = target.favorites; } } Connections { target: plasmoid.configuration onFavoriteAppsChanged: { globalFavorites.favorites = plasmoid.configuration.favoriteApps; } onFavoriteSystemActionsChanged: { systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; } } Kicker.RunnerModel { id: runnerModel appletInterface: plasmoid favoritesModel: globalFavorites runners: { var runners = new Array("services"); if (isDash) { runners = runners.concat(new Array("desktopsessions", "PowerDevil")); } if (plasmoid.configuration.useExtraRunners) { runners = runners.concat(plasmoid.configuration.extraRunners); } return runners; } deleteWhenEmpty: isDash } Kicker.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium } Kicker.ProcessRunner { id: processRunner; } Kicker.WindowSystem { id: windowSystem; } PlasmaCore.FrameSvgItem { id : highlightItemSvg visible: false imagePath: "widgets/viewitem" prefix: "hover" } PlasmaCore.FrameSvgItem { id : listItemSvg visible: false imagePath: "widgets/listitem" prefix: "normal" } PlasmaCore.Svg { id: arrows imagePath: "widgets/arrows" size: "16x16" } PlasmaCore.Svg { id: lineSvg imagePath: "widgets/line" property int horLineHeight property int vertLineWidth } PlasmaComponents.Label { id: toolTipDelegate width: contentWidth height: contentHeight property Item toolTip text: (toolTip != null) ? toolTip.text : "" } Timer { id: justOpenedTimer repeat: false interval: 600 } Connections { target: plasmoid onExpandedChanged: { if (expanded) { windowSystem.monitorWindowVisibility(plasmoid.fullRepresentationItem); justOpenedTimer.start(); } else { kicker.reset(); } } } function resetDragSource() { dragSource = null; } function enableHideOnWindowDeactivate() { plasmoid.hideOnWindowDeactivate = true; } Component.onCompleted: { if (plasmoid.hasOwnProperty("activationTogglesExpanded")) { plasmoid.activationTogglesExpanded = !isDash } - windowSystem.focusOut.connect(enableHideOnWindowDeactivate); + + windowSystem.focusIn.connect(enableHideOnWindowDeactivate); plasmoid.hideOnWindowDeactivate = true; if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { plasmoid.setAction("menuedit", i18n("Edit Applications...")); } updateSvgMetrics(); theme.themeChanged.connect(updateSvgMetrics); rootModel.refreshed.connect(reset); dragHelper.dropped.connect(resetDragSource); } } diff --git a/applets/kicker/plugin/windowsystem.cpp b/applets/kicker/plugin/windowsystem.cpp index d0afcc30b..a092d5825 100644 --- a/applets/kicker/plugin/windowsystem.cpp +++ b/applets/kicker/plugin/windowsystem.cpp @@ -1,89 +1,90 @@ /*************************************************************************** * Copyright (C) 2014 by 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 "windowsystem.h" #include #include #include WindowSystem::WindowSystem(QObject *parent) : QObject(parent) { } WindowSystem::~WindowSystem() { } bool WindowSystem::eventFilter(QObject* watched, QEvent* event) { - if (event->type() == QEvent::FocusOut) { - emit focusOut(qobject_cast(watched)); + if (event->type() == QEvent::FocusIn) { + removeEventFilter(watched); + emit focusIn(qobject_cast(watched)); } return false; } void WindowSystem::forceActive(QQuickItem *item) { if (!item || !item->window()) { return; } KWindowSystem::forceActiveWindow(item->window()->winId()); KWindowSystem::raiseWindow(item->window()->winId()); } bool WindowSystem::isActive(QQuickItem *item) { if (!item || !item->window()) { return false; } return item->window()->isActive(); } void WindowSystem::monitorWindowFocus(QQuickItem* item) { if (!item || !item->window()) { return; } item->window()->installEventFilter(this); } void WindowSystem::monitorWindowVisibility(QQuickItem* item) { if (!item || !item->window()) { return; } connect(item->window(), &QQuickWindow::visibilityChanged, this, &WindowSystem::monitoredWindowVisibilityChanged, Qt::UniqueConnection); } void WindowSystem::monitoredWindowVisibilityChanged(bool visible) const { QQuickWindow *w = static_cast(QObject::sender()); if (!visible) { emit hidden(w); } } diff --git a/applets/kicker/plugin/windowsystem.h b/applets/kicker/plugin/windowsystem.h index 387b8997e..cd104db96 100644 --- a/applets/kicker/plugin/windowsystem.h +++ b/applets/kicker/plugin/windowsystem.h @@ -1,54 +1,54 @@ /*************************************************************************** * Copyright (C) 2014 by 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 WINDOWSYSTEM_H #define WINDOWSYSTEM_H #include class QQuickItem; class QQuickWindow; class WindowSystem : public QObject { Q_OBJECT public: WindowSystem(QObject *parent = 0); ~WindowSystem(); bool eventFilter(QObject *watched, QEvent *event) Q_DECL_OVERRIDE; Q_INVOKABLE void forceActive(QQuickItem *item); Q_INVOKABLE bool isActive(QQuickItem *item); Q_INVOKABLE void monitorWindowFocus(QQuickItem *item); Q_INVOKABLE void monitorWindowVisibility(QQuickItem *item); Q_SIGNALS: - void focusOut(QQuickWindow *window) const; + void focusIn(QQuickWindow *window) const; void hidden(QQuickWindow *window) const; private Q_SLOTS: void monitoredWindowVisibilityChanged(bool visible) const; }; #endif