diff --git a/applets/kicker/plugin/actionlist.h b/applets/kicker/plugin/actionlist.h index 780505b2a..db706d920 100644 --- a/applets/kicker/plugin/actionlist.h +++ b/applets/kicker/plugin/actionlist.h @@ -1,70 +1,75 @@ /*************************************************************************** * Copyright (C) 2013 by Aurélien Gâteau * * 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 . * ***************************************************************************/ +#ifndef ACTIONLIST_H +#define ACTIONLIST_H + #include #include class KFileItem; namespace Kicker { enum { DescriptionRole = Qt::UserRole + 1, GroupRole, FavoriteIdRole, IsSeparatorRole, IsDropPlaceholderRole, IsParentRole, HasChildrenRole, HasActionListRole, ActionListRole, UrlRole }; QVariantMap createActionItem(const QString &label, const QString &actionId, const QVariant &argument = QVariant()); QVariantMap createTitleActionItem(const QString &label); QVariantMap createSeparatorActionItem(); QVariantList createActionListForFileItem(const KFileItem &fileItem); bool handleFileItemAction(const KFileItem &fileItem, const QString &actionId, const QVariant &argument, bool *close); QVariantList createAddLauncherActionList(QObject *appletInterface, const KService::Ptr &service); bool handleAddLauncherAction(const QString &actionId, QObject *appletInterface, const KService::Ptr &service); QVariantList jumpListActions(KService::Ptr service); QVariantList recentDocumentActions(KService::Ptr service); bool handleRecentDocumentAction(KService::Ptr service, const QString &actionId, const QVariant &argument); bool canEditApplication(const QString &entryPath); void editApplication(const QString &entryPath, const QString &menuId); QVariantList editApplicationAction(const KService::Ptr &service); bool handleEditApplicationAction(const QString &actionId, const KService::Ptr &service); QVariantList appstreamActions(const KService::Ptr &service); bool handleAppstreamActions(const QString &actionId, const QVariant &argument); QString resolvedServiceEntryPath(const KService::Ptr &service); } + +#endif diff --git a/applets/kickoff/package/contents/ui/FullRepresentation.qml b/applets/kickoff/package/contents/ui/FullRepresentation.qml index 7468153f2..1c22414ee 100644 --- a/applets/kickoff/package/contents/ui/FullRepresentation.qml +++ b/applets/kickoff/package/contents/ui/FullRepresentation.qml @@ -1,725 +1,725 @@ /* Copyright (C) 2011 Martin Gräßlin Copyright (C) 2012 Gregor Taetzner Copyright (C) 2012 Marco Martin Copyright (C) 2013 2014 David Edmundson Copyright 2014 Sebastian Kügler 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.3 import org.kde.plasma.plasmoid 2.0 import QtQuick.Layouts 1.1 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 import org.kde.plasma.private.kicker 0.1 as Kicker Item { id: root Layout.minimumWidth: units.gridUnit * 26 Layout.minimumHeight: units.gridUnit * 34 property string previousState property bool switchTabsOnHover: plasmoid.configuration.switchTabsOnHover property Item currentView: mainTabGroup.currentTab.decrementCurrentIndex ? mainTabGroup.currentTab : mainTabGroup.currentTab.item property KickoffButton firstButton: null property var configMenuItems property QtObject globalFavorites: rootModelFavorites state: "Normal" onFocusChanged: { header.input.forceActiveFocus(); } function switchToInitial() { if (firstButton != null) { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; header.query = "" header.state = "hint"; root.state = "Normal"; } } Kicker.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium onDropped: kickoff.dragSource = null } Kicker.AppsModel { id: rootModel autoPopulate: false appletInterface: plasmoid appNameFormat: plasmoid.configuration.showAppsByName ? 0 : 1 flat: false sorted: plasmoid.configuration.alphaSort showSeparators: false showTopLevelItems: true favoritesModel: Kicker.KAStatsFavoritesModel { id: rootModelFavorites favorites: plasmoid.configuration.favorites onFavoritesChanged: { plasmoid.configuration.favorites = favorites; } } Component.onCompleted: { favoritesModel.initForClient("org.kde.plasma.kickoff.favorites.instance-" + plasmoid.id) if (!plasmoid.configuration.favoritesPortedToKAstats) { favoritesModel.portOldFavorites(plasmoid.configuration.favorites); plasmoid.configuration.favoritesPortedToKAstats = true; } rootModel.refresh(); } } PlasmaCore.DataSource { id: pmSource engine: "powermanagement" connectedSources: ["PowerDevil"] } PlasmaCore.Svg { id: arrowsSvg imagePath: "widgets/arrows" size: "16x16" } Header { id: header } Item { id: mainArea anchors.topMargin: mainTabGroup.state == "top" ? units.smallSpacing : 0 PlasmaComponents.TabGroup { id: mainTabGroup currentTab: favoritesPage anchors { fill: parent } //pages FavoritesView { id: favoritesPage } PlasmaExtras.ConditionalLoader { id: applicationsPage when: mainTabGroup.currentTab == applicationsPage source: Qt.resolvedUrl("ApplicationsView.qml") } PlasmaExtras.ConditionalLoader { id: systemPage when: mainTabGroup.currentTab == systemPage source: Qt.resolvedUrl("ComputerView.qml") } PlasmaExtras.ConditionalLoader { id: recentlyUsedPage when: mainTabGroup.currentTab == recentlyUsedPage source: Qt.resolvedUrl("RecentlyUsedView.qml") } PlasmaExtras.ConditionalLoader { id: oftenUsedPage when: mainTabGroup.currentTab == oftenUsedPage source: Qt.resolvedUrl("OftenUsedView.qml") } PlasmaExtras.ConditionalLoader { id: leavePage when: mainTabGroup.currentTab == leavePage source: Qt.resolvedUrl("LeaveView.qml") } PlasmaExtras.ConditionalLoader { id: searchPage when: root.state == "Search" //when: mainTabGroup.currentTab == searchPage || root.state == "Search" source: Qt.resolvedUrl("SearchView.qml") } state: { switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: return LayoutMirroring.enabled ? "right" : "left"; case PlasmaCore.Types.TopEdge: return "top"; case PlasmaCore.Types.RightEdge: return LayoutMirroring.enabled ? "left" : "right"; case PlasmaCore.Types.BottomEdge: default: return "bottom"; } } states: [ State { name: "left" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header width: header.implicitWidth } AnchorChanges { target: mainArea anchors { left: tabBar.right top: root.top right: root.right bottom: header.top } } AnchorChanges { target: tabBar anchors { left: root.left top: root.top right: undefined bottom: header.top } } }, State { name: "top" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header height: header.implicitHeight } AnchorChanges { target: mainArea anchors { left: root.left top: tabBar.bottom right: root.right bottom: header.top } } AnchorChanges { target: tabBar anchors { left: root.left top: root.top right: root.right bottom: undefined } } }, State { name: "right" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header width: header.implicitWidth } AnchorChanges { target: mainArea anchors { left: root.left top: root.top right: tabBar.left bottom: header.top } } AnchorChanges { target: tabBar anchors { left: undefined top: root.top right: root.right bottom: header.top } } }, State { name: "bottom" AnchorChanges { target: header anchors { left: root.left top: root.top right: root.right bottom: undefined } } PropertyChanges { target: header height: header.implicitHeight } AnchorChanges { target: mainArea anchors { left: root.left top: header.bottom right: root.right bottom: tabBar.top } } AnchorChanges { target: tabBar anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } } ] } // mainTabGroup } PlasmaComponents.TabBar { id: tabBar property int count: 5 // updated in createButtons() width: { if (opacity == 0) { return 0; } switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: case PlasmaCore.Types.RightEdge: return units.gridUnit * 5; default: return 0; } } height: { if (opacity == 0) { return 0; } switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: case PlasmaCore.Types.RightEdge: return 0; default: return units.gridUnit * 5; } } Behavior on width { NumberAnimation { duration: units.longDuration; easing.type: Easing.InQuad; } enabled: plasmoid.expanded } Behavior on height { NumberAnimation { duration: units.longDuration; easing.type: Easing.InQuad; } enabled: plasmoid.expanded } tabPosition: { switch (plasmoid.location) { case PlasmaCore.Types.TopEdge: return Qt.TopEdge; case PlasmaCore.Types.LeftEdge: return Qt.LeftEdge; case PlasmaCore.Types.RightEdge: return Qt.RightEdge; default: return Qt.BottomEdge; } } onCurrentTabChanged: header.input.forceActiveFocus(); Connections { target: plasmoid onExpandedChanged: { if(menuItemsChanged()) { createButtons(); } if (!expanded) { switchToInitial(); } } } } // tabBar MouseArea { anchors.fill: tabBar property var oldPos: null hoverEnabled: root.switchTabsOnHover onExited: { // Reset so we switch immediately when MouseArea is entered // freshly, e.g. from the panel. oldPos = null; clickTimer.stop(); } onPositionChanged: { var button = tabBar.layout.childAt(mouse.x, mouse.y); if (!button || button.objectName != "KickoffButton") { clickTimer.stop(); return; } // Switch immediately when MouseArea was freshly entered, e.g. // from the panel. if (oldPos === null) { oldPos = Qt.point(mouse.x, mouse.y); clickTimer.stop(); button.clicked(); return; } var dx = (mouse.x - oldPos.x); var dy = (mouse.y - oldPos.y); // Check Manhattan length against drag distance to get a decent // pointer motion vector. if ((Math.abs(dx) + Math.abs(dy)) > Qt.styleHints.startDragDistance) { if (tabBar.currentTab != button) { var tabBarPos = mapToItem(tabBar, oldPos.x, oldPos.y); oldPos = Qt.point(mouse.x, mouse.y); var angleMouseMove = Math.atan2(dy, dx) * 180 / Math.PI; var angleToCornerA = 0; var angleToCornerB = 0; switch (plasmoid.location) { case PlasmaCore.Types.TopEdge: { angleToCornerA = Math.atan2(tabBar.height - tabBarPos.y, 0 - tabBarPos.x); angleToCornerB = Math.atan2(tabBar.height - tabBarPos.y, tabBar.width - tabBarPos.x); break; } case PlasmaCore.Types.LeftEdge: { angleToCornerA = Math.atan2(0 - tabBarPos.y, tabBar.width - tabBarPos.x); angleToCornerB = Math.atan2(tabBar.height - tabBarPos.y, tabBar.width - tabBarPos.x); break; } case PlasmaCore.Types.RightEdge: { angleToCornerA = Math.atan2(0 - tabBarPos.y, 0 - tabBarPos.x); angleToCornerB = Math.atan2(tabBar.height - tabBarPos.y, 0 - tabBarPos.x); break; } // PlasmaCore.Types.BottomEdge default: { angleToCornerA = Math.atan2(0 - tabBarPos.y, 0 - tabBarPos.x); angleToCornerB = Math.atan2(0 - tabBarPos.y, tabBar.width - tabBarPos.x); } } // Degrees are nicer to debug than radians. angleToCornerA = angleToCornerA * 180 / Math.PI; angleToCornerB = angleToCornerB * 180 / Math.PI; var lower = Math.min(angleToCornerA, angleToCornerB); var upper = Math.max(angleToCornerA, angleToCornerB); // If the motion vector is outside the angle range from oldPos to the // relevant tab bar corners, switch immediately. Otherwise start the // timer, which gets aborted should the pointer exit the the tab bar // early. var inRange = (lower < angleMouseMove == angleMouseMove < upper); // Mirror-flip. if (plasmoid.location == PlasmaCore.Types.RightEdge ? inRange : !inRange) { clickTimer.stop(); button.clicked(); return; } else { clickTimer.pendingButton = button; clickTimer.start(); } } else { oldPos = Qt.point(mouse.x, mouse.y); } } } onClicked: { clickTimer.stop(); var button = tabBar.layout.childAt(mouse.x, mouse.y); if (!button || button.objectName != "KickoffButton") { return; } button.clicked(); } Timer { id: clickTimer property Item pendingButton: null interval: 250 onTriggered: { if (pendingButton) { pendingButton.clicked(); } } } } Keys.forwardTo: [tabBar.layout] Keys.onPressed: { if (mainTabGroup.currentTab == applicationsPage) { if (event.key != Qt.Key_Tab) { root.state = "Applications"; } } switch(event.key) { case Qt.Key_Up: { currentView.decrementCurrentIndex(); event.accepted = true; break; } case Qt.Key_Down: { currentView.incrementCurrentIndex(); event.accepted = true; break; } case Qt.Key_Left: { if (header.input.focus && header.state == "query") { break; } if (!currentView.deactivateCurrentIndex()) { if (root.state == "Applications") { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; } root.state = "Normal" } event.accepted = true; break; } case Qt.Key_Right: { if (header.input.focus && header.state == "query") { break; } currentView.activateCurrentIndex(); event.accepted = true; break; } case Qt.Key_Tab: { root.state == "Applications" ? root.state = "Normal" : root.state = "Applications"; event.accepted = true; break; } case Qt.Key_Enter: case Qt.Key_Return: { currentView.activateCurrentIndex(1); event.accepted = true; break; } case Qt.Key_Escape: { if (header.state != "query") { plasmoid.expanded = false; } else { header.query = ""; } event.accepted = true; break; } case Qt.Key_Menu: { currentView.openContextMenu(); event.accepted = true; break; } default: if (!header.input.focus) { header.input.forceActiveFocus(); } } } states: [ State { name: "Normal" PropertyChanges { target: root Keys.forwardTo: [tabBar.layout] } PropertyChanges { target: tabBar //Set the opacity and NOT the visibility, as visibility is recursive //and this binding would be executed also on popup show/hide - //as recomended by the docs: http://doc.qt.io/qt-5/qml-qtquick-item.html#visible-prop + //as recommended by the docs: http://doc.qt.io/qt-5/qml-qtquick-item.html#visible-prop //plus, it triggers https://bugreports.qt.io/browse/QTBUG-66907 //in which a mousearea may think it's under the mouse while it isn't opacity: tabBar.count > 1 ? 1 : 0 } }, State { name: "Applications" PropertyChanges { target: root Keys.forwardTo: [root] } PropertyChanges { target: tabBar opacity: tabBar.count > 1 ? 1 : 0 } }, State { name: "Search" PropertyChanges { target: tabBar opacity: 0 } PropertyChanges { target: mainTabGroup currentTab: searchPage } PropertyChanges { target: root Keys.forwardTo: [root] } } ] // states function getButtonDefinition(name) { switch(name) { case "bookmark": return {id: "bookmarkButton", tab: favoritesPage, iconSource: "bookmarks", text: i18n("Favorites")}; case "application": return {id: "applicationButton", tab: applicationsPage, iconSource: "applications-other", text: i18n("Applications")}; case "computer": return {id: "computerButton", tab: systemPage, iconSource: pmSource.data["PowerDevil"] && pmSource.data["PowerDevil"]["Is Lid Present"] ? "computer-laptop" : "computer", text: i18n("Computer")}; case "used": return {id: "usedButton", tab: recentlyUsedPage, iconSource: "view-history", text: i18n("History")}; case "oftenUsed": return {id: "usedButton", tab: oftenUsedPage, iconSource: "office-chart-pie", text: i18n("Often Used")}; case "leave": return {id: "leaveButton", tab: leavePage, iconSource: "system-log-out", text: i18n("Leave")}; } } Component { id: kickoffButton KickoffButton {} } Component.onCompleted: { createButtons(); } function getEnabled(configuration) { var res = []; for(var i = 0; i < configuration.length; i++) { var confItemName = configuration[i].substring(0, configuration[i].indexOf(":")); var confItemEnabled = configuration[i].substring(configuration[i].length-1) == "t"; if(confItemEnabled) { res.push(confItemName); } } return res; } function createButtons() { configMenuItems = plasmoid.configuration.menuItems; var menuItems = getEnabled(plasmoid.configuration.menuItems); tabBar.count = menuItems.length // remove old menu items for(var i = tabBar.layout.children.length -1; i >= 0; i--) { if(tabBar.layout.children[i].objectName == "KickoffButton") { tabBar.layout.children[i].destroy(); } } for (var i = 0; i < menuItems.length; i++) { var props = getButtonDefinition(menuItems[i]); var button = kickoffButton.createObject(tabBar.layout, props); if(i == 0) { firstButton = button; switchToInitial(); } } } function menuItemsChanged() { if(configMenuItems.length != plasmoid.configuration.menuItems.length) { return true; } for(var i = 0; i < configMenuItems.length; i++) { if(configMenuItems[i] != plasmoid.configuration.menuItems[i]) { return true; } } return false; } } diff --git a/applets/kickoff/package/contents/ui/KickoffHighlight.qml b/applets/kickoff/package/contents/ui/KickoffHighlight.qml index cd97d2b7f..6331d7ae7 100644 --- a/applets/kickoff/package/contents/ui/KickoffHighlight.qml +++ b/applets/kickoff/package/contents/ui/KickoffHighlight.qml @@ -1,30 +1,30 @@ /* * Copyright 2014 Sebastian Kügler * * 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.components 2.0 as PlasmaComponents Item { PlasmaComponents.Highlight { anchors { fill: parent leftMargin: units.gridUnit rightMargin: units.gridUnit } } -} \ No newline at end of file +} diff --git a/applets/kimpanel/backend/ibus/ibus15/gtkaccelparse_p.h b/applets/kimpanel/backend/ibus/ibus15/gtkaccelparse_p.h index 8d179502c..69f6bc1fb 100644 --- a/applets/kimpanel/backend/ibus/ibus15/gtkaccelparse_p.h +++ b/applets/kimpanel/backend/ibus/ibus15/gtkaccelparse_p.h @@ -1,80 +1,80 @@ /* GDK - The GIMP Drawing Kit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * Copyright (C) 2005, 2006, 2007, 2009 GNOME Foundation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ -#ifndef GDKACCELPARSE_P_H -#define GDKACCELPARSE_P_H +#ifndef GTKACCELPARSE_P_H +#define GTKACCELPARSE_P_H #include G_BEGIN_DECLS typedef enum { GDK_SHIFT_MASK = 1 << 0, GDK_LOCK_MASK = 1 << 1, GDK_CONTROL_MASK = 1 << 2, GDK_MOD1_MASK = 1 << 3, GDK_MOD2_MASK = 1 << 4, GDK_MOD3_MASK = 1 << 5, GDK_MOD4_MASK = 1 << 6, GDK_MOD5_MASK = 1 << 7, GDK_BUTTON1_MASK = 1 << 8, GDK_BUTTON2_MASK = 1 << 9, GDK_BUTTON3_MASK = 1 << 10, GDK_BUTTON4_MASK = 1 << 11, GDK_BUTTON5_MASK = 1 << 12, GDK_MODIFIER_RESERVED_13_MASK = 1 << 13, GDK_MODIFIER_RESERVED_14_MASK = 1 << 14, GDK_MODIFIER_RESERVED_15_MASK = 1 << 15, GDK_MODIFIER_RESERVED_16_MASK = 1 << 16, GDK_MODIFIER_RESERVED_17_MASK = 1 << 17, GDK_MODIFIER_RESERVED_18_MASK = 1 << 18, GDK_MODIFIER_RESERVED_19_MASK = 1 << 19, GDK_MODIFIER_RESERVED_20_MASK = 1 << 20, GDK_MODIFIER_RESERVED_21_MASK = 1 << 21, GDK_MODIFIER_RESERVED_22_MASK = 1 << 22, GDK_MODIFIER_RESERVED_23_MASK = 1 << 23, GDK_MODIFIER_RESERVED_24_MASK = 1 << 24, GDK_MODIFIER_RESERVED_25_MASK = 1 << 25, /* The next few modifiers are used by XKB, so we skip to the end. * Bits 15 - 25 are currently unused. Bit 29 is used internally. */ GDK_SUPER_MASK = 1 << 26, GDK_HYPER_MASK = 1 << 27, GDK_META_MASK = 1 << 28, GDK_MODIFIER_RESERVED_29_MASK = 1 << 29, GDK_RELEASE_MASK = 1 << 30, /* Combination of GDK_SHIFT_MASK..GDK_BUTTON5_MASK + GDK_SUPER_MASK + GDK_HYPER_MASK + GDK_META_MASK + GDK_RELEASE_MASK */ GDK_MODIFIER_MASK = 0x5c001fff } GdkModifierType; void _gtk_accelerator_parse (const gchar *accelerator, guint *accelerator_key, GdkModifierType *accelerator_mods); G_END_DECLS #endif // GDKACCELPARSE_P_H diff --git a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml index 1a6ab8304..d776f0195 100644 --- a/applets/taskmanager/package/contents/ui/ToolTipInstance.qml +++ b/applets/taskmanager/package/contents/ui/ToolTipInstance.qml @@ -1,487 +1,487 @@ /* * Copyright 2013 by Sebastian Kügler * Copyright 2014 by Martin Gräßlin * Copyright 2016 by Kai Uwe Broulik * Copyright 2017 by Roman Gilg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, 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 Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.6 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import QtQml.Models 2.2 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 as KQuickControlsAddons import org.kde.taskmanager 0.1 as TaskManager Column { property var submodelIndex property int flatIndex: isGroup && index != undefined ? index : 0 spacing: units.smallSpacing readonly property string mprisSourceName: mpris2Source.sourceNameForLauncherUrl(toolTipDelegate.launcherUrl, isGroup ? AppPid : pidParent) readonly property var playerData: mprisSourceName != "" ? mpris2Source.data[mprisSourceName] : 0 readonly property bool hasPlayer: !!mprisSourceName && !!playerData readonly property bool playing: hasPlayer && playerData.PlaybackStatus === "Playing" readonly property bool canControl: hasPlayer && playerData.CanControl readonly property bool canPlay: hasPlayer && playerData.CanPlay readonly property bool canPause: hasPlayer && playerData.CanPause readonly property bool canGoBack: hasPlayer && playerData.CanGoPrevious readonly property bool canGoNext: hasPlayer && playerData.CanGoNext readonly property bool canRaise: hasPlayer && playerData.CanRaise readonly property var currentMetadata: hasPlayer ? playerData.Metadata : ({}) readonly property string track: { var xesamTitle = currentMetadata["xesam:title"] if (xesamTitle) { return xesamTitle; } // if no track title is given, print out the file name var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : "" if (!xesamUrl) { return ""; } var lastSlashPos = xesamUrl.lastIndexOf('/') if (lastSlashPos < 0) { return ""; } var lastUrlPart = xesamUrl.substring(lastSlashPos + 1) return decodeURIComponent(lastUrlPart); } readonly property string artist: currentMetadata["xesam:artist"] || "" readonly property string albumArt: currentMetadata["mpris:artUrl"] || "" // // launcher icon + text labels + close button RowLayout { id: header Layout.minimumWidth: childrenRect.width Layout.maximumWidth: Layout.minimumWidth Layout.minimumHeight: childrenRect.height Layout.maximumHeight: Layout.minimumHeight anchors.horizontalCenter: parent.horizontalCenter // launcher icon PlasmaCore.IconItem { Layout.preferredWidth: units.iconSizes.medium Layout.preferredHeight: units.iconSizes.medium source: !isWin ? icon : "" animated: false usesPlasmaTheme: false visible: !isWin } // all textlabels Column { PlasmaExtras.Heading { level: 3 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: appName opacity: flatIndex == 0 textFormat: Text.PlainText visible: text !== "" } // window title PlasmaExtras.Heading { level: 5 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: generateTitle() textFormat: Text.PlainText opacity: 0.75 } // subtext PlasmaExtras.Heading { level: 6 width: isWin ? textWidth : undefined height: undefined maximumLineCount: 1 elide: Text.ElideRight text: isWin ? generateSubText() : "" textFormat: Text.PlainText opacity: 0.6 visible: text !== "" } } // Count badge. Badge { Layout.alignment: Qt.AlignRight | Qt.AlignTop height: units.iconSizes.smallMedium visible: flatIndex === 0 && smartLauncherCountVisible number: smartLauncherCount } // close button MouseArea { Layout.alignment: Qt.AlignRight | Qt.AlignTop height: units.iconSizes.smallMedium width: height visible: isWin acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: { backend.cancelHighlightWindows(); tasksModel.requestClose(submodelIndex); } PlasmaCore.IconItem { anchors.fill: parent active: parent.containsMouse source: "window-close" animated: false } } } // thumbnail container Item { id: thumbnail width: header.width // similar to 0.5625 = 1 / (16:9) as most screens are // round necessary, otherwise shadow mask for players has gap! height: Math.round(0.5 * width) anchors.horizontalCenter: parent.horizontalCenter visible: isWin Item { id: thumbnailSourceItem anchors.fill: parent readonly property bool isMinimized: isGroup ? IsMinimized == true : isMinimizedParent // TODO: this causes XCB error message when being visible the first time property int winId: isWin && windows[flatIndex] != undefined ? windows[flatIndex] : 0 PlasmaCore.WindowThumbnail { anchors.fill: parent visible: !albumArtImage.visible && !thumbnailSourceItem.isMinimized winId: thumbnailSourceItem.winId ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } Image { id: albumArtImage // also Image.Loading to prevent loading thumbnails just because the album art takes a split second to load readonly property bool available: status === Image.Ready || status === Image.Loading anchors.fill: parent sourceSize: Qt.size(parent.width, parent.height) asynchronous: true source: albumArt fillMode: Image.PreserveAspectCrop visible: available ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } // when minimized, we don't have a preview, so show the icon PlasmaCore.IconItem { anchors.fill: parent source: thumbnailSourceItem.isMinimized && !albumArtImage.visible ? icon : "" animated: false usesPlasmaTheme: false visible: valid ToolTipWindowMouseArea { anchors.fill: parent rootTask: parentTask modelIndex: submodelIndex winId: thumbnailSourceItem.winId } } } Loader { anchors.fill: thumbnail sourceComponent: hasPlayer ? playerControlsComp : undefined } Component { id: playerControlsComp Item { anchors.fill: parent // TODO: When could this really be the case? A not-launcher-task always has a window!? // if there's no window associated with this task, we might still be able to raise the player // MouseArea { // id: raisePlayerArea // anchors.fill: parent // visible: !isWin || !windows[0] && canRaise // onClicked: mpris2Source.raise(mprisSourceName) // } Item { id: playerControlsFrostedGlass anchors.fill: parent visible: false // OpacityMask would render it Rectangle { width: parent.width height: parent.height - playerControlsRow.height opacity: 0 } Rectangle { anchors.bottom: parent.bottom width: parent.width height: playerControlsRow.height color: theme.backgroundColor opacity: 0.8 } } OpacityMask { id: playerControlsOpacityMask anchors.fill: parent source: playerControlsFrostedGlass maskSource: thumbnailSourceItem } // prevent accidental click-through when a control is disabled MouseArea { anchors.fill: playerControlsRow } RowLayout { id: playerControlsRow anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom } width: parent.width spacing: 0 enabled: canControl ColumnLayout { Layout.fillWidth: true spacing: 0 PlasmaExtras.Heading { Layout.fillWidth: true level: 4 wrapMode: Text.NoWrap elide: Text.ElideRight text: track || "" } PlasmaExtras.Heading { Layout.fillWidth: true level: 5 wrapMode: Text.NoWrap elide: Text.ElideRight text: artist || "" } } MouseArea { height: units.iconSizes.smallMedium width: height enabled: canGoBack acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: mpris2Source.goPrevious(mprisSourceName) PlasmaCore.IconItem { anchors.fill: parent enabled: canGoBack active: parent.containsMouse source: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward" animated: false } } MouseArea { height: units.iconSizes.medium width: height enabled: playing ? canPause : canPlay acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: { if (!playing) { mpris2Source.play(mprisSourceName); } else { mpris2Source.pause(mprisSourceName); } } PlasmaCore.IconItem { anchors.fill: parent enabled: playing ? canPause : canPlay active: parent.containsMouse source: playing ? "media-playback-pause" : "media-playback-start" animated: false } } MouseArea { height: units.iconSizes.smallMedium width: height enabled: canGoNext acceptedButtons: Qt.LeftButton hoverEnabled: true onClicked: mpris2Source.goNext(mprisSourceName) PlasmaCore.IconItem { anchors.fill: parent enabled: canGoNext active: parent.containsMouse source: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward" animated: false } } } } } } function generateTitle() { if (!isWin) { return genericName != undefined ? genericName : ""; } var text; if (isGroup) { if (model.display == undefined) { return ""; } text = model.display.toString(); } else { text = displayParent; } // KWin appends increasing integers in between pointy brackets to otherwise equal window titles. // In this case save <#number> as counter and delete it at the end of text. var counter = text.match(/<\d+>\W*$/); text = text.replace(/\s*<\d+>\W*$/, ""); // Remove appName from the end of text. var appNameRegex = new RegExp(appName + "$", "i"); text = text.replace(appNameRegex, ""); text = text.replace(/\s*(?:-|—)*\s*$/, ""); // Add counter back at the end. if (counter != null) { if (text == "") { text = counter; } else { text = text + " " + counter; } } - // In case the window title had only redundant informations (i.e. appName), text is now empty. + // In case the window title had only redundant information (i.e. appName), text is now empty. // Add a hyphen to indicate that and avoid empty space. if (text == "") { text = "—"; } return text.toString(); } function generateSubText() { if (activitiesParent == undefined) { return ""; } var subTextEntries = []; var vd = isGroup ? VirtualDesktop : virtualDesktopParent; if (!plasmoid.configuration.showOnlyCurrentDesktop && virtualDesktopInfo.numberOfDesktops > 1 && (isGroup ? IsOnAllVirtualDesktops : isOnAllVirtualDesktopsParent) !== true && vd != -1 && vd != undefined && virtualDesktopInfo.desktopNames[vd - 1] != undefined) { subTextEntries.push(i18n("On %1", virtualDesktopInfo.desktopNames[vd - 1])); } var act = isGroup ? Activities : activitiesParent; if (act == undefined) { return subTextEntries.join("\n"); } if (act.length == 0 && activityInfo.numberOfRunningActivities > 1) { subTextEntries.push(i18nc("Which virtual desktop a window is currently on", "Available on all activities")); } else if (act.length > 0) { var activityNames = []; for (var i = 0; i < act.length; i++) { var activity = act[i]; var activityName = activityInfo.activityName(act[i]); if (activityName == "") { continue; } if (plasmoid.configuration.showOnlyCurrentActivity) { if (activity != activityInfo.currentActivity) { activityNames.push(activityName); } } else if (activity != activityInfo.currentActivity) { activityNames.push(activityName); } } if (plasmoid.configuration.showOnlyCurrentActivity) { if (activityNames.length > 0) { subTextEntries.push(i18nc("Activities a window is currently on (apart from the current one)", "Also available on %1", activityNames.join(", "))); } } else if (activityNames.length > 0) { subTextEntries.push(i18nc("Which activities a window is currently on", "Available on %1", activityNames.join(", "))); } } return subTextEntries.join("\n"); } } diff --git a/applets/taskmanager/plugin/backend.cpp b/applets/taskmanager/plugin/backend.cpp index 881dc5632..ebfe99f61 100644 --- a/applets/taskmanager/plugin/backend.cpp +++ b/applets/taskmanager/plugin/backend.cpp @@ -1,586 +1,586 @@ /*************************************************************************** * Copyright (C) 2012-2016 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 "backend.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; Backend::Backend(QObject* parent) : QObject(parent) , m_panelWinId(0) , m_highlightWindows(false) , m_actionGroup(new QActionGroup(this)) { } Backend::~Backend() { } QQuickItem *Backend::taskManagerItem() const { return m_taskManagerItem; } void Backend::setTaskManagerItem(QQuickItem* item) { if (item != m_taskManagerItem) { m_taskManagerItem = item; emit taskManagerItemChanged(); } } QQuickItem *Backend::toolTipItem() const { return m_toolTipItem; } void Backend::setToolTipItem(QQuickItem *item) { if (item != m_toolTipItem) { m_toolTipItem = item; connect(item, SIGNAL(windowChanged(QQuickWindow*)), this, SLOT(toolTipWindowChanged(QQuickWindow*))); emit toolTipItemChanged(); } } QQuickWindow *Backend::groupDialog() const { return m_groupDialog; } void Backend::setGroupDialog(QQuickWindow *dialog) { if (dialog != m_groupDialog) { m_groupDialog = dialog; emit groupDialogChanged(); } } bool Backend::highlightWindows() const { return m_highlightWindows; } void Backend::setHighlightWindows(bool highlight) { if (highlight != m_highlightWindows) { m_highlightWindows = highlight; updateWindowHighlight(); emit highlightWindowsChanged(); } } QUrl Backend::tryDecodeApplicationsUrl(const QUrl &launcherUrl) { if (launcherUrl.isValid() && launcherUrl.scheme() == QStringLiteral("applications")) { const KService::Ptr service = KService::serviceByMenuId(launcherUrl.path()); if (service) { return QUrl::fromLocalFile(service->entryPath()); } } return launcherUrl; } QVariantList Backend::jumpListActions(const QUrl &launcherUrl, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; KDesktopFile desktopFile(desktopEntryUrl.toLocalFile()); const QStringList &jumpListActions = desktopFile.readActions(); const QLatin1String kde("KDE"); foreach (const QString &actionName, jumpListActions) { const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); if (!actionGroup.isValid() || !actionGroup.exists()) { continue; } const QStringList ¬ShowIn = actionGroup.readXdgListEntry(QStringLiteral("NotShowIn")); if (notShowIn.contains(kde)) { continue; } const QStringList &onlyShowIn = actionGroup.readXdgListEntry(QStringLiteral("OnlyShowIn")); if (!onlyShowIn.isEmpty() && !onlyShowIn.contains(kde)) { continue; } const QString &name = actionGroup.readEntry(QStringLiteral("Name")); const QString &exec = actionGroup.readEntry(QStringLiteral("Exec")); if (name.isEmpty() || exec.isEmpty()) { continue; } QAction *action = new QAction(parent); action->setText(name); action->setIcon(QIcon::fromTheme(actionGroup.readEntry("Icon"))); action->setProperty("exec", exec); // so we can show the proper application name and icon when it launches action->setProperty("applicationName", desktopFile.readName()); action->setProperty("applicationIcon", desktopFile.readIcon()); connect(action, &QAction::triggered, this, &Backend::handleJumpListAction); actions << QVariant::fromValue(action); } return actions; } QVariantList Backend::placesActions(const QUrl &launcherUrl, bool showAllPlaces, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; KDesktopFile desktopFile(desktopEntryUrl.toLocalFile()); // Since we can't have dynamic jump list actions, at least add the user's "Places" for file managers. const QStringList &categories = desktopFile.desktopGroup().readXdgListEntry(QStringLiteral("Categories")); if (!categories.contains(QLatin1String("FileManager"))) { return actions; } QString previousGroup; QMenu *subMenu = nullptr; QScopedPointer placesModel(new KFilePlacesModel()); for (int i = 0; i < placesModel->rowCount(); ++i) { QModelIndex idx = placesModel->index(i, 0); if (placesModel->isHidden(idx)) { continue; } const QString &title = idx.data(Qt::DisplayRole).toString(); const QIcon &icon = idx.data(Qt::DecorationRole).value(); const QUrl &url = idx.data(KFilePlacesModel::UrlRole).toUrl(); QAction *placeAction = new QAction(icon, title, parent); connect(placeAction, &QAction::triggered, this, [this, url, desktopEntryUrl] { KService::Ptr service = KService::serviceByDesktopPath(desktopEntryUrl.toLocalFile()); if (!service) { return; } KRun::runService(*service, {url}, QApplication::activeWindow()); }); const QString &groupName = idx.data(KFilePlacesModel::GroupRole).toString(); if (previousGroup.isEmpty()) { // Skip first group heading. previousGroup = groupName; } // Put all subsequent categories into a submenu. if (previousGroup != groupName) { QAction *subMenuAction = new QAction(groupName, parent); subMenu = new QMenu(); // Cannot parent a QMenu to a QAction, need to delete it manually. connect(parent, &QObject::destroyed, subMenu, &QObject::deleteLater); subMenuAction->setMenu(subMenu); actions << QVariant::fromValue(subMenuAction); previousGroup = groupName; } if (subMenu) { subMenu->addAction(placeAction); } else { actions << QVariant::fromValue(placeAction); } } // There is nothing more frustrating than having a "More" entry that ends up showing just one or two // additional entries. Therefore we truncate to max. 5 entries only if there are more than 7 in total. if (!showAllPlaces && actions.count() > 7) { const int totalActionCount = actions.count(); while (actions.count() > 5) { actions.removeLast(); } QAction *action = new QAction(parent); action->setText(i18ncp("Show all user Places", "%1 more Place", "%1 more Places", totalActionCount - actions.count())); connect(action, &QAction::triggered, this, &Backend::showAllPlaces); actions << QVariant::fromValue(action); } return actions; } QVariantList Backend::recentDocumentActions(const QUrl &launcherUrl, QObject *parent) { if (!parent) { return QVariantList(); } QUrl desktopEntryUrl = tryDecodeApplicationsUrl(launcherUrl); if (!desktopEntryUrl.isValid() || !desktopEntryUrl.isLocalFile() || !KDesktopFile::isDesktopFile(desktopEntryUrl.toLocalFile())) { return QVariantList(); } QVariantList actions; QString desktopName = desktopEntryUrl.fileName(); QString storageId = desktopName; if (storageId.endsWith(QLatin1String(".desktop"))) { storageId = storageId.left(storageId.length() - 8); } auto query = UsedResources | RecentlyUsedFirst | Agent(storageId) | Type::any() | Activity::current() | Url::file(); ResultSet results(query); ResultSet::const_iterator resultIt = results.begin(); int actionCount = 0; while (actionCount < 5 && resultIt != results.end()) { const QString resource = (*resultIt).resource(); const QUrl url(resource); if (!url.isValid()) { continue; } const KFileItem fileItem(url); if (!fileItem.isFile()) { continue; } QAction *action = new QAction(parent); action->setText(url.fileName()); action->setIcon(QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme(QStringLiteral("unknown")))); action->setProperty("agent", storageId); action->setProperty("entryPath", desktopEntryUrl); action->setData(resource); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); ++resultIt; ++actionCount; } if (actionCount > 0) { QAction *action = new QAction(parent); action->setText(i18n("Forget Recent Documents")); action->setProperty("agent", storageId); connect(action, &QAction::triggered, this, &Backend::handleRecentDocumentAction); actions << QVariant::fromValue(action); } return actions; } void Backend::toolTipWindowChanged(QQuickWindow *window) { Q_UNUSED(window) updateWindowHighlight(); } void Backend::handleJumpListAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } KRun::run(action->property("exec").toString(), {}, nullptr, action->property("applicationName").toString(), action->property("applicationIcon").toString()); } void Backend::handleRecentDocumentAction() const { const QAction *action = qobject_cast(sender()); if (!action) { return; } const QString agent = action->property("agent").toString(); if (agent.isEmpty()) { return; } const QString desktopPath = action->property("entryPath").toUrl().toLocalFile(); const QString resource = action->data().toString(); if (desktopPath.isEmpty() || resource.isEmpty()) { auto query = UsedResources | Agent(agent) | Type::any() | Activity::current() | Url::file(); KAStats::forgetResources(query); return; } KService::Ptr service = KService::serviceByDesktopPath(desktopPath); qDebug() << service; if (!service) { return; } KRun::runService(*service, QList() << QUrl(resource), QApplication::activeWindow()); } void Backend::setActionGroup(QAction *action) const { if (action) { action->setActionGroup(m_actionGroup); } } QRect Backend::globalRect(QQuickItem *item) const { if (!item || !item->window()) { return QRect(); } QRect iconRect(item->x(), item->y(), item->width(), item->height()); iconRect.moveTopLeft(item->parentItem()->mapToScene(iconRect.topLeft()).toPoint()); iconRect.moveTopLeft(item->window()->mapToGlobal(iconRect.topLeft())); return iconRect; } void Backend::ungrabMouse(QQuickItem *item) const { - //this is a workaround where Qt will fail to realise a mouse has been released + //this is a workaround where Qt will fail to realize 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 = [item]() { if (item && item->window() && item->window()->mouseGrabberItem()) { item->window()->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(QString::fromLatin1(qVersion())) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, item, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround } bool Backend::canPresentWindows() const { return (KWindowSystem::compositingActive() && KWindowEffects::isEffectAvailable(KWindowEffects::PresentWindowsGroup)); } void Backend::presentWindows(const QVariant &_winIds) { if (!m_taskManagerItem || !m_taskManagerItem->window()) { return; } QList winIds; const QVariantList &_winIdsList = _winIds.toList(); foreach(const QVariant &_winId, _winIdsList) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { winIds.append(winId); } } if (!winIds.count()) { return; } if (m_windowsToHighlight.count()) { m_windowsToHighlight.clear(); updateWindowHighlight(); } KWindowEffects::presentWindows(m_taskManagerItem->window()->winId(), winIds); } bool Backend::isApplication(const QUrl &url) const { if (!url.isValid() || !url.isLocalFile()) { return false; } const QString &localPath = url.toLocalFile(); if (!KDesktopFile::isDesktopFile(localPath)) { return false; } KDesktopFile desktopFile(localPath); return desktopFile.hasApplicationType(); } QList Backend::jsonArrayToUrlList(const QJsonArray &array) const { QList urls; urls.reserve(array.count()); for (auto it = array.constBegin(), end = array.constEnd(); it != end; ++it) { urls << QUrl(it->toString()); } return urls; } void Backend::cancelHighlightWindows() { m_windowsToHighlight.clear(); updateWindowHighlight(); } void Backend::windowsHovered(const QVariant &_winIds, bool hovered) { m_windowsToHighlight.clear(); if (hovered) { const QVariantList &winIds = _winIds.toList(); foreach(const QVariant &_winId, winIds) { bool ok = false; qlonglong winId = _winId.toLongLong(&ok); if (ok) { m_windowsToHighlight.append(winId); } } } updateWindowHighlight(); } void Backend::updateWindowHighlight() { if (!m_highlightWindows) { if (m_panelWinId) { KWindowEffects::highlightWindows(m_panelWinId, QList()); m_panelWinId = 0; } return; } if (m_taskManagerItem && m_taskManagerItem->window()) { m_panelWinId = m_taskManagerItem->window()->winId(); } else { return; } QList windows = m_windowsToHighlight; if (windows.count() && m_toolTipItem && m_toolTipItem->window()) { windows.append(m_toolTipItem->window()->winId()); } if (windows.count() && m_groupDialog) { windows.append(m_groupDialog->winId()); } KWindowEffects::highlightWindows(m_panelWinId, windows); } diff --git a/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp index d06b0b1bc..23d85eade 100644 --- a/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp +++ b/applets/taskmanager/plugin/smartlaunchers/smartlauncherbackend.cpp @@ -1,353 +1,353 @@ /*************************************************************************** * Copyright (C) 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) 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 "smartlauncherbackend.h" #include #include #include #include #include #include #include #include using namespace SmartLauncher; Backend::Backend(QObject *parent) : QObject(parent) , m_watcher(new QDBusServiceWatcher(this)) , m_dataEngineConsumer(new Plasma::DataEngineConsumer) , m_dataEngine(m_dataEngineConsumer->dataEngine(QStringLiteral("applicationjobs"))) { m_available = setupUnity(); m_available = setupApplicationJobs() || m_available; } Backend::~Backend() { delete m_dataEngineConsumer; } bool Backend::setupUnity() { auto sessionBus = QDBusConnection::sessionBus(); if (!sessionBus.connect({}, {}, QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update"), this, SLOT(update(QString,QMap)))) { qWarning() << "failed to register Update signal"; return false; } if (!sessionBus.registerObject(QStringLiteral("/Unity"), this)) { qWarning() << "Failed to register unity object"; return false; } if (!sessionBus.registerService(QStringLiteral("com.canonical.Unity"))) { qWarning() << "Failed to register unity service"; return false; } KConfigGroup grp(KSharedConfig::openConfig(QStringLiteral("taskmanagerrulesrc")), QStringLiteral("Unity Launcher Mapping")); foreach (const QString &key, grp.keyList()) { const QString &value = grp.readEntry(key, QString()); if (value.isEmpty()) { continue; } m_unityMappingRules.insert(key, value); } return true; } bool Backend::setupApplicationJobs() { if (!m_dataEngine->isValid()) { qWarning() << "Failed to setup application jobs, data engine is not valid"; return false; } const QStringList &sources = m_dataEngine->sources(); for (const QString &source : sources) { onApplicationJobAdded(source); } connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &Backend::onApplicationJobAdded); connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &Backend::onApplicationJobRemoved); return true; } bool Backend::available() const { return m_available; } bool Backend::hasLauncher(const QString &storageId) const { return m_launchers.contains(storageId); } int Backend::count(const QString &uri) const { return m_launchers.value(uri).count; } bool Backend::countVisible(const QString &uri) const { return m_launchers.value(uri).countVisible; } int Backend::progress(const QString &uri) const { return m_launchers.value(uri).progress; } bool Backend::progressVisible(const QString &uri) const { return m_launchers.value(uri).progressVisible; } bool Backend::urgent(const QString &uri) const { return m_launchers.value(uri).urgent; } QHash Backend::unityMappingRules() const { return m_unityMappingRules; } void Backend::update(const QString &uri, const QMap &properties) { Q_ASSERT(calledFromDBus()); QString storageId; auto foundStorageId = m_launcherUrlToStorageId.constFind(uri); if (foundStorageId == m_launcherUrlToStorageId.constEnd()) { // we don't know this one, register // According to Unity Launcher API documentation applications *should* send along their // desktop file name with application:// prefix const QString applicationSchemePrefix = QStringLiteral("application://"); QString normalizedUri = uri; if (normalizedUri.startsWith(applicationSchemePrefix)) { normalizedUri = uri.mid(applicationSchemePrefix.length()); } KService::Ptr service = KService::serviceByStorageId(normalizedUri); if (!service) { qWarning() << "Failed to find service for Unity Launcher" << uri; return; } storageId = service->storageId(); m_launcherUrlToStorageId.insert(uri, storageId); m_dbusServiceToLauncherUrl.insert(message().service(), uri); m_watcher->addWatchedService(message().service()); } else { storageId = *foundStorageId; } auto foundEntry = m_launchers.find(storageId); if (foundEntry == m_launchers.end()) { // we don't have it yet, create a new Entry Entry entry; foundEntry = m_launchers.insert(storageId, entry); } auto propertiesEnd = properties.constEnd(); auto foundCount = properties.constFind(QStringLiteral("count")); if (foundCount != propertiesEnd) { qint64 newCount = foundCount->toLongLong(); // 2 billion unread emails ought to be enough for anybody if (newCount < std::numeric_limits::max()) { int saneCount = static_cast(newCount); if (saneCount != foundEntry->count) { foundEntry->count = saneCount; emit countChanged(storageId, saneCount); } } } updateLauncherProperty(storageId, properties, QStringLiteral("count"), &foundEntry->count, &Backend::countChanged); updateLauncherProperty(storageId, properties, QStringLiteral("count-visible"), &foundEntry->countVisible, &Backend::countVisibleChanged); - // the API gives us progress as 0..1 double but we'll use percent to avoid unneccessary + // the API gives us progress as 0..1 double but we'll use percent to avoid unnecessary // changes when it just changed a fraction of a percent, hence not using our fancy updateLauncherProperty method auto foundProgress = properties.constFind(QStringLiteral("progress")); if (foundProgress != propertiesEnd) { int newProgress = qRound(foundProgress->toDouble() * 100); if (newProgress != foundEntry->progress) { foundEntry->progress = newProgress; emit progressChanged(storageId, newProgress); } } updateLauncherProperty(storageId, properties, QStringLiteral("progress-visible"), &foundEntry->progressVisible, &Backend::progressVisibleChanged); updateLauncherProperty(storageId, properties, QStringLiteral("urgent"), &foundEntry->urgent, &Backend::urgentChanged); } void Backend::onServiceUnregistered(const QString &service) { const QString &launcherUrl = m_dbusServiceToLauncherUrl.take(service); if (launcherUrl.isEmpty()) { return; } const QString &storageId = m_launcherUrlToStorageId.take(launcherUrl); if (storageId.isEmpty()) { return; } m_launchers.remove(storageId); emit launcherRemoved(storageId); } void Backend::onApplicationJobAdded(const QString &source) { m_dataEngine->connectSource(source, this); } void Backend::onApplicationJobRemoved(const QString &source) { m_dataEngine->disconnectSource(source, this); const QString &storageId = m_dataSourceToStorageId.take(source); if (storageId.isEmpty()) { return; } // remove job, calculate new percentage, or remove launcher if gone altogether auto &jobs = m_storageIdToJobs[storageId]; jobs.removeOne(source); if (jobs.isEmpty()) { m_storageIdToJobs.remove(storageId); } m_jobProgress.remove(source); auto foundEntry = m_launchers.find(storageId); if (foundEntry == m_launchers.end()) { qWarning() << "Cannot remove application job" << source << "as we don't know" << storageId; return; } updateApplicationJobPercent(storageId, &*foundEntry); if (!foundEntry->progressVisible && !foundEntry->progress) { // no progress anymore whatsoever, remove entire launcher m_launchers.remove(storageId); emit launcherRemoved(storageId); } } void Backend::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) { QString storageId; auto foundStorageId = m_dataSourceToStorageId.constFind(sourceName); if (foundStorageId == m_dataSourceToStorageId.constEnd()) { // we don't know this one, register QString appName = data.value(QStringLiteral("appName")).toString(); if (appName.isEmpty()) { qWarning() << "Application jobs got update for" << sourceName << "without app name"; return; } KService::Ptr service = KService::serviceByStorageId(appName); if (!service) { appName.prepend(QLatin1String("org.kde.")); // HACK try to find a service with org.kde. notation service = KService::serviceByStorageId(appName); if (!service) { qWarning() << "Could not find service for job" << sourceName << "with app name" << appName; return; } } storageId = service->storageId(); m_dataSourceToStorageId.insert(sourceName, storageId); } else { storageId = *foundStorageId; } auto foundEntry = m_launchers.find(storageId); if (foundEntry == m_launchers.end()) { // we don't have it yet, create new Entry Entry entry; foundEntry = m_launchers.insert(storageId, entry); } int percent = data.value(QStringLiteral("percentage"), 0).toInt(); // setup everything and calculate new percentage auto &jobs = m_storageIdToJobs[storageId]; if (!jobs.contains(sourceName)) { jobs.append(sourceName); } m_jobProgress.insert(sourceName, percent); // insert() overrides if exist updateApplicationJobPercent(storageId, &*foundEntry); } void Backend::updateApplicationJobPercent(const QString &storageId, Entry *entry) { // basically get all jobs for the given storageId and calculate an average progress const auto &jobs = m_storageIdToJobs.value(storageId); qreal jobCount = jobs.count(); int totalProgress = 0; for (const QString &job : jobs) { totalProgress += m_jobProgress.value(job, 0); } int progress = 0; if (jobCount > 0) { progress = qRound(totalProgress / jobCount); } bool visible = (jobCount > 0); if (entry->count != jobCount) { entry->count = jobCount; emit countChanged(storageId, jobCount); } if (entry->countVisible != visible) { entry->countVisible = visible; emit countVisibleChanged(storageId, visible); } if (entry->progress != progress) { entry->progress = progress; emit progressChanged(storageId, progress); } if (entry->progressVisible != visible) { entry->progressVisible = visible; emit progressVisibleChanged(storageId, visible); } } diff --git a/attica-kde/kdeplugin/kdeplatformdependent.cpp b/attica-kde/kdeplugin/kdeplatformdependent.cpp index 36dff5c2a..ab10a96ee 100644 --- a/attica-kde/kdeplugin/kdeplatformdependent.cpp +++ b/attica-kde/kdeplugin/kdeplatformdependent.cpp @@ -1,264 +1,263 @@ /* This file is part of KDE. Copyright (c) 2009 Eckhart Wörner Copyright (c) 2010 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kdeplatformdependent.h" #include "attica_plugin_debug.h" #include -#include "attica_plugin_debug.h" #include #include #include #include #include #include #include using namespace Attica; KdePlatformDependent::KdePlatformDependent() : m_config(KSharedConfig::openConfig(QStringLiteral("atticarc"))), m_accessManager(nullptr), m_wallet(nullptr) { // FIXME: Investigate how to not leak this instance witohut crashing. m_accessManager = new QNetworkAccessManager(nullptr); const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QStringLiteral("/attica"); QNetworkDiskCache *cache = new QNetworkDiskCache(m_accessManager); QStorageInfo storageInfo(cacheDir); cache->setCacheDirectory(cacheDir); cache->setMaximumCacheSize(storageInfo.bytesTotal() / 1000); m_accessManager->setCache(cache); } KdePlatformDependent::~KdePlatformDependent() { delete m_wallet; } bool KdePlatformDependent::openWallet(bool force) { if (m_wallet) { return true; } QString networkWallet = KWallet::Wallet::NetworkWallet(); // if not forced, or the folder doesn't exist, don't try to open the wallet if (force || (!KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")))) { m_wallet = KWallet::Wallet::openWallet(networkWallet, 0); } if (m_wallet) { m_wallet->createFolder(QStringLiteral("Attica")); m_wallet->setFolder(QStringLiteral("Attica")); return true; } return false; } QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, const QByteArray& data) { return m_accessManager->post(removeAuthFromRequest(request), data); } QNetworkReply* KdePlatformDependent::post(const QNetworkRequest& request, QIODevice* data) { return m_accessManager->post(removeAuthFromRequest(request), data); } QNetworkReply* KdePlatformDependent::get(const QNetworkRequest& request) { return m_accessManager->get(removeAuthFromRequest(request)); } QNetworkRequest KdePlatformDependent::removeAuthFromRequest(const QNetworkRequest& request) { const QStringList noauth = { QStringLiteral("no-auth-prompt"), QStringLiteral("true") }; QNetworkRequest notConstReq = const_cast(request); notConstReq.setAttribute(QNetworkRequest::User, noauth); return notConstReq; } bool KdePlatformDependent::saveCredentials(const QUrl& baseUrl, const QString& user, const QString& password) { m_passwords[baseUrl.toString()] = qMakePair(user, password); if (!m_wallet && !openWallet(true)) { if (KMessageBox::warningContinueCancel(nullptr, i18n("Should the password be stored in the configuration file? This is unsafe.") , i18n("Social Desktop Configuration")) == KMessageBox::Cancel) { return false; } // use kconfig KConfigGroup group(m_config, baseUrl.toString()); group.writeEntry("user", user); group.writeEntry("password", KStringHandler::obscure(password)); qCDebug(ATTICA_PLUGIN_LOG) << "Saved credentials in KConfig"; return true; } // Remove the entry when user name is empty if (user.isEmpty()) { m_wallet->removeEntry(baseUrl.toString()); return true; } const QMap entries = { { QStringLiteral("user"), user }, { QStringLiteral("password"), password } }; qCDebug(ATTICA_PLUGIN_LOG) << "Saved credentials in KWallet"; return !m_wallet->writeMap(baseUrl.toString(), entries); } bool KdePlatformDependent::hasCredentials(const QUrl& baseUrl) const { if (m_passwords.contains(baseUrl.toString())) { return true; } QString networkWallet = KWallet::Wallet::NetworkWallet(); if (!KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")) && !KWallet::Wallet::keyDoesNotExist(networkWallet, QStringLiteral("Attica"), baseUrl.toString())) { qCDebug(ATTICA_PLUGIN_LOG) << "Found credentials in KWallet"; return true; } KConfigGroup group(m_config, baseUrl.toString()); const QString user = group.readEntry("user", QString()); qCDebug(ATTICA_PLUGIN_LOG) << "Credentials found:" << !user.isEmpty(); return !user.isEmpty(); } bool KdePlatformDependent::loadCredentials(const QUrl& baseUrl, QString& user, QString& password) { QString networkWallet = KWallet::Wallet::NetworkWallet(); if (KWallet::Wallet::folderDoesNotExist(networkWallet, QStringLiteral("Attica")) && KWallet::Wallet::keyDoesNotExist(networkWallet, QStringLiteral("Attica"), baseUrl.toString())) { // use KConfig KConfigGroup group(m_config, baseUrl.toString()); user = group.readEntry("user", QString()); password = KStringHandler::obscure(group.readEntry("password", QString())); if (!user.isEmpty()) { qCDebug(ATTICA_PLUGIN_LOG) << "Successfully loaded credentials from kconfig"; m_passwords[baseUrl.toString()] = qMakePair(user, password); return true; } return false; } if (!m_wallet && !openWallet(true)) { return false; } QMap entries; if (m_wallet->readMap(baseUrl.toString(), entries) != 0) { return false; } user = entries.value(QStringLiteral("user")); password = entries.value(QStringLiteral("password")); qCDebug(ATTICA_PLUGIN_LOG) << "Successfully loaded credentials."; m_passwords[baseUrl.toString()] = qMakePair(user, password); return true; } bool Attica::KdePlatformDependent::askForCredentials(const QUrl& baseUrl, QString& user, QString& password) { Q_UNUSED(baseUrl); Q_UNUSED(user); Q_UNUSED(password); return false; } QList KdePlatformDependent::getDefaultProviderFiles() const { KConfigGroup group(m_config, "General"); QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("http://download.kde.org/ocs/providers.xml"))); QList paths; foreach (const QString& pathString, pathStrings) { paths.append(QUrl(pathString)); } qCDebug(ATTICA_PLUGIN_LOG) << "Loaded paths from config:" << paths; return paths; } void KdePlatformDependent::addDefaultProviderFile(const QUrl& url) { KConfigGroup group(m_config, "General"); QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("http://download.kde.org/ocs/providers.xml"))); QString urlString = url.toString(); if(!pathStrings.contains(urlString)) { pathStrings.append(urlString); group.writeEntry("providerFiles", pathStrings); group.sync(); qCDebug(ATTICA_PLUGIN_LOG) << "wrote providers: " << pathStrings; } } void KdePlatformDependent::removeDefaultProviderFile(const QUrl& url) { KConfigGroup group(m_config, "General"); QStringList pathStrings = group.readPathEntry("providerFiles", QStringList(QStringLiteral("http://download.kde.org/ocs/providers.xml"))); pathStrings.removeAll(url.toString()); group.writeEntry("providerFiles", pathStrings); } void KdePlatformDependent::enableProvider(const QUrl& baseUrl, bool enabled) const { KConfigGroup group(m_config, "General"); QStringList pathStrings = group.readPathEntry("disabledProviders", QStringList()); if (enabled) { pathStrings.removeAll(baseUrl.toString()); } else { if (!pathStrings.contains(baseUrl.toString())) { pathStrings.append(baseUrl.toString()); } } group.writeEntry("disabledProviders", pathStrings); group.sync(); } bool KdePlatformDependent::isEnabled(const QUrl& baseUrl) const { KConfigGroup group(m_config, "General"); return !group.readPathEntry("disabledProviders", QStringList()).contains(baseUrl.toString()); } QNetworkAccessManager* Attica::KdePlatformDependent::nam() { return m_accessManager; } // TODO: re-enable, see http://community.kde.org/Frameworks/Porting_Notes // Q_EXPORT_PLUGIN2(attica_kde, Attica::KdePlatformDependent) diff --git a/config-X11.h.cmake b/config-X11.h.cmake index ee5183a63..60286dd63 100644 --- a/config-X11.h.cmake +++ b/config-X11.h.cmake @@ -1,44 +1,44 @@ /* Define if you have the XRandR extension */ #cmakedefine HAVE_XRANDR 1 /* Define if you have the XDamage extension */ #cmakedefine HAVE_XDAMAGE 1 /* Define if you have the XKB extension */ #cmakedefine HAVE_XKB 1 /* Define if you have the Xinerama extension */ #cmakedefine HAVE_XINERAMA 1 /* Define if you have the XSHM (MIT SHM) extension */ #cmakedefine HAVE_XSHM 1 /* Define if you have the XComposite extension */ #cmakedefine HAVE_XCOMPOSITE 1 /* Define to 1 if you have Xcursor */ #cmakedefine HAVE_XCURSOR 1 /* Define if you have the xf86misc extension */ #cmakedefine HAVE_XF86MISC 1 /* Define if you have the XFixes extension */ #cmakedefine HAVE_XFIXES 1 /* Define if you have the XTest extension */ #cmakedefine HAVE_XTEST 1 /* Define if your system has XRender support */ #cmakedefine HAVE_XRENDER 1 /* Define if you have OpenGL */ #cmakedefine HAVE_OPENGL 1 /* Define if you have the XSync extension */ #cmakedefine HAVE_XSYNC 1 /* Define if you have XRandR 1.3 */ #cmakedefine HAS_RANDR_1_3 1 /* Define if you have X11 at all */ -#define HAVE_X11 ${X11_FOUND} \ No newline at end of file +#define HAVE_X11 ${X11_FOUND} diff --git a/containments/desktop/package/contents/ui/FolderView.qml b/containments/desktop/package/contents/ui/FolderView.qml index 85b2e152b..ad362b97a 100644 --- a/containments/desktop/package/contents/ui/FolderView.qml +++ b/containments/desktop/package/contents/ui/FolderView.qml @@ -1,1370 +1,1370 @@ /*************************************************************************** * 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.4 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.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.private.desktopcontainment.folder 0.1 as Folder import "code/FolderTools.js" as FolderTools FocusScope { id: main signal pressed property QtObject model: dir property Item rubberBand: null property alias isRootView: gridView.isRootView property alias currentIndex: gridView.currentIndex property alias url: dir.url property alias positions: positioner.positions property alias errorString: dir.errorString property alias dragging: dir.dragging property alias locked: dir.locked property alias sortMode: dir.sortMode property alias filterMode: dir.filterMode property alias filterPattern: dir.filterPattern property alias filterMimeTypes: dir.filterMimeTypes property alias flow: gridView.flow property alias layoutDirection: gridView.layoutDirection property alias cellWidth: gridView.cellWidth property alias cellHeight: gridView.cellHeight property alias overflowing: gridView.overflowing property alias scrollLeft: gridView.scrollLeft property alias scrollRight: gridView.scrollRight property alias scrollUp: gridView.scrollUp property alias scrollDown: gridView.scrollDown property alias hoveredItem: gridView.hoveredItem property var history: [] property Item backButton: null property var dialog: null property Item editor: null function rename() { if (gridView.currentIndex != -1) { if (!editor) { editor = editorComponent.createObject(listener); } editor.targetItem = gridView.currentItem; } } function cancelRename() { if (editor) { editor.targetItem = null; } } function linkHere(sourceUrl) { dir.linkHere(sourceUrl); } function handleDragMove(x, y) { var child = childAt(x, y); if (child != null && child == backButton) { hoveredItem = null; backButton.handleDragMove(); } else { if (backButton && backButton.containsDrag) { backButton.endDragMove(); } var pos = mapToItem(gridView.contentItem, x, y); var item = gridView.itemAt(pos.x, pos.y); if (item && item.isDir) { hoveredItem = item; } else { hoveredItem = null; } } } function endDragMove() { if (backButton && backButton.active) { backButton.endDragMove(); } else if (hoveredItem && !hoveredItem.popupDialog) { hoveredItem = null; } } function dropItemAt(pos) { var item = gridView.itemAt(pos.x, pos.y); if (item) { if (item.blank) { return -1; } var hOffset = Math.abs(Math.min(gridView.contentX, gridView.originX)); var hPos = mapToItem(item.hoverArea, pos.x + hOffset, pos.y); if ((hPos.x < 0 || hPos.y < 0 || hPos.x > item.hoverArea.width || hPos.y > item.hoverArea.height)) { return -1; } else { return positioner.map(item.index); } } return -1; } function drop(target, event, pos) { var dropPos = mapToItem(gridView.contentItem, pos.x, pos.y); var dropIndex = gridView.indexAt(dropPos.x, dropPos.y); var dragPos = mapToItem(gridView.contentItem, listener.dragX, listener.dragY); var dragIndex = gridView.indexAt(dragPos.x, dragPos.y); if (listener.dragX == -1 || dragIndex != dropIndex) { dir.drop(target, event, dropItemAt(dropPos)); } } Connections { target: dir onPopupMenuAboutToShow: { if (!plasmoid.immutable) { plasmoid.processMimeData(mimeData, x, y, dropJob); } } } Connections { target: plasmoid onExpandedChanged: { if (plasmoid.expanded && dir.status === Folder.FolderModel.Ready && !gridView.model) { gridView.model = positioner; } } } // Lower the toolBox when an item is hovered, so it doesn't interfere with // its interaction (e.g. the selection button in the top left, cf. Bug 337060) Binding { target: toolBox property: "z" // 999 is the default "z" for desktop ToolBoxRoot value: main.hoveredItem ? -100 : 999 when: toolBox } Binding { target: plasmoid property: "busy" value: !gridView.model && dir.status === Folder.FolderModel.Listing } function makeBackButton() { return Qt.createQmlObject("BackButtonItem {}", main); } function doCd(row) { history.push(url); updateHistory(); dir.cd(row); } function doBack() { url = history.pop(); updateHistory(); } // QML doesn't detect change in the array(history) property, so update it explicitly. function updateHistory() { history = history; } Connections { target: root onIsPopupChanged: { if (backButton == null && root.useListViewMode) { backButton = makeBackButton(); } else if (backButton != null) { backButton.destroy(); } } } MouseEventListener { id: listener anchors { topMargin: backButton != null ? backButton.height : undefined fill: parent } property alias hoveredItem: gridView.hoveredItem property Item pressedItem: null property int pressX: -1 property int pressY: -1 property int dragX: -1 property int dragY: -1 property variant cPress: null property bool doubleClickInProgress: false acceptedButtons: { if (hoveredItem == null && main.isRootView) { return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.BackButton) : Qt.LeftButton; } return root.isPopup ? (Qt.LeftButton | Qt.MiddleButton | Qt.RightButton | Qt.BackButton) : (Qt.LeftButton | Qt.RightButton); } hoverEnabled: true onPressXChanged: { cPress = mapToItem(gridView.contentItem, pressX, pressY); } onPressYChanged: { cPress = mapToItem(gridView.contentItem, pressX, pressY); } onPressed: { // Ignore press events outside the viewport (i.e. on scrollbars). if (!scrollArea.viewport.contains(Qt.point(mouse.x,mouse.y))) { return; } scrollArea.focus = true; if (mouse.buttons & Qt.BackButton) { if (root.isPopup && dir.resolvedUrl != dir.resolve(plasmoid.configuration.url)) { doBack(); mouse.accepted = true; } return; } if (editor && childAt(mouse.x, mouse.y) != editor) { editor.commit(); } pressX = mouse.x; pressY = mouse.y; if (!hoveredItem || hoveredItem.blank) { if (!gridView.ctrlPressed) { dir.clearSelection(); } if (mouse.buttons & Qt.RightButton) { clearPressState(); dir.openContextMenu(null, mouse.modifiers); mouse.accepted = true; } } else { pressedItem = hoveredItem; var pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { if (gridView.shiftPressed && gridView.currentIndex != -1) { positioner.setRangeSelected(gridView.anchorIndex, hoveredItem.index); } else { // FIXME TODO: Clicking one item with others selected should deselect the others, // which doesn't happen right now because initiating a drag after the press should // still drag all of them: The deselect needs to happen on release instead so we // can distinguish. if (!gridView.ctrlPressed && !dir.isSelected(positioner.map(hoveredItem.index))) { dir.clearSelection(); } if (gridView.ctrlPressed) { dir.toggleSelected(positioner.map(hoveredItem.index)); } else { dir.setSelected(positioner.map(hoveredItem.index)); } } gridView.currentIndex = hoveredItem.index; if (mouse.buttons & Qt.RightButton) { if (pressedItem.toolTip && pressedItem.toolTip.active) { pressedItem.toolTip.hideToolTip(); } clearPressState(); dir.openContextMenu(null, mouse.modifiers); mouse.accepted = true; } } } main.pressed(); } onCanceled: pressCanceled() onReleased: pressCanceled() onClicked: { clearPressState(); if (mouse.button === Qt.RightButton || (editor && childAt(mouse.x, mouse.y) === editor)) { return; } if (!hoveredItem || hoveredItem.blank || gridView.currentIndex == -1 || gridView.ctrlPressed || gridView.shiftPressed) { // Bug 357367: Replay mouse event, so containment actions assigned to left mouse button work. eventGenerator.sendMouseEvent(plasmoid, EventGenerator.MouseButtonPress, mouse.x, mouse.y, mouse.button, mouse.buttons, mouse.modifiers); return; } var pos = mapToItem(hoveredItem, mouse.x, mouse.y); // Moving from an item to its preview popup dialog doesn't unset hoveredItem // even though the cursor has left it, so we need to check whether the click - // actually occured inside the item we expect it in before going ahead. If it + // actually occurred inside the item we expect it in before going ahead. If it // didn't, clean up (e.g. dismissing the dialog as a side-effect of unsetting // hoveredItem) and abort. if (pos.x < 0 || pos.x > hoveredItem.width || pos.y < 0 || pos.y > hoveredItem.height) { hoveredItem = null; dir.clearSelection(); return; // If the hoveredItem is clicked while having a preview popup dialog open, // only dismiss the dialog and abort. } else if (hoveredItem.popupDialog) { hoveredItem.closePopup(); return; } pos = mapToItem(hoveredItem.actionsOverlay, mouse.x, mouse.y); if (!(pos.x <= hoveredItem.actionsOverlay.width && pos.y <= hoveredItem.actionsOverlay.height)) { if (Qt.styleHints.singleClickActivation || doubleClickInProgress) { var func = root.useListViewMode && (mouse.button == Qt.LeftButton) && hoveredItem.isDir ? doCd : dir.run; func(positioner.map(gridView.currentIndex)); hoveredItem = null; } else { doubleClickInProgress = true; doubleClickTimer.interval = Qt.styleHints.mouseDoubleClickInterval; doubleClickTimer.start(); } } } onPositionChanged: { gridView.ctrlPressed = (mouse.modifiers & Qt.ControlModifier); gridView.shiftPressed = (mouse.modifiers & Qt.ShiftModifier); var cPos = mapToItem(gridView.contentItem, mouse.x, mouse.y); var item = gridView.itemAt(cPos.x, cPos.y); var leftEdge = Math.min(gridView.contentX, gridView.originX); if (!item || item.blank) { if (gridView.hoveredItem && !root.containsDrag && (!dialog || !dialog.containsDrag) && !gridView.hoveredItem.popupDialog) { gridView.hoveredItem = null; } } else { var fPos = mapToItem(item.frame, mouse.x, mouse.y); if (fPos.x < 0 || fPos.y < 0 || fPos.x > item.frame.width || fPos.y > item.frame.height) { gridView.hoveredItem = null; } } // Trigger autoscroll. if (pressX != -1) { gridView.scrollLeft = (mouse.x <= 0 && gridView.contentX > leftEdge); gridView.scrollRight = (mouse.x >= gridView.width && gridView.contentX < gridView.contentItem.width - gridView.width); gridView.scrollUp = (mouse.y <= 0 && gridView.contentY > 0); gridView.scrollDown = (mouse.y >= gridView.height && gridView.contentY < gridView.contentItem.height - gridView.height); } // Update rubberband geometry. if (main.rubberBand) { var rB = main.rubberBand; if (cPos.x < cPress.x) { rB.x = Math.max(leftEdge, cPos.x); rB.width = Math.abs(rB.x - cPress.x); } else { rB.x = cPress.x; var ceil = Math.max(gridView.width, gridView.contentItem.width) + leftEdge; rB.width = Math.min(ceil - rB.x, Math.abs(rB.x - cPos.x)); } if (cPos.y < cPress.y) { rB.y = Math.max(0, cPos.y); rB.height = Math.abs(rB.y - cPress.y); } else { rB.y = cPress.y; var ceil = Math.max(gridView.height, gridView.contentItem.height); rB.height = Math.min(ceil - rB.y, Math.abs(rB.y - cPos.y)); } // Ensure rubberband is at least 1px in size or else it will become // invisible and not match any items. rB.width = Math.max(1, rB.width); rB.height = Math.max(1, rB.height); gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); return; } // Drag initiation. if (pressX != -1 && root.isDrag(pressX, pressY, mouse.x, mouse.y)) { if (pressedItem != null && dir.isSelected(positioner.map(pressedItem.index))) { pressedItem.toolTip.hideToolTip(); dragX = mouse.x; dragY = mouse.y; gridView.verticalDropHitscanOffset = pressedItem.iconArea.y + (pressedItem.iconArea.height / 2) dir.dragSelected(mouse.x, mouse.y); dragX = -1; dragY = -1; clearPressState(); } else { // Disable rubberband in popup list view mode. if (root.useListViewMode) { return; } dir.pinSelection(); main.rubberBand = Qt.createQmlObject("import QtQuick 2.0; import org.kde.private.desktopcontainment.folder 0.1 as Folder;" + "Folder.RubberBand { x: " + cPress.x + "; y: " + cPress.y + "; width: 0; height: 0; z: 99999; }", gridView.contentItem); gridView.interactive = false; } } } onContainsMouseChanged: { if (!containsMouse && !main.rubberBand) { clearPressState(); if (gridView.hoveredItem && !gridView.hoveredItem.popupDialog) { gridView.hoveredItem = null; } } } onHoveredItemChanged: { doubleClickInProgress = false; if (!hoveredItem) { hoverActivateTimer.stop(); } } function pressCanceled() { if (main.rubberBand) { main.rubberBand.visible = false; main.rubberBand.enabled = false; main.rubberBand.destroy(); main.rubberBand = null; gridView.interactive = true; gridView.cachedRectangleSelection = null; dir.unpinSelection(); } clearPressState(); gridView.cancelAutoscroll(); } function clearPressState() { pressedItem = null; pressX = -1; pressY = -1; } Timer { id: doubleClickTimer onTriggered: { listener.doubleClickInProgress = false; } } Timer { id: hoverActivateTimer interval: root.hoverActivateDelay onTriggered: { if (!hoveredItem) { return; } if (root.useListViewMode) { doCd(index); } else { hoveredItem.openPopup(); } } } PlasmaExtras.ScrollArea { id: scrollArea anchors.fill: parent focus: true property bool ready: false readonly property int viewportWidth: scrollArea.ready && viewport ? Math.ceil(viewport.width) : 0 readonly property int viewportHeight: scrollArea.ready && viewport ? Math.ceil(viewport.height) : 0 Component.onCompleted: { scrollArea.ready = true; } GridView { id: gridView property bool isRootView: false property int iconSize: makeIconSize() property int verticalDropHitscanOffset: 0 property Item hoveredItem: null property int anchorIndex: 0 property bool ctrlPressed: false property bool shiftPressed: false property bool overflowing: (visibleArea.heightRatio < 1.0 || visibleArea.widthRatio < 1.0) property bool scrollLeft: false property bool scrollRight: false property bool scrollUp: false property bool scrollDown: false property variant cachedRectangleSelection: null currentIndex: -1 keyNavigationWraps: false boundsBehavior: Flickable.StopAtBounds function calcExtraSpacing(cellSize, containerSize) { var availableColumns = Math.floor(containerSize / cellSize); var extraSpacing = 0; if (availableColumns > 0) { var allColumnSize = availableColumns * cellSize; var extraSpace = Math.max(containerSize - allColumnSize, 0); extraSpacing = extraSpace / availableColumns; } return extraSpacing; } cellWidth: { if (root.useListViewMode) { return gridView.width; } else { var iconWidth = iconSize + (2 * units.largeSpacing) + (2 * units.smallSpacing); if (root.isContainment && isRootView && scrollArea.viewportWidth > 0) { var extraWidth = calcExtraSpacing(iconWidth, scrollArea.viewportWidth); return iconWidth + extraWidth; } else { return iconWidth; } } } cellHeight: { if (root.useListViewMode) { return Math.ceil((Math.max(theme.mSize(theme.defaultFont).height, iconSize) + Math.max(highlightItemSvg.margins.top + highlightItemSvg.margins.bottom, listItemSvg.margins.top + listItemSvg.margins.bottom)) / 2) * 2; } else { var iconHeight = iconSize + (theme.mSize(theme.defaultFont).height * plasmoid.configuration.textLines) + (4 * units.smallSpacing); if (root.isContainment && isRootView && scrollArea.viewportHeight > 0) { var extraHeight = calcExtraSpacing(iconHeight, scrollArea.viewportHeight); return iconHeight + extraHeight; } else { return iconHeight; } } } delegate: FolderItemDelegate { width: gridView.cellWidth height: gridView.cellHeight } onContentXChanged: { if (hoveredItem) { hoverActivateTimer.stop(); } cancelRename(); dir.setDragHotSpotScrollOffset(contentX, contentY); if (contentX == 0) { scrollLeft = false; } if (contentX == contentItem.width - width) { scrollRight = false; } // Update rubberband geomety. if (main.rubberBand) { var rB = main.rubberBand; if (scrollLeft) { rB.x = Math.min(gridView.contentX, gridView.originX); rB.width = listener.cPress.x; } if (scrollRight) { var lastCol = gridView.contentX + gridView.width; rB.width = lastCol - rB.x; } gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); } } onContentYChanged: { if (hoveredItem) { hoverActivateTimer.stop(); } cancelRename(); dir.setDragHotSpotScrollOffset(contentX, contentY); if (contentY == 0) { scrollUp = false; } if (contentY == contentItem.height - height) { scrollDown = false; } // Update rubberband geometry. if (main.rubberBand) { var rB = main.rubberBand; if (scrollUp) { rB.y = 0; rB.height = listener.cPress.y; } if (scrollDown) { var lastRow = gridView.contentY + gridView.height; rB.height = lastRow - rB.y; } gridView.rectangleSelect(rB.x, rB.y, rB.width, rB.height); } } onScrollLeftChanged: { if (scrollLeft && gridView.visibleArea.widthRatio < 1.0) { smoothX.enabled = true; contentX = (gridView.flow == GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX; } else { contentX = contentX; smoothX.enabled = false; } } onScrollRightChanged: { if (scrollRight && gridView.visibleArea.widthRatio < 1.0) { smoothX.enabled = true; contentX = ((gridView.flow == GridView.FlowLeftToRight) ? gridView.contentX : gridView.originX) + (contentItem.width - width); } else { contentX = contentX; smoothX.enabled = false; } } onScrollUpChanged: { if (scrollUp && gridView.visibleArea.heightRatio < 1.0) { smoothY.enabled = true; contentY = 0; } else { contentY = contentY; smoothY.enabled = false; } } onScrollDownChanged: { if (scrollDown && gridView.visibleArea.heightRatio < 1.0) { smoothY.enabled = true; contentY = contentItem.height - height; } else { contentY = contentY; smoothY.enabled = false; } } onFlowChanged: { // FIXME TODO: Preserve positions. if (positioner.enabled) { positioner.reset(); } } onLayoutDirectionChanged: { // FIXME TODO: Preserve positions. if (positioner.enabled) { positioner.reset(); } } onCurrentIndexChanged: { positionViewAtIndex(currentIndex, GridView.Contain); } onCachedRectangleSelectionChanged: { if (cachedRectangleSelection == null) { return; } if (cachedRectangleSelection.length) { // Set current index to start of selection. // cachedRectangleSelection is pre-sorted. currentIndex = cachedRectangleSelection[0]; } dir.updateSelection(cachedRectangleSelection.map(positioner.map), gridView.ctrlPressed); } function makeIconSize() { if (root.useListViewMode) { return units.iconSizes.small; } return FolderTools.iconSizeFromTheme(plasmoid.configuration.iconSize); } function updateSelection(modifier) { if (modifier & Qt.ShiftModifier) { positioner.setRangeSelected(anchorIndex, currentIndex); } else { dir.clearSelection(); dir.setSelected(positioner.map(currentIndex)); } } function cancelAutoscroll() { scrollLeft = false; scrollRight = false; scrollUp = false; scrollDown = false; } function rectangleSelect(x, y, width, height) { var rows = (gridView.flow == GridView.FlowLeftToRight); var axis = rows ? gridView.width : gridView.height; var step = rows ? cellWidth : cellHeight; var perStripe = Math.floor(axis / step); var stripes = Math.ceil(gridView.count / perStripe); var cWidth = gridView.cellWidth - (2 * units.smallSpacing); var cHeight = gridView.cellHeight - (2 * units.smallSpacing); var midWidth = gridView.cellWidth / 2; var midHeight = gridView.cellHeight / 2; var indices = []; for (var s = 0; s < stripes; s++) { for (var i = 0; i < perStripe; i++) { var index = (s * perStripe) + i; if (index >= gridView.count) { break; } if (positioner.isBlank(index)) { continue; } var itemX = ((rows ? i : s) * gridView.cellWidth); var itemY = ((rows ? s : i) * gridView.cellHeight); if (gridView.effectiveLayoutDirection == Qt.RightToLeft) { itemX -= (rows ? gridView.contentX : gridView.originX); itemX += cWidth; itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; } // Check if the rubberband intersects this cell first to avoid doing more // expensive work. if (main.rubberBand.intersects(Qt.rect(itemX + units.smallSpacing, itemY + units.smallSpacing, cWidth, cHeight))) { var item = gridView.contentItem.childAt(itemX + midWidth, itemY + midHeight); // If this is a visible item, check for intersection with the actual // icon or label rects for better feel. if (item && item.iconArea) { var iconRect = Qt.rect(itemX + item.iconArea.x, itemY + item.iconArea.y, item.iconArea.width, item.iconArea.height); if (main.rubberBand.intersects(iconRect)) { indices.push(index); continue; } var labelRect = Qt.rect(itemX + item.labelArea.x, itemY + item.labelArea.y, item.labelArea.width, item.labelArea.height); if (main.rubberBand.intersects(labelRect)) { indices.push(index); continue; } } else { // Otherwise be content with the cell intersection. indices.push(index); } } } } gridView.cachedRectangleSelection = indices; } function runOrCdSelected() { if (currentIndex != -1 && dir.hasSelection()) { if (root.useListViewMode && currentItem.isDir) { doCd(positioner.map(currentIndex)); } else { dir.runSelected(); } } } Behavior on contentX { id: smoothX; enabled: false; SmoothedAnimation { velocity: 700 } } Behavior on contentY { id: smoothY; enabled: false; SmoothedAnimation { velocity: 700 } } Keys.onReturnPressed: { if (event.modifiers === Qt.AltModifier) { dir.openPropertiesDialog(); } else { runOrCdSelected(); } } Keys.onEnterPressed: Keys.returnPressed(event) Keys.onMenuPressed: { if (currentIndex != -1 && dir.hasSelection() && currentItem) { dir.setSelected(positioner.map(currentIndex)); dir.openContextMenu(currentItem.frame, event.modifiers); } else { // Otherwise let the containment handle it. event.accepted = false; } } Keys.onEscapePressed: { if (!editor || !editor.targetItem) { dir.clearSelection(); event.accepted = false; } } Folder.ShortCut { Component.onCompleted: { installAsEventFilterFor(gridView); } onDeleteFile: { dir.deleteSelected(); } onRenameFile: { rename(); } } Keys.onPressed: { event.accepted = true; if (event.matches(StandardKey.Delete)) { if (dir.hasSelection()) { dir.action("trash").trigger(); } } else if (event.key == Qt.Key_Control) { ctrlPressed = true; } else if (event.key == Qt.Key_Shift) { shiftPressed = true; if (currentIndex != -1) { anchorIndex = currentIndex; } } else if (event.key == Qt.Key_Home) { currentIndex = 0; updateSelection(event.modifiers); } else if (event.key == Qt.Key_End) { currentIndex = count - 1; updateSelection(event.modifiers); } else if (event.matches(StandardKey.Copy)) { dir.copy(); } else if (event.matches(StandardKey.Paste)) { dir.paste(); } else if (event.matches(StandardKey.Cut)) { dir.cut(); } else if (event.matches(StandardKey.Undo)) { dir.undo(); } else if (event.matches(StandardKey.Refresh)) { dir.refresh(); } else if (event.matches(StandardKey.SelectAll)) { positioner.setRangeSelected(0, count - 1); } else { event.accepted = false; } } Keys.onReleased: { if (event.key == Qt.Key_Control) { ctrlPressed = false; } else if (event.key == Qt.Key_Shift) { shiftPressed = false; anchorIndex = 0; } } Keys.onLeftPressed: { if (root.isPopup && dir.resolvedUrl != dir.resolve(plasmoid.configuration.url)) { doBack(); } else if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.LeftArrow)); if (newIndex != -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexLeft(); if (oldIndex == currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onRightPressed: { if (root.isPopup && currentIndex != -1 && dir.hasSelection()) { var func = root.isPopup ? doCd : dir.run; func(positioner.map(currentIndex)); } else if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.RightArrow)); if (newIndex != -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexRight(); if (oldIndex == currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onUpPressed: { if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.UpArrow)); if (newIndex != -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexUp(); if (oldIndex == currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onDownPressed: { if (positioner.enabled) { var newIndex = positioner.nearestItem(currentIndex, FolderTools.effectiveNavDirection(gridView.flow, gridView.effectiveLayoutDirection, Qt.DownArrow)); if (newIndex != -1) { currentIndex = newIndex; updateSelection(event.modifiers); } } else { var oldIndex = currentIndex; moveCurrentIndexDown(); if (oldIndex == currentIndex) { return; } updateSelection(event.modifiers); } } Keys.onBackPressed: { if (root.isPopup && dir.resolvedUrl != dir.resolve(plasmoid.configuration.url)) { doBack(); } } Connections { target: units onIconSizesChanged: { gridView.iconSize = gridView.makeIconSize(); } } Connections { target: plasmoid.configuration onIconSizeChanged: { gridView.iconSize = gridView.makeIconSize(); } } Connections { target: plasmoid.configuration onUrlChanged: { history = []; updateHistory(); } } } } Folder.WheelInterceptor { anchors.fill: parent enabled: root.isContainment && !gridView.overflowing destination: plasmoid } Folder.FolderModel { id: dir usedByContainment: root.isContainment && main.isRootView sortDesc: plasmoid.configuration.sortDesc sortDirsFirst: plasmoid.configuration.sortDirsFirst parseDesktopFiles: (plasmoid.configuration.url == "desktop:/") previews: plasmoid.configuration.previews previewPlugins: plasmoid.configuration.previewPlugins appletInterface: plasmoid onListingCompleted: { if (!gridView.model && plasmoid.expanded) { gridView.model = positioner; } } onMove: { var rows = (gridView.flow == GridView.FlowLeftToRight); var axis = rows ? gridView.width : gridView.height; var step = rows ? cellWidth : cellHeight; var perStripe = Math.floor(axis / step); var dropPos = mapToItem(gridView.contentItem, x, y); var leftEdge = Math.min(gridView.contentX, gridView.originX); var moves = [] var itemX = -1; var itemY = -1; var col = -1; var row = -1; var from = -1; var to = -1; for (var i = 0; i < urls.length; i++) { from = positioner.indexForUrl(urls[i]); to = -1; if (from == -1) { continue; } var offset = dir.dragCursorOffset(positioner.map(from)); if (offset.x == -1) { continue; } itemX = dropPos.x + offset.x + (listener.dragX % cellWidth) + (cellWidth / 2); itemY = dropPos.y + offset.y + (listener.dragY % cellHeight) + gridView.verticalDropHitscanOffset; if (gridView.effectiveLayoutDirection == Qt.RightToLeft) { itemX -= (rows ? gridView.contentX : gridView.originX); itemX = (rows ? gridView.width : gridView.contentItem.width) - itemX; } col = Math.floor(itemX / gridView.cellWidth); row = Math.floor(itemY / gridView.cellHeight); if ((rows ? col : row) < perStripe) { to = ((rows ? row : col) * perStripe) + (rows ? col : row); if (to < 0) { return; } } if (from != to) { moves.push(from); moves.push(to); } } if (moves.length) { positioner.move(moves); gridView.forceLayout(); } dir.clearSelection(); } } Folder.Positioner { id: positioner enabled: isContainment && sortMode === -1 folderModel: dir perStripe: Math.floor(((gridView.flow == GridView.FlowLeftToRight) ? gridView.width : gridView.height) / ((gridView.flow == GridView.FlowLeftToRight) ? gridView.cellWidth : gridView.cellHeight)); } Folder.ItemViewAdapter { id: viewAdapter adapterView: gridView adapterModel: positioner adapterIconSize: gridView.iconSize * 2 adapterVisibleArea: Qt.rect(gridView.contentX, gridView.contentY, gridView.contentWidth, gridView.contentHeight) Component.onCompleted: { gridView.movementStarted.connect(viewAdapter.viewScrolled); dir.viewAdapter = viewAdapter; } } Component { id: editorComponent PlasmaComponents.TextArea { id: editor visible: false wrapMode: root.useListViewMode ? TextEdit.NoWrap : TextEdit.Wrap textMargin: 0 horizontalAlignment: root.useListViewMode ? TextEdit.AlignHLeft : TextEdit.AlignHCenter property Item targetItem: null onTargetItemChanged: { if (targetItem != null) { var xy = getXY(); x = xy[0]; y = xy[1]; width = getWidth(); height = getInitHeight(); text = targetItem.label.text; adjustSize(); editor.select(0, dir.fileExtensionBoundary(positioner.map(targetItem.index))); if(isPopup) { flickableItem.contentX = Math.max(flickableItem.contentWidth - contentItem.width, 0); } else { flickableItem.contentY = Math.max(flickableItem.contentHeight - contentItem.height, 0); } visible = true; } else { x: 0 y: 0 visible = false; } } onVisibleChanged: { if (visible) { focus = true; } else { scrollArea.focus = true; } } Keys.onPressed: { switch(event.key) { case Qt.Key_Return: case Qt.Key_Enter: commit(); break; case Qt.Key_Escape: if (targetItem) { targetItem = null; event.accepted = true; } break; case Qt.Key_Home: if (event.modifiers & Qt.ShiftModifier) { editor.select(0, cursorPosition); } else { editor.select(0, 0); } event.accepted = true; break; case Qt.Key_End: if (event.modifiers & Qt.ShiftModifier) { editor.select(cursorPosition, text.length); } else { editor.select(text.length, text.length); } event.accepted = true; break; default: adjustSize(); break; } } Keys.onReleased: { adjustSize(); } function getXY() { var pos = main.mapFromItem(targetItem, targetItem.labelArea.x, targetItem.labelArea.y); var _x, _y; if (root.useListViewMode) { _x = targetItem.labelArea.x - __style.padding.left; _y = pos.y - __style.padding.top; } else { _x = targetItem.x + Math.abs(Math.min(gridView.contentX, gridView.originX)); _x += __style.padding.left; _x += scrollArea.viewport.x; if (verticalScrollBarPolicy == Qt.ScrollBarAlwaysOn && gridView.effectiveLayoutDirection == Qt.RightToLeft) { _x -= __verticalScrollBar.parent.verticalScrollbarOffset; } _y = pos.y + units.smallSpacing - __style.padding.top; } return([ _x, _y ]); } function getWidth(addWidthVerticalScroller) { return(targetItem.label.parent.width - units.smallSpacing + (root.useListViewMode ? -(__style.padding.left + __style.padding.right + units.smallSpacing) : 0) + (addWidthVerticalScroller ? __verticalScrollBar.parent.verticalScrollbarOffset : 0)); } function getHeight(addWidthHoriozontalScroller, init) { var _height; if(isPopup || init) { _height = targetItem.labelArea.height + __style.padding.top + __style.padding.bottom; } else { var realHeight = contentHeight + __style.padding.top + __style.padding.bottom; var maxHeight = theme.mSize(theme.defaultFont).height * (plasmoid.configuration.textLines + 1) + __style.padding.top + __style.padding.bottom; _height = Math.min(realHeight, maxHeight); } return(_height + (addWidthHoriozontalScroller ? __horizontalScrollBar.parent.horizontalScrollbarOffset : 0)); } function getInitHeight() { return(getHeight(false, true)); } function adjustSize() { if(isPopup) { if(contentWidth + __style.padding.left + __style.padding.right > width) { visible = true; horizontalScrollBarPolicy = Qt.ScrollBarAlwaysOn; height = getHeight(true); } else { horizontalScrollBarPolicy = Qt.ScrollBarAlwaysOff; height = getHeight(); } } else { height = getHeight(); if(contentHeight + __style.padding.top + __style.padding.bottom > height) { visible = true; verticalScrollBarPolicy = Qt.ScrollBarAlwaysOn; width = getWidth(true); } else { verticalScrollBarPolicy = Qt.ScrollBarAlwaysOff; width = getWidth(); } } var xy = getXY(); x = xy[0]; y = xy[1]; } function commit() { if (targetItem) { dir.rename(positioner.map(targetItem.index), text); targetItem = null; } } } } Component.onCompleted: { dir.requestRename.connect(rename); } } Component.onCompleted: { if (backButton == null && root.useListViewMode) { backButton = makeBackButton(); } } } diff --git a/containments/desktop/plugins/folder/autotests/positionertest.cpp b/containments/desktop/plugins/folder/autotests/positionertest.cpp index db0e1d9ad..f89f6f360 100644 --- a/containments/desktop/plugins/folder/autotests/positionertest.cpp +++ b/containments/desktop/plugins/folder/autotests/positionertest.cpp @@ -1,342 +1,340 @@ /*************************************************************************** * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * * * * Author: Andras Mantia * * * * 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 "positionertest.h" -#include - #include #include #include #include "foldermodel.h" #include "positioner.h" #include "screenmapper.h" QTEST_MAIN(PositionerTest) static const QLatin1String desktop(QLatin1String("Desktop")); void PositionerTest::initTestCase() { m_folderDir = new QTemporaryDir(); QDir dir(m_folderDir->path()); dir.mkdir(desktop); dir.cd(desktop); dir.mkdir("firstDir"); QFile f; for (int i = 1; i < 10; i++) { f.setFileName(QStringLiteral("%1/file%2.txt").arg(dir.path(), QString::number(i))); f.open(QFile::WriteOnly); f.close(); } } void PositionerTest::cleanupTestCase() { delete m_folderDir; } void PositionerTest::init() { m_folderModel = new FolderModel(this); m_folderModel->classBegin(); m_folderModel->setScreen(0); m_folderModel->setUsedByContainment(true); m_folderModel->componentComplete(); m_positioner = new Positioner(this); m_positioner->setEnabled(true); m_positioner->setFolderModel(m_folderModel); m_positioner->setPerStripe(3); m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop ); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); s.wait(1000); } void PositionerTest::cleanup() { delete m_folderModel; m_folderModel = nullptr; delete m_positioner; m_positioner = nullptr; } void PositionerTest::tst_positions_data() { QTest::addColumn("perStripe"); QTest::newRow("3 per column") << 3; QTest::newRow("5 per column") << 5; } void PositionerTest::tst_positions() { QFETCH(int, perStripe); m_positioner->setPerStripe(perStripe); checkPositions(perStripe); } void PositionerTest::tst_map() { //by default the mapping is 1-1 for (int i = 0; i < m_positioner->rowCount(); i++) { QCOMPARE(m_positioner->map(i), i); } } void PositionerTest::tst_move_data() { QTest::addColumn("moves"); QTest::addColumn >("result"); QTest::newRow("First to last") << QVariantList({0, 10}) << QVector({-1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}); QTest::newRow("First to after last") << QVariantList({0, 11}) << QVector({-1, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, 0}); QTest::newRow("Switch 2nd with 3rd ") << QVariantList({1, 2, 2, 1}) << QVector({0, 2, 1, 3, 4, 5, 6, 7, 8, 9}); QTest::newRow("Switch 2nd with 2nd ") << QVariantList({1, 1, 1, 1}) << QVector({0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); QTest::newRow("2nd to last") << QVariantList({2, 10}) << QVector({0, 1, -1, 3, 4, 5, 6, 7, 8, 9, 2}); } void PositionerTest::tst_move() { QFETCH(QVariantList, moves); QFETCH(QVector, result); m_positioner->move(moves); for (int i = 0; i < m_positioner->rowCount(); i++) { QCOMPARE(m_positioner->map(i), result[i]); } } void PositionerTest::tst_nearestitem_data() { QTest::addColumn("index"); QTest::addColumn >("result"); QTest::newRow("Around first") << 0 << QVector{-1, -1, 3, -1, 1}; QTest::newRow("Around second") << 1 << QVector{-1, -1, 4, 0, 2}; QTest::newRow("Around 5th") << 4 << QVector{-1, 1, 7, 3, 5}; QTest::newRow("Around last") << 9 << QVector{-1, 6, -1, -1, 7}; QTest::newRow("Around invalid") << 11 << QVector{-1, -1, -1, -1, -1}; } void PositionerTest::tst_nearestitem() { QFETCH(int, index); QFETCH(QVector, result); for (int i = Qt::NoArrow; i <= Qt::RightArrow; i++) { QCOMPARE(m_positioner->nearestItem(index, (Qt::ArrowType)i), result[i]); } } void PositionerTest::tst_isBlank() { QCOMPARE(m_positioner->isBlank(0), false); QCOMPARE(m_positioner->isBlank(11), true); m_positioner->move({0, 10}); QCOMPARE(m_positioner->isBlank(0), true); } void PositionerTest::tst_reset() { m_positioner->move({0, 10}); m_positioner->reset(); QSignalSpy s(m_positioner, &Positioner::positionsChanged); s.wait(500); QCOMPARE(s.count(), 1); checkPositions(3); for (int i = 0; i < m_positioner->rowCount(); i++) { QCOMPARE(m_positioner->map(i), i); } } void PositionerTest::tst_defaultValues() { Positioner positioner; QVERIFY(!positioner.enabled()); QVERIFY(!positioner.folderModel()); QCOMPARE(positioner.perStripe(), 0); QVERIFY(positioner.positions().isEmpty()); } void PositionerTest::tst_changeEnabledStatus() { Positioner positioner; QVERIFY(!positioner.enabled()); QSignalSpy s(&positioner, &Positioner::enabledChanged); positioner.setEnabled(true); QCOMPARE(s.count(), 1); positioner.setEnabled(false); QCOMPARE(s.count(), 2); //No change positioner.setEnabled(false); QCOMPARE(s.count(), 2); } void PositionerTest::tst_changePerStripe() { Positioner positioner; QCOMPARE(positioner.perStripe(), 0); QSignalSpy s(&positioner, &Positioner::perStripeChanged); positioner.setPerStripe(1); QCOMPARE(s.count(), 1); //No change positioner.setPerStripe(1); QCOMPARE(s.count(), 1); positioner.setPerStripe(4); QCOMPARE(s.count(), 2); } void PositionerTest::tst_proxyMapping() { auto *screenMapper = ScreenMapper::instance(); FolderModel secondFolderModel; secondFolderModel.classBegin(); secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop ); secondFolderModel.setUsedByContainment(true); secondFolderModel.setScreen(1); secondFolderModel.componentComplete(); Positioner secondPositioner; secondPositioner.setEnabled(true); secondPositioner.setFolderModel(&secondFolderModel); secondPositioner.setPerStripe(3); QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); QVERIFY(s2.wait(1000)); QHash expectedSource2ProxyScreen0; QHash expectedProxy2SourceScreen0; QHash expectedProxy2SourceScreen1; QHash expectedSource2ProxyScreen1; for (int i = 0; i < m_folderModel->rowCount(); i++) { expectedSource2ProxyScreen0[i] = i; expectedProxy2SourceScreen0[i] = i; } // swap items 1 and 2 in the positioner m_positioner->move({1, 2, 2, 1}); expectedSource2ProxyScreen0[1] = 2; expectedSource2ProxyScreen0[2] = 1; expectedProxy2SourceScreen0[1] = 2; expectedProxy2SourceScreen0[2] = 1; auto savedSource2ProxyScreen0 = expectedSource2ProxyScreen0; auto savedProxy2SourceScreen0 = expectedProxy2SourceScreen0; auto verifyMapping = [](const QHash &actual, const QHash &expected) { auto ensureUnique = [](const QHash mapping) { auto values = mapping.values(); qSort(values); auto uniqueValues = values.toSet().toList(); qSort(uniqueValues); QVERIFY(uniqueValues == values); }; ensureUnique(actual); QCOMPARE(actual, expected); }; verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); const auto movedItem = m_folderModel->index(1, 0).data(FolderModel::UrlRole).toUrl(); // move the item 1 from source (now in position 2) to the second screen screenMapper->addMapping(movedItem, 1); expectedProxy2SourceScreen1[0] = 0; expectedSource2ProxyScreen1[0] = 0; expectedSource2ProxyScreen0.clear(); expectedProxy2SourceScreen0.clear(); for (int i = 0; i < m_folderModel->rowCount(); i++) { // as item 1 disappeared, the mapping of all items after that are shifted auto proxyIndex = (i <= 1) ? i : i + 1; expectedProxy2SourceScreen0[proxyIndex] = i; expectedSource2ProxyScreen0[i] = proxyIndex; } verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); // move back the same item to the first screen screenMapper->addMapping(movedItem, 0); // nothing on the second screen expectedSource2ProxyScreen1.clear(); expectedProxy2SourceScreen1.clear(); // first screen should look like in the beginning expectedSource2ProxyScreen0 = savedSource2ProxyScreen0; expectedProxy2SourceScreen0 = savedProxy2SourceScreen0; verifyMapping(m_positioner->proxyToSourceMapping(), expectedProxy2SourceScreen0); verifyMapping(m_positioner->sourceToProxyMapping(), expectedSource2ProxyScreen0); verifyMapping(secondPositioner.proxyToSourceMapping(), expectedProxy2SourceScreen1); verifyMapping(secondPositioner.sourceToProxyMapping(), expectedSource2ProxyScreen1); } void PositionerTest::checkPositions(int perStripe) { QSignalSpy s(m_positioner, &Positioner::positionsChanged); s.wait(500); const auto positions = m_positioner->positions(); struct Pos { int x; int y; }; const auto fileCount = m_folderModel->rowCount(); QHash posHash; QCOMPARE(positions[0].toInt(), 1 + ((fileCount - 1) / perStripe)); // rows QCOMPARE(positions[1].toInt(), perStripe); // columns for (int i = 2; i < positions.length() - 2; i+=3) { posHash[positions[i]] = {positions[i + 1].toInt(), positions[i + 2].toInt()}; } int row = 0; int col = 0; for (int i = 0; i < fileCount; i++) { const auto index = m_folderModel->index(i, 0); const auto url = index.data(FolderModel::UrlRole).toString(); const Pos pos = posHash[url]; QCOMPARE(pos.x, row); QCOMPARE(pos.y, col); col++; if (col == perStripe) { row++; col = 0; } } } diff --git a/containments/panel/contents/ui/main.qml b/containments/panel/contents/ui/main.qml index a3f25766e..bdf1b5b86 100644 --- a/containments/panel/contents/ui/main.qml +++ b/containments/panel/contents/ui/main.qml @@ -1,444 +1,444 @@ /* * Copyright 2013 Marco Martin * * 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 2.010-1301, USA. */ import QtQuick 2.1 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 import org.kde.draganddrop 2.0 as DragDrop import "LayoutManager.js" as LayoutManager DragDrop.DropArea { id: root width: 640 height: 48 //BEGIN properties Layout.minimumWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.minimumWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.maximumWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.maximumWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.preferredWidth: fixedWidth > 0 ? fixedWidth : (currentLayout.Layout.preferredWidth + (isHorizontal && toolBox ? toolBox.width : 0)) Layout.minimumHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.minimumHeight + (!isHorizontal && toolBox ? toolBox.height : 0)) Layout.maximumHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.maximumHeight + (!isHorizontal && toolBox ? toolBox.height : 0)) Layout.preferredHeight: fixedHeight > 0 ? fixedHeight : (currentLayout.Layout.preferredHeight + (!isHorizontal && toolBox? toolBox.height : 0)) property Item toolBox property var layoutManager: LayoutManager property Item dragOverlay property bool isHorizontal: plasmoid.formFactor != PlasmaCore.Types.Vertical property int fixedWidth: 0 property int fixedHeight: 0 //END properties //BEGIN functions function addApplet(applet, x, y) { // don't show applet if it choses to be hidden but still make it // accessible in the panelcontroller // Due to the nature of how "visible" propagates in QML, we need to // explicitly set it on the container (so the Layout ignores it) // as well as the applet (so it reliably knows about), otherwise it can // happen that an applet erroneously thinks it's visible, or suddenly // starts thinking that way on teardown (virtual desktop pager) // leading to crashes var visibleBinding = Qt.binding(function() { return applet.status !== PlasmaCore.Types.HiddenStatus || (!plasmoid.immutable && plasmoid.userConfiguring); }) var container = appletContainerComponent.createObject(root, { applet: applet, visible: visibleBinding }); applet.parent = container; applet.anchors.fill = container; applet.visible = visibleBinding; // Is there a DND placeholder? Replace it! if (dndSpacer.parent === currentLayout) { LayoutManager.insertBefore(dndSpacer, container); dndSpacer.parent = root; return; // If the provided position is valid, use it. } else if (x >= 0 && y >= 0) { var index = LayoutManager.insertAtCoordinates(container, x , y); // Fall through to determining an appropriate insert position. } else { var before = lastSpacer; container.animationsEnabled = false; // Insert icons to the left of whatever is at the center (usually a Task Manager), // if it exists. // FIXME TODO: This is a real-world fix to produce a sensible initial position for // launcher icons added by launcher menu applets. The basic approach has been used // since Plasma 1. However, "add launcher to X" is a generic-enough concept and - // frequent-enough occurence that we'd like to abstract it further in the future - // and get rid of the uglyness of parties external to the containment adding applets + // frequent-enough occurrence that we'd like to abstract it further in the future + // and get rid of the ugliness of parties external to the containment adding applets // of a specific type, and the containment caring about the applet type. In a better // system the containment would be informed of requested launchers, and determine by // itself what it wants to do with that information. if (!startupTimer.running && applet.pluginName == "org.kde.plasma.icon") { var middle = currentLayout.childAt(root.width / 2, root.height / 2); if (middle) { before = middle; } // lastSpacer is here, enqueue before it. } LayoutManager.insertBefore(before, container); //event compress the enable of animations startupTimer.restart(); } } function checkLastSpacer() { var flexibleFound = false; for (var i = 0; i < currentLayout.children.length; ++i) { var applet = currentLayout.children[i].applet; if (!applet) { continue; } if (!applet.visible || !applet.Layout) { continue; } if ((root.isHorizontal && applet.Layout.fillWidth) || (!root.isHorizontal && applet.Layout.fillHeight)) { flexibleFound = true; break } } lastSpacer.visible= !flexibleFound; } //END functions //BEGIN connections Component.onCompleted: { currentLayout.isLayoutHorizontal = isHorizontal LayoutManager.plasmoid = plasmoid; LayoutManager.root = root; LayoutManager.layout = currentLayout; LayoutManager.lastSpacer = lastSpacer; LayoutManager.restore(); containmentSizeSyncTimer.restart(); plasmoid.action("configure").visible = Qt.binding(function() { return !plasmoid.immutable; }); plasmoid.action("configure").enabled = Qt.binding(function() { return !plasmoid.immutable; }); } onDragEnter: { if (plasmoid.immutable) { event.ignore(); return; } //during drag operations we disable panel auto resize if (root.isHorizontal) { root.fixedWidth = root.width } else { root.fixedHeight = root.height } LayoutManager.insertAtCoordinates(dndSpacer, event.x, event.y) } onDragMove: { LayoutManager.insertAtCoordinates(dndSpacer, event.x, event.y) } onDragLeave: { dndSpacer.parent = root; root.fixedWidth = 0; root.fixedHeight = 0; } onDrop: { plasmoid.processMimeData(event.mimeData, event.x, event.y); event.accept(event.proposedAction); root.fixedWidth = 0; root.fixedHeight = 0; containmentSizeSyncTimer.restart(); } Containment.onAppletAdded: { addApplet(applet, x, y); checkLastSpacer(); LayoutManager.save(); } Containment.onAppletRemoved: { LayoutManager.removeApplet(applet); checkLastSpacer(); LayoutManager.save(); } Plasmoid.onUserConfiguringChanged: { if (plasmoid.immutable) { if (dragOverlay) { dragOverlay.destroy(); } return; } if (plasmoid.userConfiguring) { for (var i = 0; i < plasmoid.applets.length; ++i) { plasmoid.applets[i].expanded = false; } if (!dragOverlay) { var component = Qt.createComponent("ConfigOverlay.qml"); if (component.status === Component.Ready) { dragOverlay = component.createObject(root); } else { console.log("Could not create ConfigOverlay:", component.errorString()); } component.destroy(); } else { dragOverlay.visible = true; } } else { dragOverlay.destroy(); } } Plasmoid.onFormFactorChanged: containmentSizeSyncTimer.restart(); Plasmoid.onImmutableChanged: containmentSizeSyncTimer.restart(); onToolBoxChanged: { containmentSizeSyncTimer.restart(); if (startupTimer.running) { startupTimer.restart(); } } //END connections //BEGIN components Component { id: appletContainerComponent // This loader conditionally manages the BusyIndicator, it's not // loading the applet. The applet becomes a regular child item. Loader { id: container visible: false property bool animationsEnabled: true //when the applet moves caused by its resize, don't animate. //this is completely heuristic, but looks way less "jumpy" property bool movingForResize: false Layout.fillWidth: applet && applet.Layout.fillWidth Layout.onFillWidthChanged: { if (plasmoid.formFactor != PlasmaCore.Types.Vertical) { checkLastSpacer(); } } Layout.fillHeight: applet && applet.Layout.fillHeight Layout.onFillHeightChanged: { if (plasmoid.formFactor == PlasmaCore.Types.Vertical) { checkLastSpacer(); } } Layout.minimumWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.minimumWidth > 0 ? applet.Layout.minimumWidth : root.height) : root.width) Layout.minimumHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.minimumHeight > 0 ? applet.Layout.minimumHeight : root.width) : root.height) Layout.preferredWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.preferredWidth > 0 ? applet.Layout.preferredWidth : root.height) : root.width) Layout.preferredHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.preferredHeight > 0 ? applet.Layout.preferredHeight : root.width) : root.height) Layout.maximumWidth: (currentLayout.isLayoutHorizontal ? (applet && applet.Layout.maximumWidth > 0 ? applet.Layout.maximumWidth : (Layout.fillWidth ? root.width : root.height)) : root.height) Layout.maximumHeight: (!currentLayout.isLayoutHorizontal ? (applet && applet.Layout.maximumHeight > 0 ? applet.Layout.maximumHeight : (Layout.fillHeight ? root.height : root.width)) : root.width) property int oldX: x property int oldY: y property Item applet onAppletChanged: { if (!applet) { destroy(); } } active: applet && applet.busy sourceComponent: PlasmaComponents.BusyIndicator {} Layout.onMinimumWidthChanged: movingForResize = true; Layout.onMinimumHeightChanged: movingForResize = true; Layout.onMaximumWidthChanged: movingForResize = true; Layout.onMaximumHeightChanged: movingForResize = true; onXChanged: { if (movingForResize) { movingForResize = false; return; } if (!animationsEnabled) { startupTimer.restart(); return; } translation.x = oldX - x translation.y = oldY - y translAnim.running = true oldX = x oldY = y } onYChanged: { if (movingForResize) { movingForResize = false; return; } if (!animationsEnabled) { startupTimer.restart(); return; } translation.x = oldX - x translation.y = oldY - y translAnim.running = true oldX = x oldY = y } transform: Translate { id: translation } NumberAnimation { id: translAnim duration: units.longDuration easing.type: Easing.InOutQuad target: translation properties: "x,y" to: 0 } } } //END components //BEGIN UI elements Item { id: lastSpacer parent: currentLayout Layout.fillWidth: true Layout.fillHeight: true } Item { id: dndSpacer Layout.preferredWidth: width Layout.preferredHeight: height width: (plasmoid.formFactor == PlasmaCore.Types.Vertical) ? currentLayout.width : theme.mSize(theme.defaultFont).width * 10 height: (plasmoid.formFactor == PlasmaCore.Types.Vertical) ? theme.mSize(theme.defaultFont).width * 10 : currentLayout.height } // while the user is moving the applet when configuring the panel, the applet is reparented // here so it can be moved freely; previously it was reparented to "root" but this one does not // take into account the toolbox (which is left-of) the layout in right-to-left languages Item { id: moveAppletLayer anchors.fill: currentLayout } GridLayout { id: currentLayout property bool isLayoutHorizontal rowSpacing: units.smallSpacing columnSpacing: units.smallSpacing Layout.preferredWidth: { var width = 0; for (var i = 0, length = currentLayout.children.length; i < length; ++i) { var item = currentLayout.children[i]; if (item.Layout) { width += Math.max(item.Layout.minimumWidth, item.Layout.preferredWidth); } } return width; } Layout.preferredHeight: { var height = 0; for (var i = 0, length = currentLayout.children.length; i < length; ++i) { var item = currentLayout.children[i]; if (item.Layout) { height += Math.max(item.Layout.minimumHeight, item.Layout.preferredHeight); } } return height; } rows: 1 columns: 1 //when horizontal layout top-to-bottom, this way it will obey our limit of one row and actually lay out left to right flow: isHorizontal ? GridLayout.TopToBottom : GridLayout.LeftToRight layoutDirection: Qt.application.layoutDirection } onWidthChanged: { containmentSizeSyncTimer.restart() if (startupTimer.running) { startupTimer.restart(); } } onHeightChanged: { containmentSizeSyncTimer.restart() if (startupTimer.running) { startupTimer.restart(); } } Timer { id: containmentSizeSyncTimer interval: 150 onTriggered: { dndSpacer.parent = root; currentLayout.x = (isHorizontal && toolBox && Qt.application.layoutDirection === Qt.RightToLeft && !plasmoid.immutable) ? toolBox.width : 0; currentLayout.y = 0 currentLayout.width = root.width - (isHorizontal && toolBox && !plasmoid.immutable ? toolBox.width : 0) currentLayout.height = root.height - (!isHorizontal && toolBox && !plasmoid.immutable ? toolBox.height : 0) currentLayout.isLayoutHorizontal = isHorizontal } } //FIXME: I don't see other ways at the moment a way to see when the UI is REALLY ready Timer { id: startupTimer interval: 4000 onTriggered: { for (var i = 0; i < currentLayout.children.length; ++i) { var item = currentLayout.children[i]; if (item.hasOwnProperty("animationsEnabled")) { item.animationsEnabled = true; } } } } //END UI elements } diff --git a/desktoppackage/contents/configuration/AppletConfiguration.qml b/desktoppackage/contents/configuration/AppletConfiguration.qml index 5ca93a0f4..9ada1121b 100644 --- a/desktoppackage/contents/configuration/AppletConfiguration.qml +++ b/desktoppackage/contents/configuration/AppletConfiguration.qml @@ -1,431 +1,431 @@ /* * Copyright 2013 Marco Martin * * 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 2.010-1301, USA. */ import QtQuick 2.0 import QtQuick.Dialogs 1.1 import QtQuick.Controls 1.3 as QtControls import QtQuick.Layouts 1.0 import QtQuick.Window 2.2 import org.kde.plasma.core 2.1 as PlasmaCore import org.kde.plasma.configuration 2.0 //TODO: all of this will be done with desktop components Rectangle { id: root Layout.minimumWidth: units.gridUnit * 30 Layout.minimumHeight: units.gridUnit * 20 LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true //BEGIN properties color: syspal.window width: units.gridUnit * 40 height: units.gridUnit * 30 property bool isContainment: false //END properties //BEGIN model property ConfigModel globalConfigModel: globalAppletConfigModel ConfigModel { id: globalAppletConfigModel ConfigCategory { name: i18nd("plasma_shell_org.kde.plasma.desktop", "Keyboard shortcuts") icon: "preferences-desktop-keyboard" source: "ConfigurationShortcuts.qml" } } PlasmaCore.SortFilterModel { id: configDialogFilterModel sourceModel: configDialog.configModel filterRole: "visible" filterCallback: function(source_row, value) { return value; } } //END model //BEGIN functions function saveConfig() { if (main.currentItem.saveConfig) { main.currentItem.saveConfig() } for (var key in plasmoid.configuration) { if (main.currentItem["cfg_"+key] !== undefined) { plasmoid.configuration[key] = main.currentItem["cfg_"+key] } } } function settingValueChanged() { applyButton.enabled = true; } //END functions //BEGIN connections Component.onCompleted: { if (!isContainment && configDialog.configModel && configDialog.configModel.count > 0) { if (configDialog.configModel.get(0).source) { main.sourceFile = configDialog.configModel.get(0).source } else if (configDialog.configModel.get(0).kcm) { main.sourceFile = Qt.resolvedUrl("ConfigurationKcmPage.qml"); main.currentItem.kcm = configDialog.configModel.get(0).kcm; } else { main.sourceFile = ""; } main.title = configDialog.configModel.get(0).name } else { main.sourceFile = globalConfigModel.get(0).source main.title = globalConfigModel.get(0).name } // root.width = mainColumn.implicitWidth // root.height = mainColumn.implicitHeight } //END connections //BEGIN UI components SystemPalette {id: syspal} MessageDialog { id: messageDialog icon: StandardIcon.Warning property Item delegate title: i18nd("plasma_shell_org.kde.plasma.desktop", "Apply Settings") text: i18nd("plasma_shell_org.kde.plasma.desktop", "The settings of the current module have changed. Do you want to apply the changes or discard them?") standardButtons: StandardButton.Apply | StandardButton.Discard | StandardButton.Cancel onApply: { applyAction.trigger() delegate.openCategory() } onDiscard: { delegate.openCategory() } } ColumnLayout { id: mainColumn anchors { fill: parent margins: mainColumn.spacing * units.devicePixelRatio //margins are hardcoded in QStyle we should match that here } property int implicitWidth: Math.max(contentRow.implicitWidth, buttonsRow.implicitWidth) + 8 property int implicitHeight: contentRow.implicitHeight + buttonsRow.implicitHeight + 8 RowLayout { id: contentRow anchors { left: parent.left right: parent.right } spacing: 0 Layout.fillHeight: true Layout.preferredHeight: parent.height - buttonsRow.height QtControls.ScrollView { id: categoriesScroll frameVisible: false horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff Layout.fillHeight: true visible: (configDialog.configModel ? configDialog.configModel.count : 0) + globalConfigModel.count > 1 Layout.maximumWidth: units.gridUnit * 7 Layout.preferredWidth: categories.implicitWidth flickableItem.interactive: false Keys.onUpPressed: { var buttons = categories.children var foundPrevious = false for (var i = buttons.length - 1; i >= 0; --i) { var button = buttons[i]; if (!button.hasOwnProperty("current")) { // not a ConfigCategoryDelegate continue; } if (foundPrevious) { button.openCategory() return } else if (button.current) { foundPrevious = true } } } Keys.onDownPressed: { var buttons = categories.children var foundNext = false for (var i = 0, length = buttons.length; i < length; ++i) { var button = buttons[i]; console.log(button) if (!button.hasOwnProperty("current")) { continue; } if (foundNext) { button.openCategory() return } else if (button.current) { foundNext = true } } } ColumnLayout { id: categories spacing: 0 width: categoriesScroll.viewport.width property Item currentItem: children[1] Repeater { model: root.isContainment ? globalConfigModel : undefined delegate: ConfigCategoryDelegate {} } Repeater { model: configDialogFilterModel delegate: ConfigCategoryDelegate {} } Repeater { model: !root.isContainment ? globalConfigModel : undefined delegate: ConfigCategoryDelegate {} } } } Rectangle { Layout.fillHeight: true width: Math.round(units.devicePixelRatio) color: syspal.highlight visible: categoriesScroll.visible opacity: categoriesScroll.activeFocus && Window.active ? 1 : 0.3 Behavior on color { ColorAnimation { duration: units.longDuration easing.type: Easing.InOutQuad } } } Item { // spacer width: units.largeSpacing visible: categoriesScroll.visible } QtControls.ScrollView { id: scroll Layout.fillHeight: true Layout.fillWidth: true // we want to focus the controls in the settings page right away, don't focus the ScrollView activeFocusOnTab: false // this horrible code below ensures the control with active focus stays visible in the window // by scrolling the view up or down as needed when tabbing through the window Window.onActiveFocusItemChanged: { var flickable = scroll.flickableItem; var item = Window.activeFocusItem; if (!item) { return; } - // when an item withing ScrollView has active focus the ScrollView, + // when an item within ScrollView has active focus the ScrollView, // as FocusScope, also has it, so we only scroll in this case if (!scroll.activeFocus) { return; } var padding = units.gridUnit * 2 // some padding to the top/bottom when we scroll var yPos = item.mapToItem(scroll.contentItem, 0, 0).y; if (yPos < flickable.contentY) { flickable.contentY = Math.max(0, yPos - padding); // The "Math.min(padding, item.height)" ensures that we only scroll the item into view // when it's barely visible. The logic was mostly meant for keyboard navigating through // a list of CheckBoxes, so this check keeps us from trying to scroll an inner ScrollView // into view when it implicitly gains focus (like plasma-pa config dialog has). } else if (yPos + Math.min(padding, item.height) > flickable.contentY + flickable.height) { flickable.contentY = Math.min(flickable.contentHeight - flickable.height, yPos - flickable.height + item.height + padding); } } Column { spacing: units.largeSpacing / 2 QtControls.Label { id: pageTitle width: scroll.viewport.width font.pointSize: theme.defaultFont.pointSize*2 font.weight: Font.Light text: main.title } QtControls.StackView { id: main property string title: "" property bool invertAnimations: false height: Math.max((scroll.viewport.height - pageTitle.height - parent.spacing), (main.currentItem ? (main.currentItem.implicitHeight ? main.currentItem.implicitHeight : main.currentItem.childrenRect.height) : 0)) width: scroll.viewport.width property string sourceFile onSourceFileChanged: { if (!sourceFile) { return; } var props = {} var plasmoidConfig = plasmoid.configuration for (var key in plasmoidConfig) { props["cfg_" + key] = plasmoid.configuration[key] } var newItem = push({ item: Qt.resolvedUrl(sourceFile), replace: true, properties: props }) for (var key in plasmoidConfig) { var changedSignal = newItem["cfg_" + key + "Changed"] if (changedSignal) { changedSignal.connect(root.settingValueChanged) } } var configurationChangedSignal = newItem.configurationChanged if (configurationChangedSignal) { configurationChangedSignal.connect(root.settingValueChanged) } applyButton.enabled = false; scroll.flickableItem.contentY = 0 /* * This is not needed on a desktop shell that has ok/apply/cancel buttons, i'll leave it here only for future reference until we have a prototype for the active shell. * root.pageChanged will start a timer, that in turn will call saveConfig() when triggered for (var prop in currentItem) { if (prop.indexOf("cfg_") === 0) { currentItem[prop+"Changed"].connect(root.pageChanged) } }*/ } delegate: QtControls.StackViewDelegate { function transitionFinished(properties) { properties.exitItem.opacity = 1 } pushTransition: QtControls.StackViewTransition { PropertyAnimation { target: enterItem property: "opacity" from: 0 to: 1 duration: units.longDuration easing.type: Easing.InOutQuad } PropertyAnimation { target: enterItem property: "x" from: main.invertAnimations ? -target.width/3: target.width/3 to: 0 duration: units.longDuration easing.type: Easing.InOutQuad } PropertyAnimation { target: exitItem property: "opacity" from: 1 to: 0 duration: units.longDuration easing.type: Easing.InOutQuad } PropertyAnimation { target: exitItem property: "x" from: 0 to: main.invertAnimations ? target.width/3 : -target.width/3 duration: units.longDuration easing.type: Easing.InOutQuad } } } } } } } QtControls.Action { id: acceptAction onTriggered: { applyAction.trigger(); configDialog.close(); } shortcut: "Return" } QtControls.Action { id: applyAction onTriggered: { root.saveConfig(); applyButton.enabled = false; } } QtControls.Action { id: cancelAction onTriggered: configDialog.close(); shortcut: "Escape" } RowLayout { id: buttonsRow Layout.alignment: Qt.AlignVCenter | Qt.AlignRight QtControls.Button { iconName: "dialog-ok" text: i18nd("plasma_shell_org.kde.plasma.desktop", "OK") onClicked: acceptAction.trigger() } QtControls.Button { id: applyButton enabled: false iconName: "dialog-ok-apply" text: i18nd("plasma_shell_org.kde.plasma.desktop", "Apply") visible: main.currentItem && (!main.currentItem.kcm || main.currentItem.kcm.buttons & 4) // 4 = Apply button onClicked: applyAction.trigger() } QtControls.Button { iconName: "dialog-cancel" text: i18nd("plasma_shell_org.kde.plasma.desktop", "Cancel") onClicked: cancelAction.trigger() } } } //END UI components } diff --git a/imports/activitymanager/sortedactivitiesmodel.h b/imports/activitymanager/sortedactivitiesmodel.h index b51088592..a262e5647 100644 --- a/imports/activitymanager/sortedactivitiesmodel.h +++ b/imports/activitymanager/sortedactivitiesmodel.h @@ -1,94 +1,94 @@ /* * Copyright (C) 2016 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * 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 SORTED_ACTIVITY_MODEL -#define SORTED_ACTIVITY_MODEL +#ifndef SORTED_ACTIVITIES_MODEL_H +#define SORTED_ACTIVITIES_MODEL_H // Qt #include #include //For WId // KDE #include #include #include #include #include class SortedActivitiesModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(bool inhibitUpdates READ inhibitUpdates WRITE setInhibitUpdates NOTIFY inhibitUpdatesChanged) public: SortedActivitiesModel(const QVector &states, QObject *parent = nullptr); ~SortedActivitiesModel() override; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; QString relativeActivity(int relative) const; protected: uint lastUsedTime(const QString &activity) const; bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override; enum AdditionalRoles { LastTimeUsed = KActivities::ActivitiesModel::UserRole, LastTimeUsedString = KActivities::ActivitiesModel::UserRole + 1, WindowCount = KActivities::ActivitiesModel::UserRole + 2, HasWindows = KActivities::ActivitiesModel::UserRole + 3 }; public Q_SLOTS: bool inhibitUpdates() const; void setInhibitUpdates(bool sortByLastUsedTime); void onBackgroundsUpdated(const QStringList &changedBackgrounds); void onCurrentActivityChanged(const QString ¤tActivity); QString activityIdForRow(int row) const; QString activityIdForIndex(const QModelIndex &index) const; int rowForActivityId(const QString &activity) const; void rowChanged(int row, const QVector &roles); void onWindowAdded(WId window); void onWindowRemoved(WId window); void onWindowChanged(WId window, NET::Properties properties, NET::Properties2 properties2); Q_SIGNALS: void inhibitUpdatesChanged(bool inhibitUpdates); private: bool m_inhibitUpdates; QString m_previousActivity; KActivities::ActivitiesModel *m_activitiesModel = nullptr; KActivities::Consumer *m_activities = nullptr; QHash> m_activitiesWindows; }; -#endif // SORTED_ACTIVITY_MODEL +#endif // SORTED_ACTIVITIES_MODEL_H diff --git a/kcms/activities/ExtraActivitiesInterface.h b/kcms/activities/ExtraActivitiesInterface.h index 92ded41b6..2e3abe896 100644 --- a/kcms/activities/ExtraActivitiesInterface.h +++ b/kcms/activities/ExtraActivitiesInterface.h @@ -1,50 +1,50 @@ /* * Copyright (C) 2015 - 2016 by Ivan Cukic * * 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 EXTRA_ACTIVITES_INTERFACE_H -#define EXTRA_ACTIVITES_INTERFACE_H +#ifndef EXTRA_ACTIVITIES_INTERFACE_H +#define EXTRA_ACTIVITIES_INTERFACE_H #include #include #include #include class ExtraActivitiesInterface : public QObject { Q_OBJECT public: explicit ExtraActivitiesInterface(QObject *parent = nullptr); ~ExtraActivitiesInterface(); public Q_SLOTS: void setIsPrivate(const QString &activity, bool isPrivate, QJSValue callback); void getIsPrivate(const QString &activity, QJSValue callback); void setShortcut(const QString &activity, const QKeySequence &keySequence); QKeySequence shortcut(const QString &activity); private: D_PTR; }; -#endif // EXTRA_ACTIVITES_INTERFACE_H +#endif // EXTRA_ACTIVITIES_INTERFACE_H diff --git a/kcms/activities/utils/optional_view.h b/kcms/activities/utils/optional_view.h index 72c613881..004ebb2a9 100644 --- a/kcms/activities/utils/optional_view.h +++ b/kcms/activities/utils/optional_view.h @@ -1,79 +1,79 @@ /* * Copyright (C) 2015 - 2016 by Ivan Cukic * * 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 UTILS_OPTIONAL_H -#define UTILS_OPTIONAL_H +#ifndef UTILS_OPTIONAL_VIEW_H +#define UTILS_OPTIONAL_VIEW_H namespace kamd { namespace utils { struct none_t {}; inline const none_t none() { return none_t(); } // A simple implementation of the optional class // until we can rely on std::optional. // It is not going to come close in the supported // features to the std one. // (we need it in the core library, so we don't // want to use boost.optional) template class optional_view { public: explicit optional_view(const T &value) : m_value(&value) { } optional_view(const none_t &) : m_value(nullptr) { } bool is_initialized() const { return m_value != nullptr; } const T &get() const { return *m_value; } const T *operator->() const { return m_value; } private: const T *const m_value; }; template optional_view make_optional_view(const T &value) { return optional_view(value); } } // namespace utils } // namespace kamd -#endif // UTILS_OPTIONAL_H +#endif // UTILS_OPTIONAL_VIEW_H diff --git a/kcms/baloo/folderselectionwidget.cpp b/kcms/baloo/folderselectionwidget.cpp index 76caa8b04..e8a4c5ebd 100644 --- a/kcms/baloo/folderselectionwidget.cpp +++ b/kcms/baloo/folderselectionwidget.cpp @@ -1,355 +1,355 @@ /* * This file is part of the KDE Baloo Project * Copyright (C) 2014 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "folderselectionwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include FolderSelectionWidget::FolderSelectionWidget(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) { m_listWidget = new QListWidget(this); m_listWidget->setAlternatingRowColors(true); m_messageWidget = new KMessageWidget(this); m_messageWidget->hide(); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(m_messageWidget); layout->addWidget(m_listWidget); QHBoxLayout* hLayout = new QHBoxLayout; QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); hLayout->addItem(spacer); m_addButton = new QPushButton(this); m_addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); connect(m_addButton, &QPushButton::clicked, this, &FolderSelectionWidget::slotAddButtonClicked); m_removeButton = new QPushButton(this); m_removeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_removeButton->setEnabled(false); connect(m_removeButton, &QPushButton::clicked, this, &FolderSelectionWidget::slotRemoveButtonClicked); connect(m_listWidget, &QListWidget::currentItemChanged, this, &FolderSelectionWidget::slotCurrentItemChanged); hLayout->addWidget(m_addButton); hLayout->addWidget(m_removeButton); layout->addItem(hLayout); } namespace { QStringList addTrailingSlashes(const QStringList& input) { QStringList output; Q_FOREACH (QString str, input) { if (!str.endsWith(QDir::separator())) str.append(QDir::separator()); output << str; } return output; } QString makeHomePretty(const QString& url) { if (url.startsWith(QDir::homePath())) return QString(url).replace(0, QDir::homePath().length(), QStringLiteral("~")); return url; } } void FolderSelectionWidget::setDirectoryList(QStringList includeDirs, QStringList exclude) { m_listWidget->clear(); m_mountPoints.clear(); QList devices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); Q_FOREACH (const Solid::Device& dev, devices) { const Solid::StorageAccess* sa = dev.as(); if (!sa->isAccessible()) continue; QString mountPath = sa->filePath(); if (!shouldShowMountPoint(mountPath)) continue; m_mountPoints << mountPath; } m_mountPoints << QDir::homePath(); m_mountPoints = addTrailingSlashes(m_mountPoints); includeDirs = addTrailingSlashes(includeDirs); exclude = addTrailingSlashes(exclude); QStringList excludeList = exclude; Q_FOREACH (const QString& mountPath, m_mountPoints) { if (includeDirs.contains(mountPath)) continue; if (exclude.contains(mountPath)) continue; if (!excludeList.contains(mountPath)) { excludeList << mountPath; } } Q_FOREACH (QString url, excludeList) { QListWidgetItem* item = new QListWidgetItem(m_listWidget); QString display = folderDisplayName(url); item->setData(Qt::DisplayRole, display); item->setData(Qt::WhatsThisRole, url); item->setData(UrlRole, url); item->setData(Qt::DecorationRole, QIcon::fromTheme(iconName(url))); item->setToolTip(makeHomePretty(url)); m_listWidget->addItem(item); } if (m_listWidget->count() == 0) { m_removeButton->setEnabled(false); } } QStringList FolderSelectionWidget::includeFolders() const { QStringList folders; Q_FOREACH (const QString& mountPath, m_mountPoints) { bool inExclude = false; for (int i=0; icount(); ++i) { QListWidgetItem* item = m_listWidget->item(i); QString url = item->data(UrlRole).toString(); if (mountPath == url) { inExclude = true; break; } } if (!inExclude && !folders.contains(mountPath)) { folders << mountPath; } } return folders; } QStringList FolderSelectionWidget::excludeFolders() const { QStringList folders; for (int i=0; icount(); ++i) { QListWidgetItem* item = m_listWidget->item(i); QString url = item->data(UrlRole).toString(); if (!folders.contains(url)) { folders << url; } } return folders; } QString FolderSelectionWidget::fetchMountPoint(const QString& url) const { QString mountPoint; Q_FOREACH (const QString& mount, m_mountPoints) { if (url.startsWith(mount) && mount.size() > mountPoint.size()) mountPoint = mount; } return mountPoint; } void FolderSelectionWidget::slotAddButtonClicked() { QString url = QFileDialog::getExistingDirectory(this, i18n("Select the folder which should be excluded")); if (url.isEmpty()) { return; } if (!url.endsWith(QDir::separator())) url.append(QDir::separator()); // We don't care about the root dir if (url == QLatin1String("/")) { showMessage(i18n("Not allowed to exclude root folder, please disable File Search if you do not want it")); return; } // Remove any existing folder with that name // Remove any folder which is a sub-folder QVector deleteList; for (int i=0; icount(); ++i) { QListWidgetItem* item = m_listWidget->item(i); QString existingUrl = item->data(UrlRole).toString(); if (existingUrl == url) { QString name = QUrl::fromLocalFile(url).fileName(); showMessage(i18n("Folder %1 is already excluded", name)); deleteList << item; continue; } QString existingMountPoint = fetchMountPoint(existingUrl); QString mountPoint = fetchMountPoint(url); if (existingMountPoint == mountPoint) { // existingUrl is not required since it comes under url if (existingUrl.startsWith(url)) { deleteList << item; } else if (url.startsWith(existingUrl)) { // No point adding ourselves since our parents exists // we just move the parent to the bottom url = existingUrl; deleteList << item; QString name = QUrl::fromLocalFile(url).adjusted(QUrl::StripTrailingSlash).fileName(); showMessage(i18n("Folder's parent %1 is already excluded", name)); } } } qDeleteAll(deleteList); QListWidgetItem* item = new QListWidgetItem(m_listWidget); QString displayName = folderDisplayName(url); item->setData(Qt::DisplayRole, displayName); item->setData(Qt::WhatsThisRole, url); item->setData(UrlRole, url); item->setData(Qt::DecorationRole, QIcon::fromTheme(iconName(url))); item->setToolTip(makeHomePretty(url)); m_listWidget->addItem(item); m_listWidget->setCurrentItem(item); Q_EMIT changed(); } void FolderSelectionWidget::slotRemoveButtonClicked() { QListWidgetItem* item = m_listWidget->currentItem(); delete item; Q_EMIT changed(); } void FolderSelectionWidget::slotCurrentItemChanged(QListWidgetItem* current, QListWidgetItem*) { m_removeButton->setEnabled(current != 0); } void FolderSelectionWidget::showMessage(const QString& message) { m_messageWidget->setText(message); m_messageWidget->setMessageType(KMessageWidget::Warning); m_messageWidget->animatedShow(); QTimer::singleShot(3000, m_messageWidget, &KMessageWidget::animatedHide); } QString FolderSelectionWidget::folderDisplayName(const QString& url) const { QString name = url; // Check Home Dir QString homePath = QDir::homePath() + QLatin1Char('/'); if (url == homePath) { return QDir(homePath).dirName(); } if (url.startsWith(homePath)) { name = url.mid(homePath.size()); } else { // Check Mount allMountPointsExcluded Q_FOREACH (QString mountPoint, m_mountPoints) { if (url.startsWith(mountPoint)) { name = QLatin1Char('[') + QDir(mountPoint).dirName() + QStringLiteral("]/") + url.mid(mountPoint.length()); break; } } } if (name.endsWith(QLatin1Char('/'))) { name = name.mid(0, name.size() - 1); } return name; } bool FolderSelectionWidget::shouldShowMountPoint(const QString& mountPoint) { if (mountPoint == QLatin1String("/")) return false; if (mountPoint.startsWith(QLatin1String("/boot"))) return false; if (mountPoint.startsWith(QLatin1String("/tmp"))) return false; // The user's home directory is forcibly added so we can ignore /home - // if /home actually contains the home direcory + // if /home actually contains the home directory if (mountPoint.startsWith(QLatin1String("/home")) && QDir::homePath().startsWith(QLatin1String("/home"))) return false; return true; } QString FolderSelectionWidget::iconName(QString path) const { // Ensure paths end with / if (!path.endsWith(QDir::separator())) path.append(QDir::separator()); QString homePath = QDir::homePath(); if (!homePath.endsWith(QDir::separator())) homePath.append(QDir::separator()); if (path == homePath) return QStringLiteral("user-home"); if (m_mountPoints.contains(path)) return QStringLiteral("drive-harddisk"); return QStringLiteral("folder"); } diff --git a/kcms/colors/previewwidget.h b/kcms/colors/previewwidget.h index 0657c68a6..473a9ed7e 100644 --- a/kcms/colors/previewwidget.h +++ b/kcms/colors/previewwidget.h @@ -1,49 +1,49 @@ /* Preview widget for KDE Display color scheme setup module * Copyright (C) 2007 Matthew Woehlke * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef __SCHEMEPREVIEW_H__ -#define __SCHEMEPREVIEW_H__ +#ifndef __PREVIEWWIDGET_H__ +#define __PREVIEWWIDGET_H__ #include #include #include #include "ui_preview.h" /** * The Desktop/Colors tab in kcontrol. */ class PreviewWidget : public QFrame, Ui::preview { Q_OBJECT public: PreviewWidget(QWidget *parent); ~PreviewWidget() override; void setPalette(const KSharedConfigPtr &config, QPalette::ColorGroup state = QPalette::Active); protected: void setPaletteRecursive(QWidget*, const QPalette&); bool eventFilter(QObject *, QEvent *) override; }; #endif diff --git a/kcms/colors/setpreviewwidget.h b/kcms/colors/setpreviewwidget.h index 557760bd5..5e44b830f 100644 --- a/kcms/colors/setpreviewwidget.h +++ b/kcms/colors/setpreviewwidget.h @@ -1,49 +1,49 @@ /* Colorset Preview widget for KDE Display color scheme setup module * Copyright (C) 2008 Matthew Woehlke * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ -#ifndef __SCHEMEPREVIEWSET_H__ -#define __SCHEMEPREVIEWSET_H__ +#ifndef __SETPREVIEWWIDGET_H__ +#define __SETPREVIEWWIDGET_H__ #include #include #include #include #include "ui_setpreview.h" /** * The Desktop/Colors tab in kcontrol. */ class SetPreviewWidget : public QFrame, Ui::setpreview { Q_OBJECT public: explicit SetPreviewWidget(QWidget *parent); ~SetPreviewWidget() override; void setPalette(const KSharedConfigPtr &config, KColorScheme::ColorSet); protected: void setPaletteRecursive(QWidget*, const QPalette&); bool eventFilter(QObject *, QEvent *) override; }; #endif diff --git a/kcms/cursortheme/kcmcursortheme.h b/kcms/cursortheme/kcmcursortheme.h index 3035297a9..f36975ed0 100644 --- a/kcms/cursortheme/kcmcursortheme.h +++ b/kcms/cursortheme/kcmcursortheme.h @@ -1,140 +1,140 @@ /* * Copyright © 2003-2007 Fredrik Höglund * * 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 KCMCURSORTHEME_H #define KCMCURSORTHEME_H #include #include class QStandardItemModel; class QTemporaryFile; class CursorThemeModel; class SortProxyModel; class CursorTheme; class CursorThemeConfig : public KQuickAddons::ConfigModule { Q_OBJECT Q_PROPERTY(bool canInstall READ canInstall WRITE setCanInstall NOTIFY canInstallChanged) Q_PROPERTY(bool canResize READ canResize WRITE setCanResize NOTIFY canResizeChanged) Q_PROPERTY(bool canConfigure READ canConfigure WRITE setCanConfigure NOTIFY canConfigureChanged) Q_PROPERTY(QAbstractItemModel *cursorsModel READ cursorsModel CONSTANT) Q_PROPERTY(QAbstractItemModel *sizesModel READ sizesModel CONSTANT) Q_PROPERTY(int selectedThemeRow READ selectedThemeRow WRITE setSelectedThemeRow NOTIFY selectedThemeRowChanged) Q_PROPERTY(int preferredSize READ preferredSize WRITE setPreferredSize NOTIFY preferredSizeChanged) public: CursorThemeConfig(QObject *parent, const QVariantList &); ~CursorThemeConfig(); public: void load() override; void save() override; void defaults() override; //for QML properties bool canInstall() const; void setCanInstall(bool can); bool canResize() const; void setCanResize(bool can); bool canConfigure() const; void setCanConfigure(bool can); int selectedThemeRow() const; void setSelectedThemeRow(int row); /** @returns 0 if in the UI "automatic size" is selected, otherwise returns the custom size. */ int preferredSize() const; void setPreferredSize(int size); QAbstractItemModel *cursorsModel(); QAbstractItemModel *sizesModel(); Q_SIGNALS: void canInstallChanged(); void canResizeChanged(); void canConfigureChanged(); void selectedThemeRowChanged(); void preferredSizeChanged(); void showSuccessMessage(const QString &message); void showInfoMessage(const QString &message); void showErrorMessage(const QString &message); public Q_SLOTS: void getNewClicked(); void installThemeFromFile(const QUrl &url); void removeTheme(int row); private Q_SLOTS: void selectionChanged(); /** Updates the size combo box. It loads the size list of the selected cursor theme with the corresponding icons and chooses an appropriate entry. It enables the combo box and the label if the theme provides more than one size, otherwise it disables it. If the size setting is looked in kiosk mode, it stays always disabled. */ void updateSizeComboBox(); private: QModelIndex selectedIndex() const; void installThemeFile(const QString &path); /** Applies a given theme, using XFixes, XCursor and KGlobalSettings. @param theme The cursor theme to be applied. It is save to pass 0 here (will result in \e false as return value). @param size The size hint that is used to select the cursor size. @returns If the changes could be applied. Will return \e false if \e theme is 0 or if the XFixes and XCursor libraries aren't available in the required version, otherwise returns \e true. */ bool applyTheme(const CursorTheme *theme, const int size); bool iconsIsWritable() const; CursorThemeModel *m_model; SortProxyModel *m_proxyModel; QStandardItemModel *m_sizesModel; int m_appliedSize; // This index refers to the CursorThemeModel, not the proxy or the view QPersistentModelIndex m_appliedIndex; -/** Holds the last size that was choosen by the user. Example: The user chooses +/** Holds the last size that was chosen by the user. Example: The user chooses theme1 which provides the sizes 24 and 36. He chooses 36. preferredSize gets set to 36. Now, he switchs to theme2 which provides the sizes 30 and 40. preferredSize still is 36, so the UI will default to 40, which is next to 36. Now, he chooses theme3 which provides the sizes 34 and 44. preferredSize is still 36, so the UI defaults to 34. Now the user changes manually to 44. This will also change preferredSize. */ int m_preferredSize; int m_originalPreferredSize; int m_selectedThemeRow; int m_originalSelectedThemeRow; bool m_canInstall; bool m_canResize; bool m_canConfigure; QScopedPointer m_tempInstallFile; }; #endif diff --git a/kcms/emoticons/emoticonslist.cpp b/kcms/emoticons/emoticonslist.cpp index 1863fb12b..8b15a0da3 100644 --- a/kcms/emoticons/emoticonslist.cpp +++ b/kcms/emoticons/emoticonslist.cpp @@ -1,490 +1,489 @@ /*************************************************************************** * Copyright (C) 2007 by Carlo Segato * * Copyright (C) 2008 Montel Laurent * * * * 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 "emoticonslist.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include EditDialog::EditDialog(QWidget *parent, const QString &name) : KDialog(parent) { setCaption(name); setupDlg(); } EditDialog::EditDialog(QWidget *parent, const QString &name, QListWidgetItem *itm, const QString &file) : KDialog(parent) { setCaption(name); emoticon = file; setupDlg(); leText->setText(itm->text()); btnIcon->setIcon(itm->icon()); } void EditDialog::setupDlg() { wdg = new QWidget(this); QVBoxLayout *vl = new QVBoxLayout; QHBoxLayout *hb = new QHBoxLayout; leText = new KLineEdit(this); btnIcon = new QPushButton(this); btnIcon->setFixedSize(QSize(64, 64)); btnIcon->setIconSize(QSize(64, 64)); QLabel *lab = new QLabel(i18n("Insert the string for the emoticon. If you want multiple strings, separate them by spaces."), wdg); lab->setWordWrap(true); vl->addWidget(lab); hb->addWidget(btnIcon); hb->addWidget(leText); vl->addLayout(hb); wdg->setLayout(vl); setMainWidget(wdg); connect(btnIcon, &QPushButton::clicked, this, &EditDialog::btnIconClicked); connect(leText, &KLineEdit::textChanged, this, &EditDialog::updateOkButton); updateOkButton(); leText->setFocus(); } void EditDialog::btnIconClicked() { const QUrl url = KFileDialog::getImageOpenUrl(); if (!url.isLocalFile()) return; emoticon = url.toLocalFile(); if (emoticon.isEmpty()) return; btnIcon->setIcon(QPixmap(emoticon)); updateOkButton(); } void EditDialog::updateOkButton() { enableButtonOk(!leText->text().isEmpty() && !emoticon.isEmpty()); } K_PLUGIN_FACTORY(EmoticonsFactory, registerPlugin();) EmoticonList::EmoticonList(QWidget *parent, const QVariantList &args) : KCModule(parent, args) { KAboutData *about = new KAboutData(QStringLiteral("kcm_emoticons"), i18n("Emoticons"), QStringLiteral("1.0"), QString(), KAboutLicense::GPL); setAboutData(about); // setButtons(Apply | Help); setupUi(this); btAdd->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); btEdit->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); btRemoveEmoticon->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); btNew->setIcon(QIcon::fromTheme(QStringLiteral("document-new"))); btGetNew->setIcon(QIcon::fromTheme(QStringLiteral("get-hot-new-stuff"))); btInstall->setIcon(QIcon::fromTheme(QStringLiteral("document-import"))); btRemoveTheme->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete"))); connect(themeList, &QListWidget::itemSelectionChanged, this, &EmoticonList::selectTheme); connect(themeList, &QListWidget::itemSelectionChanged, this, &EmoticonList::updateButton); connect(btRemoveTheme, &QPushButton::clicked, this, &EmoticonList::btRemoveThemeClicked); connect(btInstall, &QPushButton::clicked, this, &EmoticonList::installEmoticonTheme); connect(btNew, &QPushButton::clicked, this, &EmoticonList::newTheme); connect(btGetNew, &QPushButton::clicked, this, &EmoticonList::getNewStuff); connect(cbStrict, &QCheckBox::clicked, this, &EmoticonList::somethingChanged); connect(btAdd, &QPushButton::clicked, this, &EmoticonList::addEmoticon); connect(btEdit, &QPushButton::clicked, this, &EmoticonList::editEmoticon); connect(btRemoveEmoticon, &QPushButton::clicked, this, &EmoticonList::btRemoveEmoticonClicked); connect(emoList, &QListWidget::itemSelectionChanged, this, &EmoticonList::updateButton); connect(emoList, &QListWidget::itemDoubleClicked, this, &EmoticonList::editEmoticon); } EmoticonList::~EmoticonList() { } bool caseInsensitiveLessThan(const QString &s1, const QString &s2) { return (QString::localeAwareCompare(s1, s2) < 0); } void EmoticonList::load() { delFiles.clear(); themeList->clear(); emoMap.clear(); emoList->clear(); QStringList themeList = kEmoticons.themeList(); qSort(themeList.begin(), themeList.end(), caseInsensitiveLessThan); for (int i = 0; i < themeList.count(); i++) { loadTheme(themeList.at(i)); } if (kEmoticons.parseMode() & KEmoticonsTheme::StrictParse) { cbStrict->setChecked(true); } else { cbStrict->setChecked(false); } updateButton(); emit changed(false); } void EmoticonList::save() { for (int i = 0; i < delFiles.size(); i++) { KIO::NetAccess::del(QUrl(delFiles.at(i)), this); } foreach (KEmoticonsTheme t, emoMap) { t.save(); } if (themeList->currentItem()) { kEmoticons.setTheme(themeList->currentItem()->text()); } if (cbStrict->isChecked()) { kEmoticons.setParseMode((kEmoticons.parseMode() |= KEmoticonsTheme::StrictParse) &= ~KEmoticonsTheme::RelaxedParse); } else { kEmoticons.setParseMode((kEmoticons.parseMode() |= KEmoticonsTheme::RelaxedParse) &= ~KEmoticonsTheme::StrictParse); } } void EmoticonList::somethingChanged() { emit changed(true); } void EmoticonList::updateButton() { const bool can = canEditTheme(); btRemoveEmoticon->setEnabled(themeList->currentItem() && emoList->selectedItems().size() && can); btRemoveTheme->setEnabled(themeList->currentItem() && themeList->currentItem()->text() != QLatin1String("Glass") && themeList->count() > 1 && can); btEdit->setEnabled(themeList->currentItem() && emoList->selectedItems().size() && can); btAdd->setEnabled(themeList->currentItem() && can); } void EmoticonList::selectTheme() { qDebug() << "current_item: " << themeList->currentItem(); updateButton(); if (!themeList->currentItem()) { emoList->clear(); return; } if (!themeList->currentItem()) { themeList->currentItem()->setSelected(true); return; } emoList->clear(); KEmoticonsTheme em = emoMap.value(themeList->currentItem()->text()); QHash::const_iterator it = em.emoticonsMap().constBegin(); for (; it != em.emoticonsMap().constEnd(); ++it) { QString text; if (it.value().size()) { text = it.value().at(0); for (int i = 1; i < it.value().size(); i++) { text += QLatin1Char(' ') + it.value().at(i); } } new QListWidgetItem(QIcon(it.key()), text, emoList); } emit changed(); } void EmoticonList::btRemoveThemeClicked() { if (!themeList->currentItem()) { return; } const QString name = themeList->currentItem()->text(); delFiles.append(KStandardDirs::locate("emoticons", name + QDir::separator())); delete themeList->currentItem(); emoMap.remove(name); emit changed(); } void EmoticonList::installEmoticonTheme() { const QUrl themeURL = KUrlRequesterDialog::getUrl(QUrl(), this, i18n("Drag or Type Emoticon Theme URL")); if (themeURL.isEmpty()) return; if (!themeURL.isLocalFile()) { KMessageBox::queuedMessageBox(this, KMessageBox::Error, i18n("Emoticon themes must be installed from local files."), i18n("Could Not Install Emoticon Theme")); return; } const QStringList installed = kEmoticons.installTheme(themeURL.toLocalFile()); for (int i = 0; i < installed.size(); i++) loadTheme(installed.at(i)); } void EmoticonList::btRemoveEmoticonClicked() { if (!emoList->currentItem()) { return; } QListWidgetItem *itm = emoList->currentItem(); KEmoticonsTheme theme = emoMap.value(themeList->currentItem()->text()); const QString fPath = theme.emoticonsMap().key(itm->text().split(QLatin1Char(' '))); if (theme.removeEmoticon(itm->text())) { int ret = KMessageBox::questionYesNo(this, i18n("Do you want to remove %1 too?", fPath), i18n("Delete emoticon")); if (ret == KMessageBox::Yes) { delFiles.append(fPath); } delete itm; themeList->currentItem()->setIcon(QIcon(previewEmoticon(theme))); emit changed(); } } void EmoticonList::addEmoticon() { if (!themeList->currentItem()) return; EditDialog *dlg = new EditDialog(this, i18n("Add Emoticon")); if (dlg->exec() == QDialog::Rejected) { delete dlg; return; } KEmoticonsTheme theme = emoMap.value(themeList->currentItem()->text()); if (theme.addEmoticon(dlg->getEmoticon(), dlg->getText(), KEmoticonsProvider::Copy)) { new QListWidgetItem(QPixmap(dlg->getEmoticon()), dlg->getText(), emoList); themeList->currentItem()->setIcon(QIcon(previewEmoticon(theme))); emit changed(); } delete dlg; } void EmoticonList::editEmoticon() { if (!themeList->currentItem() || !emoList->currentItem()) return; KEmoticonsTheme theme = emoMap.value(themeList->currentItem()->text()); const QString path = theme.emoticonsMap().key(emoList->currentItem()->text().split(' ')); const QString f = QFileInfo(path).fileName(); EditDialog *dlg = new EditDialog(this, i18n("Edit Emoticon"), emoList->currentItem(), path); if (dlg->exec() == QDialog::Rejected) { delete dlg; return; } bool copy; QString emo = dlg->getEmoticon(); if (path != dlg->getEmoticon()) { copy = true; } else { copy = false; KStandardDirs *dir = KGlobal::dirs(); emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f); if (emo.isNull()) emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f + QStringLiteral(".mng")); if (emo.isNull()) emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f + QStringLiteral(".png")); if (emo.isNull()) emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f + QStringLiteral(".gif")); if (emo.isNull()) emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f + QStringLiteral(".jpg")); if (emo.isNull()) emo = dir->findResource("emoticons", themeList->currentItem()->text() + QDir::separator() + f + QStringLiteral(".jpeg")); if (emo.isNull()) { delete dlg; return; } } if (theme.removeEmoticon(emoList->currentItem()->text())) { delete emoList->currentItem(); } if (theme.addEmoticon(emo, dlg->getText(), copy ? KEmoticonsProvider::Copy : KEmoticonsProvider::DoNotCopy)) { new QListWidgetItem(QPixmap(emo), dlg->getText(), emoList); } emit changed(); delete dlg; } void EmoticonList::newTheme() { const QString name = KInputDialog::getText(i18n("New Emoticon Theme"), i18n("Enter the name of the new emoticon theme:")); if (name.isEmpty()) return; const QString path = KGlobal::dirs()->saveLocation("emoticons", name, false); if (KIO::NetAccess::exists(QUrl(path), KIO::NetAccess::SourceSide, this)) { KMessageBox::error(this, i18n("%1 theme already exists", name)); } else { const QString constraint(QStringLiteral("(exist Library)")); KService::List srv = KServiceTypeTrader::self()->query(QStringLiteral("KEmoticons"), constraint); QStringList ls; ls.reserve(srv.size()); int current = 0; for (int i = 0; i < srv.size(); ++i) { ls << srv.at(i)->name(); if (srv.at(i)->property(QStringLiteral("X-KDE-Priority")).toInt() > srv.at(current)->property(QStringLiteral("X-KDE-Priority")).toInt()) { current = i; } } bool ok; const QString type = KInputDialog::getItem(i18n("New Emoticon Theme"), i18n("Choose the type of emoticon theme to create"), ls, current, false, &ok, this); if (ok && !type.isEmpty()) { int index = ls.indexOf(type); kEmoticons.newTheme(name, srv.at(index)); loadTheme(name); } } } void EmoticonList::loadTheme(const QString &name) { if (name.isEmpty()) return; if (emoMap.contains(name)) { emoMap.remove(name); QListls = themeList->findItems(name, Qt::MatchExactly); if (ls.size()) { delete ls.at(0); } } KEmoticonsTheme emo = kEmoticons.theme(name); if (!emo.isNull()) { emoMap[name] = emo; QIcon previewIcon = QIcon(previewEmoticon(emo)); QListWidgetItem *itm = new QListWidgetItem(previewIcon, name, themeList); if (name == kEmoticons.currentThemeName()) { themeList->setCurrentItem(itm); } } } void EmoticonList::getNewStuff() { KNS3::DownloadDialog dialog(QStringLiteral("emoticons.knsrc"), this); dialog.exec(); if (!dialog.changedEntries().isEmpty()) { KNS3::Entry::List entries = dialog.changedEntries(); for (int i = 0; i < entries.size(); i ++) { if (entries.at(i).status() == KNS3::Entry::Installed && !entries.at(i).installedFiles().isEmpty()) { QString name = entries.at(i).installedFiles().at(0).section(QLatin1Char('/'), -2, -2); loadTheme(name); } else if (entries.at(i).status() == KNS3::Entry::Deleted) { QString name = entries.at(i).uninstalledFiles().at(0).section(QLatin1Char('/'), -2, -2); QList ls = themeList->findItems(name, Qt::MatchExactly); if (ls.size()) { delete ls.at(0); emoMap.remove(name); } } } } } QString EmoticonList::previewEmoticon(const KEmoticonsTheme &theme) { QString path = theme.tokenize(QStringLiteral(":)"))[0].picPath; if (path.isEmpty()) { path = theme.emoticonsMap().keys().value(0); } return path; } void EmoticonList::initDefaults() { QListls = themeList->findItems(QStringLiteral("Glass"), Qt::MatchExactly); if (ls.isEmpty()) return; themeList->setCurrentItem( ls.first() ); cbStrict->setChecked(false); } void EmoticonList::defaults() { initDefaults(); selectTheme(); emit changed(true); } bool EmoticonList::canEditTheme() { if (!themeList->currentItem()) { return false; } KEmoticonsTheme theme = emoMap.value(themeList->currentItem()->text()); QFileInfo inf(theme.themePath() + QLatin1Char('/') + theme.fileName()); return inf.isWritable(); } #include "emoticonslist.moc" // kate: space-indent on; indent-width 4; replace-tabs on; diff --git a/kcms/formats/writeexports.h b/kcms/formats/writeexports.h index be001e254..3faceccd9 100644 --- a/kcms/formats/writeexports.h +++ b/kcms/formats/writeexports.h @@ -1,103 +1,108 @@ /* * Copyright 2014 Sebastian Kügler * * 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 */ +#ifndef WRITEEXPORTS_H +#define WRITEEXPORTS_H + #include #include #include #include const static QString configFile = QStringLiteral("plasma-localerc"); const static QString exportFile = QStringLiteral("plasma-locale-settings.sh"); const static QString lcLang = QStringLiteral("LANG"); const static QString lcNumeric = QStringLiteral("LC_NUMERIC"); const static QString lcTime = QStringLiteral("LC_TIME"); const static QString lcMonetary = QStringLiteral("LC_MONETARY"); const static QString lcMeasurement = QStringLiteral("LC_MEASUREMENT"); const static QString lcCollate = QStringLiteral("LC_COLLATE"); const static QString lcCtype = QStringLiteral("LC_CTYPE"); const static QString lcLanguage = QStringLiteral("LANGUAGE"); void writeExports() { const QString configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + exportFile; QString script(QStringLiteral("# Generated script, do not edit\n")); script.append(QLatin1String("# Exports language-format specific env vars from startkde.\n")); script.append(QLatin1String("# This script has been generated from kcmshell5 formats.\n")); script.append(QLatin1String("# It will automatically be overwritten from there.\n")); KConfigGroup formatsConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Formats"); KConfigGroup languageConfig = KConfigGroup(KSharedConfig::openConfig(configFile), "Translations"); const QString _export = QStringLiteral("export "); // Formats, uses LC_* and LANG variables const QString lang = formatsConfig.readEntry(lcLang, QString()); if (!lang.isEmpty()) { script.append(_export + lcLang + QLatin1Char('=') + lang + QLatin1Char('\n')); } const QString numeric = formatsConfig.readEntry(lcNumeric, QString()); if (!numeric.isEmpty()) { script.append(_export + lcNumeric + QLatin1Char('=') + numeric + QLatin1Char('\n')); } const QString time = formatsConfig.readEntry(lcTime, QString()); if (!time.isEmpty()) { script.append(_export + lcTime + QLatin1Char('=') + time + QLatin1Char('\n')); } const QString monetary = formatsConfig.readEntry(lcMonetary, QString()); if (!monetary.isEmpty()) { script.append(_export + lcMonetary + QLatin1Char('=') + monetary + QLatin1Char('\n')); } const QString measurement = formatsConfig.readEntry(lcMeasurement, QString()); if (!measurement.isEmpty()) { script.append(_export + lcMeasurement + QLatin1Char('=') + measurement + QLatin1Char('\n')); } const QString collate = formatsConfig.readEntry(lcCollate, QString()); if (!collate.isEmpty()) { script.append(_export + lcCollate + QLatin1Char('=') + collate + QLatin1Char('\n')); } const QString ctype = formatsConfig.readEntry(lcCtype, QString()); if (!ctype.isEmpty()) { script.append(_export + lcCtype + QLatin1Char('=') + ctype + QLatin1Char('\n')); } // Translations, uses LANGUAGE variable const QString language = languageConfig.readEntry(lcLanguage, QString()); if (!language.isEmpty()) { script.append(_export + lcLanguage + QLatin1Char('=') + language + QLatin1Char('\n')); } QFile file(configPath); file.open(QIODevice::WriteOnly | QIODevice::Text); QTextStream out(&file); qDebug() << "Wrote script: " << configPath << "\n" << script; out << script; file.close(); } + +#endif diff --git a/kcms/keys/globalshortcuts.h b/kcms/keys/globalshortcuts.h index 3f6c53644..3880a0890 100644 --- a/kcms/keys/globalshortcuts.h +++ b/kcms/keys/globalshortcuts.h @@ -1,44 +1,44 @@ /* * Copyright 2007 Andreas Pakulat * Copyright 2008 Michael Jansen * * 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 SHORTCUTS_MODULE_H -#define SHORTCUTS_MODULE_H +#ifndef GLOBAL_SHORTCUTS_H +#define GLOBAL_SHORTCUTS_H #include #include class KGlobalShortcutsEditor; class GlobalShortcutsModule : public KCModule { Q_OBJECT public: GlobalShortcutsModule(QWidget *parent, const QVariantList &args); ~GlobalShortcutsModule(); void save() override; void load() override; void defaults() override; private: KGlobalShortcutsEditor *editor; }; #endif diff --git a/kcms/kfontinst/kcmfontinst/GroupList.cpp b/kcms/kfontinst/kcmfontinst/GroupList.cpp index 0871445ee..4e34ac54e 100644 --- a/kcms/kfontinst/kcmfontinst/GroupList.cpp +++ b/kcms/kfontinst/kcmfontinst/GroupList.cpp @@ -1,1026 +1,1026 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "GroupList.h" #include "FontList.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FcEngine.h" #include "Misc.h" #include "KfiConstants.h" namespace KFI { #define GROUPS_DOC "groups" #define GROUP_TAG "group" #define NAME_ATTR "name" #define FAMILY_TAG "family" enum EGroupColumns { COL_GROUP_NAME, NUM_GROUP_COLS }; CGroupListItem::CGroupListItem(const QString &name) : itsName(name), itsType(CUSTOM), itsHighlighted(false), itsStatus(CFamilyItem::ENABLED) { itsData.validated=false; } CGroupListItem::CGroupListItem(EType type, CGroupList *p) : itsType(type), itsHighlighted(false), itsStatus(CFamilyItem::ENABLED) { switch(itsType) { case ALL: itsName=i18n("All Fonts"); break; case PERSONAL: itsName=i18n("Personal Fonts"); break; case SYSTEM: itsName=i18n("System Fonts"); break; default: itsName=i18n("Unclassified"); } itsData.parent=p; } bool CGroupListItem::hasFont(const CFontItem *fnt) const { switch(itsType) { case CUSTOM: return itsFamilies.contains(fnt->family()); case PERSONAL: return !fnt->isSystem(); case SYSTEM: return fnt->isSystem(); case ALL: return true; case UNCLASSIFIED: { QList::ConstIterator it(itsData.parent->itsGroups.begin()), end(itsData.parent->itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom() && (*it)->families().contains(fnt->family())) return false; return true; } default: return false; } return false; } void CGroupListItem::updateStatus(QSet &enabled, QSet &disabled, QSet &partial) { QSet families(itsFamilies); if(0!=families.intersect(partial).count()) itsStatus=CFamilyItem::PARTIAL; else { families=itsFamilies; bool haveEnabled(0!=families.intersect(enabled).count()); families=itsFamilies; bool haveDisabled(0!=families.intersect(disabled).count()); if(haveEnabled && haveDisabled) itsStatus=CFamilyItem::PARTIAL; else if(haveEnabled && !haveDisabled) itsStatus=CFamilyItem::ENABLED; else itsStatus=CFamilyItem::DISABLED; } } bool CGroupListItem::load(QDomElement &elem) { if(elem.hasAttribute(NAME_ATTR)) { itsName=elem.attribute(NAME_ATTR); addFamilies(elem); return true; } return false; } bool CGroupListItem::addFamilies(QDomElement &elem) { int b4(itsFamilies.count()); for(QDomNode n=elem.firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement ent=n.toElement(); if(FAMILY_TAG==ent.tagName()) itsFamilies.insert(ent.text()); } return b4!=itsFamilies.count(); } void CGroupListItem::save(QTextStream &str) { str << " <" GROUP_TAG " " NAME_ATTR "=\"" << Misc::encodeText(itsName, str) << "\">" << endl; if(itsFamilies.count()) { QSet::ConstIterator it(itsFamilies.begin()), end(itsFamilies.end()); for(; it!=end; ++it) str << " <" FAMILY_TAG ">" << Misc::encodeText(*it, str) << "" << endl; } str << " " << endl; } CGroupList::CGroupList(QWidget *parent) : QAbstractItemModel(parent), itsTimeStamp(0), itsModified(false), itsParent(parent), itsSortOrder(Qt::AscendingOrder) { itsSpecialGroups[CGroupListItem::ALL]=new CGroupListItem(CGroupListItem::ALL, this); itsGroups.append(itsSpecialGroups[CGroupListItem::ALL]); if(Misc::root()) itsSpecialGroups[CGroupListItem::PERSONAL]= itsSpecialGroups[CGroupListItem::SYSTEM]=NULL; else { itsSpecialGroups[CGroupListItem::PERSONAL]=new CGroupListItem(CGroupListItem::PERSONAL, this); itsGroups.append(itsSpecialGroups[CGroupListItem::PERSONAL]); itsSpecialGroups[CGroupListItem::SYSTEM]=new CGroupListItem(CGroupListItem::SYSTEM, this); itsGroups.append(itsSpecialGroups[CGroupListItem::SYSTEM]); } itsSpecialGroups[CGroupListItem::UNCLASSIFIED]= new CGroupListItem(CGroupListItem::UNCLASSIFIED, this); // Locate groups.xml file - normall will be ~/.config/fontgroups.xml QString path(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/'); if(!Misc::dExists(path)) Misc::createDir(path); itsFileName=path+'/'+KFI_GROUPS_FILE; rescan(); } CGroupList::~CGroupList() { save(); qDeleteAll(itsGroups); itsGroups.clear(); } int CGroupList::columnCount(const QModelIndex &) const { return NUM_GROUP_COLS; } void CGroupList::update(const QModelIndex &unHighlight, const QModelIndex &highlight) { if(unHighlight.isValid()) { CGroupListItem *grp=static_cast(unHighlight.internalPointer()); if(grp) grp->setHighlighted(false); emit dataChanged(unHighlight, unHighlight); } if(highlight.isValid()) { CGroupListItem *grp=static_cast(highlight.internalPointer()); if(grp) grp->setHighlighted(true); emit dataChanged(highlight, highlight); } } void CGroupList::updateStatus(QSet &enabled, QSet &disabled, QSet &partial) { QList::Iterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom()) (*it)->updateStatus(enabled, disabled, partial); emit layoutChanged(); } inline QColor midColour(const QColor &a, const QColor &b) { return QColor((a.red()+b.red())>>1, (a.green()+b.green())>>1, (a.blue()+b.blue())>>1); } QVariant CGroupList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); CGroupListItem *grp=static_cast(index.internalPointer()); if(grp) switch(index.column()) { case COL_GROUP_NAME: switch(role) { case Qt::FontRole: if(CGroupListItem::SYSTEM==grp->type()) { QFont font; font.setItalic(true); return font; } break; case Qt::SizeHintRole: { const int s = KIconLoader::global()->currentSize(KIconLoader::Small); return QSize(s, s + 4); } case Qt::EditRole: case Qt::DisplayRole: return grp->name(); case Qt::DecorationRole: if(grp->highlighted()) switch(grp->type()) { case CGroupListItem::ALL: // Removing from a group return SmallIcon("list-remove"); case CGroupListItem::PERSONAL: // Copying/moving case CGroupListItem::SYSTEM: // Copying/moving return SmallIcon(Qt::LeftToRight==QApplication::layoutDirection() ? "go-next" : "go-previous"); case CGroupListItem::CUSTOM: // Adding to a group return SmallIcon("list-add"); default: break; } else switch(grp->type()) { case CGroupListItem::ALL: return SmallIcon("preferences-desktop-font"); case CGroupListItem::PERSONAL: return SmallIcon("user-identity"); case CGroupListItem::SYSTEM: return SmallIcon("computer"); case CGroupListItem::UNCLASSIFIED: return SmallIcon("fontstatus"); case CGroupListItem::CUSTOM: if(0==grp->families().count()) return SmallIcon("image-missing"); switch(grp->status()) { case CFamilyItem::PARTIAL: return SmallIcon("dialog-ok", 0, KIconLoader::DisabledState); case CFamilyItem::ENABLED: return SmallIcon("dialog-ok"); case CFamilyItem::DISABLED: return SmallIcon("dialog-cancel"); } break; } default: break; } break; } return QVariant(); } bool CGroupList::setData(const QModelIndex &index, const QVariant &value, int role) { if(Qt::EditRole==role && index.isValid()) { QString name(value.toString().trimmed()); if(!name.isEmpty()) { CGroupListItem *grp=static_cast(index.internalPointer()); if(grp && grp->isCustom() && grp->name()!=name && !exists(name, false)) { grp->setName(name); itsModified=true; save(); sort(0, itsSortOrder); return true; } } } return false; } Qt::ItemFlags CGroupList::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; CGroupListItem *grp=static_cast(index.internalPointer()); return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | (grp && grp->type()==CGroupListItem::CUSTOM ? Qt::ItemIsEditable : Qt::NoItemFlags); } QVariant CGroupList::headerData(int section, Qt::Orientation orientation, int role) const { if (Qt::Horizontal==orientation && COL_GROUP_NAME==section) switch(role) { case Qt::DisplayRole: return i18n("Group"); case Qt::TextAlignmentRole: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); case Qt::WhatsThisRole: return whatsThis(); default: break; } return QVariant(); } QModelIndex CGroupList::index(int row, int column, const QModelIndex &parent) const { if(!parent.isValid()) { CGroupListItem *grp=itsGroups.value(row); if(grp) return createIndex(row, column, grp); } return QModelIndex(); } QModelIndex CGroupList::parent(const QModelIndex &) const { return QModelIndex(); } int CGroupList::rowCount(const QModelIndex &) const { return itsGroups.count(); } void CGroupList::rescan() { save(); load(); sort(0, itsSortOrder); } void CGroupList::load() { time_t ts=Misc::getTimeStamp(itsFileName); if(!ts || ts!=itsTimeStamp) { clear(); itsTimeStamp=ts; if(load(itsFileName)) itsModified=false; } } bool CGroupList::load(const QString &file) { QFile f(file); bool rv(false); if(f.open(QIODevice::ReadOnly)) { QDomDocument doc; if(doc.setContent(&f)) for(QDomNode n=doc.documentElement().firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement e=n.toElement(); if(GROUP_TAG==e.tagName() && e.hasAttribute(NAME_ATTR)) { QString name(e.attribute(NAME_ATTR)); CGroupListItem *item=find(name); if(!item) { item=new CGroupListItem(name); if(!itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.append(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); itsGroups.append(item); rv=true; } if(item->addFamilies(e)) rv=true; } } } return rv; } bool CGroupList::save() { if(itsModified && save(itsFileName, NULL)) { itsTimeStamp=Misc::getTimeStamp(itsFileName); return true; } return false; } bool CGroupList::save(const QString &fileName, CGroupListItem *grp) { QSaveFile file(fileName); if (file.open(QIODevice::WriteOnly)) { QTextStream str(&file); str << "<" GROUPS_DOC ">" << endl; if(grp) grp->save(str); else { QList::Iterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom()) (*it)->save(str); } str << "" << endl; itsModified=false; return file.commit(); } return false; } void CGroupList::merge(const QString &file) { if(load(file)) { itsModified=true; sort(0, itsSortOrder); } } void CGroupList::clear() { beginResetModel(); itsGroups.removeFirst(); // Remove all if(itsSpecialGroups[CGroupListItem::SYSTEM]) { itsGroups.removeFirst(); // Remove personal itsGroups.removeFirst(); // Remove system } if(itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.removeFirst(); // Remove unclassif... qDeleteAll(itsGroups); itsGroups.clear(); itsGroups.append(itsSpecialGroups[CGroupListItem::ALL]); if(itsSpecialGroups[CGroupListItem::SYSTEM]) { itsGroups.append(itsSpecialGroups[CGroupListItem::PERSONAL]); itsGroups.append(itsSpecialGroups[CGroupListItem::SYSTEM]); } - // Dont add 'Unclassif' until we have some user groups + // Don't add 'Unclassif' until we have some user groups endResetModel(); } QModelIndex CGroupList::index(CGroupListItem::EType t) { return createIndex(t, 0, itsSpecialGroups[t]); } void CGroupList::createGroup(const QString &name) { if(!exists(name)) { if(!itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.append(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); itsGroups.append(new CGroupListItem(name)); itsModified=true; save(); sort(0, itsSortOrder); } } bool CGroupList::removeGroup(const QModelIndex &idx) { if(idx.isValid()) { CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp && grp->isCustom() && KMessageBox::Yes==KMessageBox::warningYesNo(itsParent, i18n("

Do you really want to remove \'%1\'?

" "

This will only remove the group, and not " "the actual fonts.

", grp->name()), i18n("Remove Group"), KGuiItem(i18n("Remove"), "list-remove", i18n("Remove group")))) { itsModified=true; itsGroups.removeAll(grp); int stdGroups=1 +// All (itsSpecialGroups[CGroupListItem::SYSTEM] ? 2 : 0)+ // Personal, System 1; // Unclassified if(stdGroups==itsGroups.count() && itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.removeAll(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); delete grp; save(); sort(0, itsSortOrder); return true; } } return false; } void CGroupList::removeFromGroup(const QModelIndex &group, const QSet &families) { if(group.isValid()) { CGroupListItem *grp=static_cast(group.internalPointer()); if(grp && grp->isCustom()) { QSet::ConstIterator it(families.begin()), end(families.end()); bool update(false); for(; it!=end; ++it) if(removeFromGroup(grp, *it)) update=true; if(update) emit refresh(); } } } QString CGroupList::whatsThis() const { return i18n("

Font Groups

This list displays the font groups available on your system. " "There are 2 main types of font groups:" "

  • Standard are special groups used by the font manager.
      %1
  • " "
  • Custom are groups created by you. To add a font family to one of " "these groups simply drag it from the list of fonts, and drop " "onto the desired group. To remove a family from the group, drag " "the font onto the \"All Fonts\" group.
  • " "

", Misc::root() ? i18n("
  • All Fonts contains all the fonts installed on your system.
  • " "
  • Unclassified contains all fonts that have not yet been placed " "within a \"Custom\" group.
  • ") : i18n("
  • All Fonts contains all the fonts installed on your system - " "both \"System\" and \"Personal\".
  • " "
  • System contains all fonts that are installed system-wide (i.e. " "available to all users).
  • " "
  • Personal contains your personal fonts.
  • " "
  • Unclassified contains all fonts that have not yet been placed " "within a \"Custom\" group.
  • ")); } void CGroupList::addToGroup(const QModelIndex &group, const QSet &families) { if(group.isValid()) { CGroupListItem *grp=static_cast(group.internalPointer()); if(grp && grp->isCustom()) { QSet::ConstIterator it(families.begin()), end(families.end()); bool update(false); for(; it!=end; ++it) if(!grp->hasFamily(*it)) { grp->addFamily(*it); update=true; itsModified=true; } if(update) emit refresh(); } } } void CGroupList::removeFamily(const QString &family) { QList::ConstIterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) removeFromGroup(*it, family); } bool CGroupList::removeFromGroup(CGroupListItem *grp, const QString &family) { if(grp && grp->isCustom() && grp->hasFamily(family)) { grp->removeFamily(family); itsModified=true; return true; } return false; } static bool groupNameLessThan(const CGroupListItem *f1, const CGroupListItem *f2) { return f1 && f2 && (f1->type()type() || (f1->type()==f2->type() && QString::localeAwareCompare(f1->name(), f2->name())<0)); } static bool groupNameGreaterThan(const CGroupListItem *f1, const CGroupListItem *f2) { return f1 && f2 && (f1->type()type() || (f1->type()==f2->type() && QString::localeAwareCompare(f1->name(), f2->name())>0)); } void CGroupList::sort(int, Qt::SortOrder order) { itsSortOrder=order; qSort(itsGroups.begin(), itsGroups.end(), Qt::AscendingOrder==order ? groupNameLessThan : groupNameGreaterThan); emit layoutChanged(); } Qt::DropActions CGroupList::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList CGroupList::mimeTypes() const { QStringList types; types << KFI_FONT_DRAG_MIME; return types; } CGroupListItem * CGroupList::find(const QString &name) { QList::ConstIterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->name()==name) return (*it); return NULL; } bool CGroupList::exists(const QString &name, bool showDialog) { if(NULL!=find(name)) { if(showDialog) KMessageBox::error(itsParent, i18n("A group named \'%1\' already " "exists.", name)); return true; } return false; } class CGroupListViewDelegate : public QStyledItemDelegate { public: CGroupListViewDelegate(QObject *p) : QStyledItemDelegate(p) { } virtual ~CGroupListViewDelegate() { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override { CGroupListItem *grp=static_cast(idx.internalPointer()); QStyleOptionViewItem opt(option); if(grp && grp->isUnclassified()) opt.rect.adjust(0, 0, 0, -1); QStyledItemDelegate::paint(painter, opt, idx); if(grp && grp->isUnclassified()) { opt.rect.adjust(2, 0, -2, 1); painter->setPen(QApplication::palette().color(QPalette::Text)); painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight()); } } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const override { QSize sz(QStyledItemDelegate::sizeHint(option, idx)); CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp && grp->isUnclassified()) sz.setHeight(sz.height()+1); return sz; } static bool isCloseEvent(QKeyEvent *event) { return Qt::Key_Tab==event->key() || Qt::Key_Backtab==event->key() || Qt::Key_Enter==event->key() || Qt::Key_Return==event->key(); } bool eventFilter(QObject *editor, QEvent *event) override { if(editor && event && QEvent::KeyPress==event->type() && isCloseEvent(static_cast(event)) && qobject_cast(editor)) { QString text=static_cast(editor)->text().trimmed(); if(!text.isEmpty() && !static_cast(static_cast(parent())->model())->exists(text, false)) { emit commitData(static_cast(editor)); emit closeEditor(static_cast(editor)); return true; } } return false; } }; CGroupListView::CGroupListView(QWidget *parent, CGroupList *model) : QTreeView(parent) { setModel(model); setItemDelegate(new CGroupListViewDelegate(this)); sortByColumn(COL_GROUP_NAME, Qt::AscendingOrder); setSelectionMode(QAbstractItemView::SingleSelection); setSortingEnabled(true); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setAcceptDrops(true); setDragDropMode(QAbstractItemView::DropOnly); setDropIndicatorShown(true); setDragEnabled(false); header()->setSortIndicatorShown(true); setRootIsDecorated(false); itsMenu=new QMenu(this); itsDeleteAct=itsMenu->addAction(QIcon::fromTheme("list-remove"), i18n("Remove"), this, SIGNAL(del())); itsMenu->addSeparator(); itsEnableAct=itsMenu->addAction(QIcon::fromTheme("enablefont"), i18n("Enable"), this, SIGNAL(enable())); itsDisableAct=itsMenu->addAction(QIcon::fromTheme("disablefont"), i18n("Disable"), this, SIGNAL(disable())); itsMenu->addSeparator(); itsRenameAct=itsMenu->addAction(QIcon::fromTheme("edit-rename"), i18n("Rename..."), this, SLOT(rename())); if(!Misc::app(KFI_PRINTER).isEmpty()) { itsMenu->addSeparator(); itsPrintAct=itsMenu->addAction(QIcon::fromTheme("document-print"), i18n("Print..."), this, SIGNAL(print())); } else itsPrintAct=0L; itsMenu->addSeparator(); itsExportAct=itsMenu->addAction(QIcon::fromTheme("document-export"), i18n("Export..."), this, SIGNAL(zip())); setWhatsThis(model->whatsThis()); header()->setWhatsThis(whatsThis()); connect(this, SIGNAL(addFamilies(QModelIndex,QSet)), model, SLOT(addToGroup(QModelIndex,QSet))); connect(this, SIGNAL(removeFamilies(QModelIndex,QSet)), model, SLOT(removeFromGroup(QModelIndex,QSet))); } CGroupListItem::EType CGroupListView::getType() { QModelIndexList selectedItems(selectedIndexes()); if(selectedItems.count() && selectedItems.last().isValid()) { CGroupListItem *grp=static_cast(selectedItems.last().internalPointer()); return grp->type(); } return CGroupListItem::ALL; } void CGroupListView::controlMenu(bool del, bool en, bool dis, bool p, bool exp) { itsDeleteAct->setEnabled(del); itsRenameAct->setEnabled(del); itsEnableAct->setEnabled(en); itsDisableAct->setEnabled(dis); if(itsPrintAct) itsPrintAct->setEnabled(p); itsExportAct->setEnabled(exp); } void CGroupListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList deselectedItems(deselected.indexes()); QAbstractItemView::selectionChanged(selected, deselected); QModelIndexList selectedItems(selectedIndexes()); if(0==selectedItems.count() && 1==deselectedItems.count()) selectionModel()->select(deselectedItems.last(), QItemSelectionModel::Select); else emit itemSelected(selectedItems.count() ? selectedItems.last() : QModelIndex()); } void CGroupListView::rename() { QModelIndex index(currentIndex()); if(index.isValid()) edit(index); } void CGroupListView::emitMoveFonts() { emit moveFonts(); } void CGroupListView::contextMenuEvent(QContextMenuEvent *ev) { if(indexAt(ev->pos()).isValid()) itsMenu->popup(ev->globalPos()); } void CGroupListView::dragEnterEvent(QDragEnterEvent *event) { if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) event->acceptProposedAction(); } void CGroupListView::dragMoveEvent(QDragMoveEvent *event) { if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) { QModelIndex index(indexAt(event->pos())); if(index.isValid()) { if(COL_GROUP_NAME!=index.column()) index=((CGroupList *)model())->createIdx(index.row(), COL_GROUP_NAME, index.internalPointer()); CGroupListItem *dest=static_cast(index.internalPointer()); CGroupListItem::EType type=getType(); if(dest) if(!selectedIndexes().contains(index)) { bool ok(true); if(dest->isCustom()) emit info(i18n("Add to \"%1\".", dest->name())); else if(CGroupListItem::CUSTOM==type && dest->isAll()) emit info(i18n("Remove from current group.")); else if(!Misc::root() && dest->isPersonal() && CGroupListItem::SYSTEM==type) emit info(i18n("Move to personal folder.")); else if(!Misc::root() && dest->isSystem() && CGroupListItem::PERSONAL==type) emit info(i18n("Move to system folder.")); else ok=false; if(ok) { drawHighlighter(index); event->acceptProposedAction(); return; } } } event->ignore(); drawHighlighter(QModelIndex()); emit info(QString()); } } void CGroupListView::dragLeaveEvent(QDragLeaveEvent *) { drawHighlighter(QModelIndex()); emit info(QString()); } void CGroupListView::dropEvent(QDropEvent *event) { emit info(QString()); drawHighlighter(QModelIndex()); if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) { event->acceptProposedAction(); QSet families; QByteArray encodedData(event->mimeData()->data(KFI_FONT_DRAG_MIME)); QDataStream ds(&encodedData, QIODevice::ReadOnly); QModelIndex from(selectedIndexes().last()), to(indexAt(event->pos())); ds >> families; // Are we mvoeing/copying, removing a font from the current group? if(to.isValid() && from.isValid()) { if( ((static_cast(from.internalPointer()))->isSystem() && (static_cast(to.internalPointer()))->isPersonal()) || ((static_cast(from.internalPointer()))->isPersonal() && (static_cast(to.internalPointer()))->isSystem())) QTimer::singleShot(0, this, SLOT(emitMoveFonts())); else if((static_cast(from.internalPointer()))->isCustom() && !(static_cast(to.internalPointer()))->isCustom()) emit removeFamilies(from, families); else emit addFamilies(to, families); } if(isUnclassified()) emit unclassifiedChanged(); } } void CGroupListView::drawHighlighter(const QModelIndex &idx) { if(itsCurrentDropItem!=idx) { ((CGroupList *)model())->update(itsCurrentDropItem, idx); itsCurrentDropItem=idx; } } bool CGroupListView::viewportEvent(QEvent *event) { executeDelayedItemsLayout(); return QTreeView::viewportEvent(event); } } diff --git a/kcms/kfontinst/viewpart/CharTip.cpp b/kcms/kfontinst/viewpart/CharTip.cpp index 68fdf18b2..90de98d37 100644 --- a/kcms/kfontinst/viewpart/CharTip.cpp +++ b/kcms/kfontinst/viewpart/CharTip.cpp @@ -1,297 +1,296 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * Inspired by konq_filetip.cc * * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2000, 2001, 2002 David Faure * Copyright (C) 2004 Martin Koller */ #include "CharTip.h" #include "FontPreview.h" #include "UnicodeCategories.h" #include #include #include #include #include #include #include #include -#include #include namespace KFI { EUnicodeCategory getCategory(quint32 ucs2) { for(int i=0; UNICODE_INVALID!=constUnicodeCategoryList[i].category; ++i) if(constUnicodeCategoryList[i].start<=ucs2 && constUnicodeCategoryList[i].end>=ucs2) return constUnicodeCategoryList[i].category; return UNICODE_UNASSIGNED; } static QString toStr(EUnicodeCategory cat) { switch (cat) { case UNICODE_CONTROL: return i18n("Other, Control"); case UNICODE_FORMAT: return i18n("Other, Format"); case UNICODE_UNASSIGNED: return i18n("Other, Not Assigned"); case UNICODE_PRIVATE_USE: return i18n("Other, Private Use"); case UNICODE_SURROGATE: return i18n("Other, Surrogate"); case UNICODE_LOWERCASE_LETTER: return i18n("Letter, Lowercase"); case UNICODE_MODIFIER_LETTER: return i18n("Letter, Modifier"); case UNICODE_OTHER_LETTER: return i18n("Letter, Other"); case UNICODE_TITLECASE_LETTER: return i18n("Letter, Titlecase"); case UNICODE_UPPERCASE_LETTER: return i18n("Letter, Uppercase"); case UNICODE_COMBINING_MARK: return i18n("Mark, Spacing Combining"); case UNICODE_ENCLOSING_MARK: return i18n("Mark, Enclosing"); case UNICODE_NON_SPACING_MARK: return i18n("Mark, Non-Spacing"); case UNICODE_DECIMAL_NUMBER: return i18n("Number, Decimal Digit"); case UNICODE_LETTER_NUMBER: return i18n("Number, Letter"); case UNICODE_OTHER_NUMBER: return i18n("Number, Other"); case UNICODE_CONNECT_PUNCTUATION: return i18n("Punctuation, Connector"); case UNICODE_DASH_PUNCTUATION: return i18n("Punctuation, Dash"); case UNICODE_CLOSE_PUNCTUATION: return i18n("Punctuation, Close"); case UNICODE_FINAL_PUNCTUATION: return i18n("Punctuation, Final Quote"); case UNICODE_INITIAL_PUNCTUATION: return i18n("Punctuation, Initial Quote"); case UNICODE_OTHER_PUNCTUATION: return i18n("Punctuation, Other"); case UNICODE_OPEN_PUNCTUATION: return i18n("Punctuation, Open"); case UNICODE_CURRENCY_SYMBOL: return i18n("Symbol, Currency"); case UNICODE_MODIFIER_SYMBOL: return i18n("Symbol, Modifier"); case UNICODE_MATH_SYMBOL: return i18n("Symbol, Math"); case UNICODE_OTHER_SYMBOL: return i18n("Symbol, Other"); case UNICODE_LINE_SEPARATOR: return i18n("Separator, Line"); case UNICODE_PARAGRAPH_SEPARATOR: return i18n("Separator, Paragraph"); case UNICODE_SPACE_SEPARATOR: return i18n("Separator, Space"); default: return ""; } } CCharTip::CCharTip(CFontPreview *parent) : QFrame(0, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint), itsParent(parent) { itsPixmapLabel = new QLabel(this); itsLabel = new QLabel(this); itsTimer = new QTimer(this); QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, this); layout->setMargin(8); layout->setSpacing(0); layout->addWidget(itsPixmapLabel); layout->addWidget(itsLabel); setPalette(QToolTip::palette()); setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); hide(); } CCharTip::~CCharTip() { } void CCharTip::setItem(const CFcEngine::TChar &ch) { hideTip(); itsItem=ch; itsTimer->disconnect(this); connect(itsTimer, SIGNAL(timeout()), this, SLOT(showTip())); itsTimer->setSingleShot(true); itsTimer->start(300); } void CCharTip::showTip() { if(!itsParent->underMouse()) return; static const int constPixSize=96; EUnicodeCategory cat(getCategory(itsItem.ucs4)); QString details(""); details+=""; details+=""; QString str(QString::fromUcs4(&(itsItem.ucs4), 1)); details+=""; details+=""; details+="
    "+i18n("Category")+" "+ toStr(cat)+"
    "+i18n("UCS-4")+" "+ QString().sprintf("U+%4.4X", itsItem.ucs4)+" 
    "+i18n("UTF-16")+" "; const ushort *utf16(str.utf16()); for(int i=0; utf16[i]; ++i) { if(i) details+=' '; details+=QString().sprintf("0x%4.4X", utf16[i]); } details+="
    "+i18n("UTF-8")+" "; QByteArray utf8(str.toUtf8()); for(int i=0; i below is just to stop Qt converting the xml entry into // a character! if ((0x0001 <= itsItem.ucs4 && itsItem.ucs4 <= 0xD7FF) || (0xE000 <= itsItem.ucs4 && itsItem.ucs4 <= 0xFFFD) || (0x10000 <= itsItem.ucs4 && itsItem.ucs4 <= 0x10FFFF)) details+="
    "+i18n("XML Decimal Entity")+" "+ QString().sprintf("&#%d;", itsItem.ucs4)+"
    "; itsLabel->setText(details); QList range; range.append(CFcEngine::TRange(itsItem.ucs4, 0)); QColor bgnd(Qt::white); bgnd.setAlpha(0); QImage img=itsParent->engine()->draw(itsParent->itsFontName, itsParent->itsStyleInfo, itsParent->itsCurrentFace-1, palette().text().color(), bgnd, constPixSize, constPixSize, false, range, NULL); if(!img.isNull()) itsPixmapLabel->setPixmap(QPixmap::fromImage(img)); else itsPixmapLabel->setPixmap(QPixmap()); itsTimer->disconnect(this); connect(itsTimer, SIGNAL(timeout()), this, SLOT(hideTip())); itsTimer->setSingleShot(true); itsTimer->start(15000); qApp->installEventFilter(this); reposition(); show(); } void CCharTip::hideTip() { itsTimer->stop(); qApp->removeEventFilter(this); hide(); } void CCharTip::reposition() { QRect rect(itsItem); rect.moveTopRight(itsParent->mapToGlobal(rect.topRight())); QPoint pos(rect.center()); QRect desk(QApplication::desktop()->screenGeometry(rect.center())); if ((rect.center().x() + width()) > desk.right()) { if (pos.x() - width() < 0) pos.setX(0); else pos.setX( pos.x() - width() ); } // should the tooltip be shown above or below the ivi ? if (rect.bottom() + height() > desk.bottom()) pos.setY( rect.top() - height() ); else pos.setY(rect.bottom() + 1); move(pos); update(); } void CCharTip::resizeEvent(QResizeEvent *event) { QFrame::resizeEvent(event); reposition(); } bool CCharTip::eventFilter(QObject *, QEvent *e) { switch (e->type()) { case QEvent::Leave: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::Wheel: hideTip(); default: break; } return false; } } diff --git a/kcms/migrationlib/kdelibs4config.h b/kcms/migrationlib/kdelibs4config.h index 074f53298..f28e31ff3 100644 --- a/kcms/migrationlib/kdelibs4config.h +++ b/kcms/migrationlib/kdelibs4config.h @@ -1,41 +1,45 @@ /* * * Copyright (C) 2014 David Edmundson * * 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 KDELIBS4CONFIG_H +#define KDELIBS4CONFIG_H + #include #include #include class Kdelibs4SharedConfig { public: static void syncConfigGroup(const QLatin1String &sourceGroup, const QString &fileName) { Kdelibs4Migration migration; QString configDirPath = migration.saveLocation("config"); KSharedConfigPtr kde4Config = KSharedConfig::openConfig(configDirPath + '/' + fileName); KSharedConfigPtr simpleConfig = KSharedConfig::openConfig(fileName, KConfig::SimpleConfig); KConfigGroup simpleConfigGroup(simpleConfig, sourceGroup); KConfigGroup kde4ConfigGroup = kde4Config->group(sourceGroup); simpleConfigGroup.copyTo(&kde4ConfigGroup); kde4ConfigGroup.sync(); } }; +#endif diff --git a/kcms/mouse/kcm/libinput/libinput_config.cpp b/kcms/mouse/kcm/libinput/libinput_config.cpp index bb63f6e32..e235e447b 100644 --- a/kcms/mouse/kcm/libinput/libinput_config.cpp +++ b/kcms/mouse/kcm/libinput/libinput_config.cpp @@ -1,230 +1,230 @@ /* * Copyright 2018 Roman Gilg * * 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 "libinput_config.h" #include "../configcontainer.h" #include #include #include #include #include #include #include #include #include #include #include "inputbackend.h" static QVariant getDeviceList(InputBackend *backend) { return QVariant::fromValue(backend->getDevices().toList()); } LibinputConfig::LibinputConfig(ConfigContainer *parent, InputBackend *backend) : ConfigPlugin(parent) { m_backend = backend; KAboutData* data = new KAboutData(QStringLiteral("kcmmouse"), i18n("Pointer device KCM"), QStringLiteral("1.0"), i18n("System Settings module for managing mice and trackballs."), KAboutLicense::GPL_V2, i18n("Copyright 2018 Roman Gilg"), QString()); data->addAuthor(i18n("Roman Gilg"), i18n("Developer"), QStringLiteral("subdiff@gmail.com")); m_parent->setAboutData(data); m_initError = !m_backend->errorString().isNull(); m_view = new QQuickWidget(this); m_errorMessage = new KMessageWidget(this); m_errorMessage->setCloseButtonVisible(false); m_errorMessage->setWordWrap(true); m_errorMessage->setVisible(false); QVBoxLayout *layout = new QVBoxLayout(parent); layout->addWidget(m_errorMessage); layout->addWidget(m_view); parent->setLayout(layout); m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); m_view->setClearColor(Qt::transparent); m_view->setAttribute(Qt::WA_AlwaysStackOnTop); m_view->rootContext()->setContextProperty("backend", m_backend); m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend)); KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(m_view->engine()); kdeclarative.setupBindings(); if (m_backend->mode() == InputBackendMode::XLibinput) { m_view->setSource(QUrl("qrc:/libinput/main_deviceless.qml")); } else { m_view->setSource(QUrl("qrc:/libinput/main.qml")); } if (m_initError) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(m_backend->errorString()); QMetaObject::invokeMethod(m_errorMessage, "animatedShow", Qt::QueuedConnection); } else { connect(m_backend, SIGNAL(deviceAdded(bool)), this, SLOT(onDeviceAdded(bool))); connect(m_backend, SIGNAL(deviceRemoved(int)), this, SLOT(onDeviceRemoved(int))); connect(m_view->rootObject(), SIGNAL(changeSignal()), this, SLOT(onChange())); } m_view->show(); } QSize LibinputConfig::sizeHint() const { return QQmlProperty::read(m_view->rootObject(), "sizeHint").toSize(); } QSize LibinputConfig::minimumSizeHint() const { return QQmlProperty::read(m_view->rootObject(), "minimumSizeHint").toSize(); } void LibinputConfig::load() { // in case of critical init error in backend, don't try if (m_initError) { return; } if (!m_backend->getConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); - m_errorMessage->setText(i18n("Error while loading values. See logs for more informations. Please restart this configuration module.")); + m_errorMessage->setText(i18n("Error while loading values. See logs for more information. Please restart this configuration module.")); m_errorMessage->animatedShow(); } else { if (!m_backend->deviceCount()) { m_errorMessage->setMessageType(KMessageWidget::Information); m_errorMessage->setText(i18n("No pointer device found. Connect now.")); m_errorMessage->animatedShow(); } } QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend"); } void LibinputConfig::save() { if (!m_backend->applyConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); - m_errorMessage->setText(i18n("Not able to save all changes. See logs for more informations. Please restart this configuration module and try again.")); + m_errorMessage->setText(i18n("Not able to save all changes. See logs for more information. Please restart this configuration module and try again.")); m_errorMessage->animatedShow(); } else { hideErrorMessage(); } // load newly written values load(); // in case of error, config still in changed state emit m_parent->changed(m_backend->isChangedConfig()); } void LibinputConfig::defaults() { // in case of critical init error in backend, don't try if (m_initError) { return; } if (!m_backend->getDefaultConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(i18n("Error while loading default values. Failed to set some options to their default values.")); m_errorMessage->animatedShow(); } QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend"); emit m_parent->changed(m_backend->isChangedConfig()); } void LibinputConfig::onChange() { if (!m_backend->deviceCount()) { return; } hideErrorMessage(); emit m_parent->changed(m_backend->isChangedConfig()); } void LibinputConfig::onDeviceAdded(bool success) { QQuickItem *rootObj = m_view->rootObject(); if (!success) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(i18n("Error while adding newly connected device. Please reconnect it and restart this configuration module.")); } int activeIndex; if (m_backend->deviceCount() == 1) { // if no pointer device was connected previously, show the new device and hide the no-device-message activeIndex = 0; hideErrorMessage(); } else { activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt(); } m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend)); QMetaObject::invokeMethod(rootObj, "resetModel", Q_ARG(QVariant, activeIndex)); QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend"); } void LibinputConfig::onDeviceRemoved(int index) { QQuickItem *rootObj = m_view->rootObject(); int activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt(); if (activeIndex == index) { m_errorMessage->setMessageType(KMessageWidget::Information); if (m_backend->deviceCount()) { m_errorMessage->setText(i18n("Pointer device disconnected. Closed its setting dialog.")); } else { m_errorMessage->setText(i18n("Pointer device disconnected. No other devices found.")); } m_errorMessage->animatedShow(); activeIndex = 0; } else { if (index < activeIndex) { activeIndex--; } } m_view->rootContext()->setContextProperty("deviceModel", getDeviceList(m_backend)); QMetaObject::invokeMethod(m_view->rootObject(), "resetModel", Q_ARG(QVariant, activeIndex)); QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend"); emit m_parent->changed(m_backend->isChangedConfig()); } void LibinputConfig::hideErrorMessage() { if (m_errorMessage->isVisible()) { m_errorMessage->animatedHide(); } } diff --git a/kcms/touchpad/src/kcm/libinput/touchpadconfiglibinput.cpp b/kcms/touchpad/src/kcm/libinput/touchpadconfiglibinput.cpp index cd20d8851..a9703249a 100644 --- a/kcms/touchpad/src/kcm/libinput/touchpadconfiglibinput.cpp +++ b/kcms/touchpad/src/kcm/libinput/touchpadconfiglibinput.cpp @@ -1,221 +1,221 @@ /* * Copyright 2017 Roman Gilg * * 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 "touchpadconfiglibinput.h" #include #include #include #include #include #include #include #include #include #include #include "../touchpadconfigcontainer.h" #include "touchpadbackend.h" #include "version.h" TouchpadConfigLibinput::TouchpadConfigLibinput(TouchpadConfigContainer *parent, const QVariantList &args) : TouchpadConfigPlugin(parent) { KAboutData* data = new KAboutData(QStringLiteral("kcm_touchpad"), i18n("Touchpad KCM"), TOUCHPAD_KCM_VERSION, i18n("System Settings module for managing your touchpad"), KAboutLicense::GPL_V2, i18n("Copyright © 2016 Roman Gilg"), QString()); data->addAuthor(i18n("Roman Gilg"), i18n("Developer"), QStringLiteral("subdiff@gmail.com")); m_parent->setAboutData(data); m_backend = TouchpadBackend::implementation(); m_initError = !m_backend->errorString().isNull(); m_view = new QQuickWidget(this); m_errorMessage = new KMessageWidget(this); m_errorMessage->setCloseButtonVisible(false); m_errorMessage->setWordWrap(true); m_errorMessage->setVisible(false); QVBoxLayout *layout = new QVBoxLayout(parent); layout->addWidget(m_errorMessage); layout->addWidget(m_view); parent->setLayout(layout); m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); m_view->setClearColor(Qt::transparent); m_view->setAttribute(Qt::WA_AlwaysStackOnTop); m_view->rootContext()->setContextProperty("backend", m_backend); m_view->rootContext()->setContextProperty("deviceModel", QVariant::fromValue(m_backend->getDevices().toList())); KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(m_view->engine()); kdeclarative.setupBindings(); m_view->setSource(QUrl("qrc:/libinput/main.qml")); if (m_initError) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(m_backend->errorString()); QMetaObject::invokeMethod(m_errorMessage, "animatedShow", Qt::QueuedConnection); } else { connect(m_backend, SIGNAL(touchpadAdded(bool)), this, SLOT(onTouchpadAdded(bool))); connect(m_backend, SIGNAL(touchpadRemoved(int)), this, SLOT(onTouchpadRemoved(int))); connect(m_view->rootObject(), SIGNAL(changeSignal()), this, SLOT(onChange())); } m_view->show(); } QSize TouchpadConfigLibinput::sizeHint() const { return QQmlProperty::read(m_view->rootObject(), "sizeHint").toSize(); } QSize TouchpadConfigLibinput::minimumSizeHint() const { return QQmlProperty::read(m_view->rootObject(), "minimumSizeHint").toSize(); } void TouchpadConfigLibinput::load() { // in case of critical init error in backend, don't try if (m_initError) { return; } if (!m_backend->getConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); - m_errorMessage->setText(i18n("Error while loading values. See logs for more informations. Please restart this configuration module.")); + m_errorMessage->setText(i18n("Error while loading values. See logs for more information. Please restart this configuration module.")); m_errorMessage->animatedShow(); } else { if (!m_backend->touchpadCount()) { m_errorMessage->setMessageType(KMessageWidget::Information); m_errorMessage->setText(i18n("No touchpad found. Connect touchpad now.")); m_errorMessage->animatedShow(); } } QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend"); } void TouchpadConfigLibinput::save() { if (!m_backend->applyConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); - m_errorMessage->setText(i18n("Not able to save all changes. See logs for more informations. Please restart this configuration module and try again.")); + m_errorMessage->setText(i18n("Not able to save all changes. See logs for more information. Please restart this configuration module and try again.")); m_errorMessage->animatedShow(); } else { hideErrorMessage(); } // load newly written values load(); // in case of error, config still in changed state emit m_parent->changed(m_backend->isChangedConfig()); } void TouchpadConfigLibinput::defaults() { // in case of critical init error in backend, don't try if (m_initError) { return; } if (!m_backend->getDefaultConfig()) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(i18n("Error while loading default values. Failed to set some options to their default values.")); m_errorMessage->animatedShow(); } QMetaObject::invokeMethod(m_view->rootObject(), "syncValuesFromBackend"); emit m_parent->changed(m_backend->isChangedConfig()); } void TouchpadConfigLibinput::onChange() { if (!m_backend->touchpadCount()) { return; } hideErrorMessage(); emit m_parent->changed(m_backend->isChangedConfig()); } void TouchpadConfigLibinput::onTouchpadAdded(bool success) { QQuickItem *rootObj = m_view->rootObject(); if (!success) { m_errorMessage->setMessageType(KMessageWidget::Error); m_errorMessage->setText(i18n("Error while adding newly connected device. Please reconnect it and restart this configuration module.")); } int activeIndex; if (m_backend->touchpadCount() == 1) { // if no touchpad was connected previously, show the new device and hide the no-device-message activeIndex = 0; hideErrorMessage(); } else { activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt(); } m_view->rootContext()->setContextProperty("deviceModel", QVariant::fromValue(m_backend->getDevices())); QMetaObject::invokeMethod(rootObj, "resetModel", Q_ARG(QVariant, activeIndex)); QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend"); } void TouchpadConfigLibinput::onTouchpadRemoved(int index) { QQuickItem *rootObj = m_view->rootObject(); int activeIndex = QQmlProperty::read(rootObj, "deviceIndex").toInt(); if (activeIndex == index) { m_errorMessage->setMessageType(KMessageWidget::Information); if (m_backend->touchpadCount()) { m_errorMessage->setText(i18n("Touchpad disconnected. Closed its setting dialog.")); } else { m_errorMessage->setText(i18n("Touchpad disconnected. No other touchpads found.")); } m_errorMessage->animatedShow(); activeIndex = 0; } else { if (index < activeIndex) { activeIndex--; } } m_view->rootContext()->setContextProperty("deviceModel", QVariant::fromValue(m_backend->getDevices())); QMetaObject::invokeMethod(m_view->rootObject(), "resetModel", Q_ARG(QVariant, activeIndex)); QMetaObject::invokeMethod(rootObj, "syncValuesFromBackend"); emit m_parent->changed(m_backend->isChangedConfig()); } void TouchpadConfigLibinput::hideErrorMessage() { if (m_errorMessage->isVisible()) { m_errorMessage->animatedHide(); } } diff --git a/solid-device-automounter/kcm/LayoutSettings.kcfg b/solid-device-automounter/kcm/LayoutSettings.kcfg index bcfd2460b..634913ca0 100644 --- a/solid-device-automounter/kcm/LayoutSettings.kcfg +++ b/solid-device-automounter/kcm/LayoutSettings.kcfg @@ -1,16 +1,16 @@ true false - \ No newline at end of file + diff --git a/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml b/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml index c3713f375..04f382ab0 100644 --- a/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml +++ b/toolboxes/desktoptoolbox/contents/ui/ToolBoxButton.qml @@ -1,312 +1,312 @@ /*************************************************************************** * Copyright 2012,2015 by Sebastian Kügler * * Copyright 2015 by 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) 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.4 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 KQuickControlsAddons import org.kde.plasma.plasmoid 2.0 Item { id: toolBoxButton property string text: main.Plasmoid.activityName property bool isCorner: !buttonMouse.dragging && ((state == "topleft") || (state == "topright") || (state == "bottomright") || (state == "bottomleft")) property bool isHorizontal: (state != "left" && state != "right") rotation: switch(state) { case "left": return -90; case "right": return 90; default: return 0; } transform: Translate { x: state == "left" ? Math.round(-width/2 + height/2) : state == "right" ? + Math.round(width/2 - height/2) : 0 Behavior on x { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } } } transformOrigin: Item.Center Behavior on rotation { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } Behavior on x { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } Behavior on y { NumberAnimation { duration: units.shortDuration * 3; easing.type: Easing.InOutExpo; } enabled: visible } clip: backgroundFrameWidthAnimation.running width: backgroundFrame.width + backgroundFrame.width % 2 height: backgroundFrame.height + backgroundFrame.height % 2 //x and y default to 0, so top left would be correct - //If the position is anything else it will updated via onXChanged during intialisation + //If the position is anything else it will updated via onXChanged during initialization state: "topleft" onXChanged: stateTimer.restart() onYChanged: stateTimer.restart() Timer { id: stateTimer interval: 100 onTriggered: updateState() } function updateState() { var container = main; //print(" w: " + container.width +"x"+container.height+" : "+x+"/"+y+" tbw: " + toolBoxButton.width); var x = toolBoxButton.x - main.x; var y = toolBoxButton.y - main.y; var cornerSnap = iconWidth if (x < cornerSnap && y < cornerSnap) { toolBoxButton.state = "topleft"; } else if (container.width - x - buttonLayout.width < cornerSnap && y < cornerSnap) { toolBoxButton.state = "topright"; } else if (container.width - x - buttonLayout.width < cornerSnap && container.height - y - buttonLayout.width < cornerSnap) { toolBoxButton.state = "bottomright"; } else if (x < cornerSnap && container.height - y - buttonLayout.width < cornerSnap) { toolBoxButton.state = "bottomleft"; //top diagonal half } else if (x > (y * (container.width/container.height))) { //Top edge if (container.width - x > y ) { toolBoxButton.state = "top"; //right edge } else { //toolBoxButton.transformOrigin = Item.BottomRight toolBoxButton.state = "right"; } //bottom diagonal half } else { //Left edge if (container.height - y > x ) { //toolBoxButton.transformOrigin = Item.TopLeft toolBoxButton.state = "left"; //Bottom edge } else { toolBoxButton.state = "bottom"; } } if (!buttonMouse.pressed) { main.placeToolBox(toolBoxButton.state); } stateTimer.running = false; } PlasmaCore.FrameSvgItem { id: backgroundFrame anchors { left: parent.left top: parent.top } imagePath: "widgets/translucentbackground" opacity: buttonMouse.containsMouse || (toolBoxLoader.item && toolBoxLoader.item.visible) ? 1.0 : 0.4 width: Math.round((isCorner ? buttonLayout.height : buttonLayout.width) + margins.horizontal) height: Math.round(buttonLayout.height + margins.vertical) Behavior on width { NumberAnimation { id: backgroundFrameWidthAnimation duration: units.longDuration; easing.type: Easing.InOutQuad; } } Behavior on opacity { NumberAnimation { duration: units.longDuration; } } } Row { id: buttonLayout anchors.centerIn: parent height: Math.max(toolBoxIcon.height, fontMetrics.height) spacing: units.smallSpacing Behavior on x { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutQuad; } } PlasmaCore.SvgItem { id: toolBoxIcon svg: PlasmaCore.Svg { id: iconSvg imagePath: "widgets/configuration-icons" onRepaintNeeded: toolBoxIcon.elementId = iconSvg.hasElement("menu") ? "menu" : "configure" } elementId: iconSvg.hasElement("menu") ? "menu" : "configure" anchors.verticalCenter: parent.verticalCenter width: iconSize height: iconSize opacity: buttonMouse.containsMouse || (toolBoxLoader.item && toolBoxLoader.item.visible) ? 1 : 0.5 rotation: isHorizontal ? 0 : -90; transformOrigin: Item.Center Behavior on opacity { NumberAnimation { duration: units.longDuration; easing.type: Easing.InOutExpo; } } } PlasmaComponents.Label { id: activityName anchors.verticalCenter: parent.verticalCenter opacity: isCorner ? 0 : 1 text: toolBoxButton.text visible: opacity Behavior on opacity { //only have this animation when going from hidden -> shown enabled: activityName.opacity == 0 SequentialAnimation { //pause to allow the toolbox frame to resize //otherwise we see the text overflow the box //whilst that animates PauseAnimation { duration: units.longDuration } NumberAnimation { duration: units.shortDuration easing.type: Easing.InOutExpo } } } } FontMetrics { id: fontMetrics font: activityName.font } } MouseArea { id: buttonMouse property QtObject container: main property int pressedX property int pressedY property bool dragging: false anchors { fill: parent margins: -10 } drag { target: main.Plasmoid.immutable ? undefined : toolBoxButton minimumX: 0 maximumX: container.width - toolBoxIcon.width minimumY: 0 maximumY: container.height - toolBoxIcon.height } hoverEnabled: true onPressed: { pressedX = toolBoxButton.x pressedY = toolBoxButton.y } onPositionChanged: { if (pressed && (Math.abs(toolBoxButton.x - pressedX) > iconSize || Math.abs(toolBoxButton.y - pressedY) > iconSize)) { dragging = true; } } onClicked: { // the dialog auto-closes on losing focus main.open = !main.dialogWasVisible plasmoid.focus = true; } onReleased: { main.Plasmoid.configuration.ToolBoxButtonState = toolBoxButton.state; main.Plasmoid.configuration.ToolBoxButtonX = toolBoxButton.x; main.Plasmoid.configuration.ToolBoxButtonY = toolBoxButton.y; //print("Saved coordinates for ToolBox in config: " + toolBoxButton.x + ", " +toolBoxButton.x); if (dragging) { main.placeToolBox(); } dragging = false; stateTimer.stop(); updateState(); } onCanceled: dragging = false; } states: [ State { name: "topleft" }, State { name: "top" }, State { name: "topright" }, State { name: "right" }, State { name: "bottomright" }, State { name: "bottom" }, State { name: "bottomleft" }, State { name: "topleft" }, State { name: "left" } ] }