diff --git a/applets/kicker/CMakeLists.txt b/applets/kicker/CMakeLists.txt index 033ad6cea..ba8651ec4 100644 --- a/applets/kicker/CMakeLists.txt +++ b/applets/kicker/CMakeLists.txt @@ -1,81 +1,84 @@ add_definitions( -DQT_USE_QSTRINGBUILDER -DQT_NO_CAST_TO_ASCII # -DQT_NO_CAST_FROM_ASCII -DQT_STRICT_ITERATORS -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_USE_FAST_OPERATOR_PLUS -DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.kicker\" ) plasma_install_package(package org.kde.plasma.kicker) set(kickerplugin_SRCS plugin/abstractentry.cpp plugin/abstractmodel.cpp plugin/actionlist.cpp plugin/appentry.cpp plugin/appsmodel.cpp plugin/computermodel.cpp plugin/contactentry.cpp plugin/containmentinterface.cpp plugin/draghelper.cpp - plugin/favoritesmodel.cpp + plugin/simplefavoritesmodel.cpp + plugin/kastatsfavoritesmodel.cpp plugin/fileentry.cpp plugin/forwardingmodel.cpp + plugin/placeholdermodel.cpp plugin/funnelmodel.cpp plugin/dashboardwindow.cpp plugin/kickerplugin.cpp plugin/menuentryeditor.cpp plugin/processrunner.cpp plugin/rootmodel.cpp plugin/runnermodel.cpp plugin/runnermatchesmodel.cpp plugin/recentcontactsmodel.cpp plugin/recentusagemodel.cpp plugin/submenu.cpp plugin/systementry.cpp plugin/systemmodel.cpp plugin/systemsettings.cpp plugin/wheelinterceptor.cpp plugin/windowsystem.cpp + plugin/funnelmodel.cpp ) qt5_add_dbus_interface(kickerplugin_SRCS ${KRUNNERAPP_INTERFACE} krunner_interface) qt5_add_dbus_interface(kickerplugin_SRCS ${KSMSERVER_DBUS_INTERFACE} ksmserver_interface) install(FILES plugin/qmldir DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/kicker) add_library(kickerplugin SHARED ${kickerplugin_SRCS}) target_link_libraries(kickerplugin Qt5::Core Qt5::DBus Qt5::Qml Qt5::Quick Qt5::X11Extras KF5::Activities KF5::ActivitiesStats KF5::ConfigCore KF5::CoreAddons KF5::I18n KF5::ItemModels KF5::KDELibs4Support # FIXME: New Solid power management API doesn't exist yet, so we need to use deprecated stuff. KF5::KIOCore KF5::KIOWidgets KF5::People KF5::PeopleWidgets KF5::PlasmaQuick KF5::Runner KF5::Service KF5::Solid KF5::WindowSystem PW::KWorkspace) if (${HAVE_APPSTREAMQT}) target_link_libraries(kickerplugin AppStreamQt) endif() install(TARGETS kickerplugin DESTINATION ${QML_INSTALL_DIR}/org/kde/plasma/private/kicker) diff --git a/applets/kicker/package/contents/code/tools.js b/applets/kicker/package/contents/code/tools.js index ccfa2c592..c6fc3d9f3 100644 --- a/applets/kicker/package/contents/code/tools.js +++ b/applets/kicker/package/contents/code/tools.js @@ -1,96 +1,202 @@ /*************************************************************************** * Copyright (C) 2013 by Aurélien Gâteau * * Copyright (C) 2013-2015 by Eike Hein * + * Copyright (C) 2017 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) 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 . * ***************************************************************************/ function fillActionMenu(actionMenu, actionList, favoriteModel, favoriteId) { // Accessing actionList can be a costly operation, so we don't // access it until we need the menu. - var action = createFavoriteAction(favoriteModel, favoriteId); + var actions = createFavoriteActions(favoriteModel, favoriteId); - if (action) { + if (actions) { if (actionList && actionList.length > 0) { var separator = { "type": "separator" }; - actionList.unshift(action, separator); + actionList.unshift(separator); + // actionList = actions.concat(actionList); // this crashes Qt O.o + actionList.unshift.apply(actionList, actions); } else { - actionList = [action]; + actionList = actions; } } actionMenu.actionList = actionList; } -function createFavoriteAction(favoriteModel, favoriteId) { +function createFavoriteActions(favoriteModel, favoriteId) { if (favoriteModel === null || !favoriteModel.enabled || favoriteId == null) { return null; } - var action = {}; + var activities = favoriteModel.activities.runningActivities; + + if (activities.length <= 1) { + var action = {}; + + if (favoriteModel.isFavorite(favoriteId)) { + action.text = i18n("Remove from Favorites"); + action.icon = "list-remove"; + action.actionId = "_kicker_favorite_remove"; + } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { + action.text = i18n("Add to Favorites"); + action.icon = "bookmark-new"; + action.actionId = "_kicker_favorite_add"; + } else { + return null; + } + + action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + + return [action]; - if (favoriteModel.isFavorite(favoriteId)) { - action.text = i18n("Remove from Favorites"); - action.icon = "list-remove"; - action.actionId = "_kicker_favorite_remove"; - } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { - action.text = i18n("Add to Favorites"); - action.icon = "bookmark-new"; - action.actionId = "_kicker_favorite_add"; } else { - return null; - } + var actions = []; + + var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); + + // Adding the item to link/unlink to all activities + + var linkedToAllActivities = + !(linkedActivities.indexOf(":global") === -1); + + actions.push({ + text : i18n("On All Activities"), + checkable : true, + + actionId : linkedToAllActivities ? + "_kicker_favorite_remove_from_activity" : + "_kicker_favorite_set_to_activity", + checked : linkedToAllActivities, + + actionArgument : { + favoriteModel: favoriteModel, + favoriteId: favoriteId, + favoriteActivity: "" + } + }); + + + // Adding items for each activity separately + + var addActivityItem = function(activityId, activityName) { + var linkedToThisActivity = + !(linkedActivities.indexOf(activityId) === -1); - action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + actions.push({ + text : activityName, + checkable : true, + checked : linkedToThisActivity && !linkedToAllActivities, - return action; + actionId : + // If we are on all activities, and the user clicks just one + // specific activity, unlink from everything else + linkedToAllActivities ? "_kicker_favorite_set_to_activity" : + + // If we are linked to the current activity, just unlink from + // that single one + linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : + + // Otherwise, link to this activity, but do not unlink from + // other ones + "_kicker_favorite_add_to_activity", + + actionArgument : { + favoriteModel : favoriteModel, + favoriteId : favoriteId, + favoriteActivity : activityId + } + }); + }; + + // Adding the item to link/unlink to the current activity + + addActivityItem(favoriteModel.activities.currentActivity, i18n("On The Current Activity")); + + actions.push({ + type: "separator", + actionId: "_kicker_favorite_separator" + }); + + // Adding the items for each activity + + activities.forEach(function(activityId) { + addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); + }); + + return [{ + text : i18n("Show In Favorites"), + icon : "favorite", + subActions : actions + }]; + } } function triggerAction(model, index, actionId, actionArgument) { function startsWith(txt, needle) { return txt.substr(0, needle.length) === needle; } if (startsWith(actionId, "_kicker_favorite_")) { handleFavoriteAction(actionId, actionArgument); return; } var closeRequested = model.trigger(index, actionId, actionArgument); if (closeRequested) { plasmoid.expanded = false; return true; } return false; } function handleFavoriteAction(actionId, actionArgument) { var favoriteId = actionArgument.favoriteId; var favoriteModel = actionArgument.favoriteModel; + console.log(actionId); + if (favoriteModel === null || favoriteId == null) { return null; } + if (actionId == "_kicker_favorite_remove") { - favoriteModel.removeFavorite(favoriteId); + console.log("Removing from all activities"); + favoriteModel.removeFavoriteFrom(favoriteId, ":any"); + } else if (actionId == "_kicker_favorite_add") { - favoriteModel.addFavorite(favoriteId); + console.log("Adding to global activity"); + favoriteModel.addFavoriteTo(favoriteId, ":global"); + + } else if (actionId == "_kicker_favorite_remove_from_activity") { + console.log("Removing from a specific activity"); + favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_add_to_activity") { + console.log("Adding to another activity"); + favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_set_to_activity") { + console.log("Removing the item from the favourites, and re-adding it just to be on a specific activity"); + favoriteModel.setFavoriteOn(favoriteId, actionArgument.favoriteActivity); + } } diff --git a/applets/kicker/package/contents/config/main.xml b/applets/kicker/package/contents/config/main.xml index 0d296463f..0d75ef1f4 100644 --- a/applets/kicker/package/contents/config/main.xml +++ b/applets/kicker/package/contents/config/main.xml @@ -1,79 +1,83 @@ start-here-kde false 0 false false 0 preferred://browser,kontact.desktop,systemsettings.desktop,org.kde.dolphin.desktop,ktp-contactlist.desktop,org.kde.kate.desktop,org.kde.discover logout,reboot,shutdown + + + false + true true false true bookmarks,baloosearch,locations true diff --git a/applets/kicker/package/contents/ui/ActionMenu.qml b/applets/kicker/package/contents/ui/ActionMenu.qml index e8c340c89..5109aef67 100644 --- a/applets/kicker/package/contents/ui/ActionMenu.qml +++ b/applets/kicker/package/contents/ui/ActionMenu.qml @@ -1,99 +1,138 @@ /*************************************************************************** * 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents Item { id: root property QtObject menu property Item visualParent property variant actionList property bool opened: menu ? (menu.status != PlasmaComponents.DialogStatus.Closed) : false signal actionClicked(string actionId, variant actionArgument) signal closed onActionListChanged: refreshMenu(); onOpenedChanged: { if (!opened) { closed(); } } function open(x, y) { if (!actionList) { return; } if (x && y) { menu.open(x, y); } else { menu.open(); } } function refreshMenu() { if (menu) { menu.destroy(); } if (!actionList) { return; } menu = contextMenuComponent.createObject(root); - actionList.forEach(function(actionItem) { - var item = contextMenuItemComponent.createObject(menu, { - "actionItem": actionItem, - }); + fillMenu(menu, actionList); + } + + function fillMenu(menu, items) { + items.forEach(function(actionItem) { + if (actionItem.subActions) { + // This is a menu + var submenuItem = contextSubmenuItemComponent.createObject( + menu, { "actionItem" : actionItem }); + + fillMenu(submenuItem.submenu, actionItem.subActions); + + } else { + var item = contextMenuItemComponent.createObject( + menu, + { + "actionItem": actionItem, + } + ); + } }); + } Component { id: contextMenuComponent PlasmaComponents.ContextMenu { visualParent: root.visualParent } } Component { - id: contextMenuItemComponent + id: contextSubmenuItemComponent PlasmaComponents.MenuItem { + id: submenuItem + property variant actionItem text: actionItem.text ? actionItem.text : "" - enabled: actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) - separator: actionItem.type == "separator" - section: actionItem.type == "title" icon: actionItem.icon ? actionItem.icon : null + property variant submenu : submenu_ + + PlasmaComponents.ContextMenu { + id: submenu_ + visualParent: submenuItem.action + } + } + } + + Component { + id: contextMenuItemComponent + + PlasmaComponents.MenuItem { + property variant actionItem + + text : actionItem.text ? actionItem.text : "" + enabled : actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) + separator : actionItem.type == "separator" + section : actionItem.type == "title" + icon : actionItem.icon ? actionItem.icon : null + checkable : actionItem.checkable ? actionItem.checkable : false + checked : actionItem.checked ? actionItem.checked : false + onClicked: { actionClicked(actionItem.actionId, actionItem.actionArgument); } } } } diff --git a/applets/kicker/package/contents/ui/main.qml b/applets/kicker/package/contents/ui/main.qml index 742cbbf78..d87571295 100644 --- a/applets/kicker/package/contents/ui/main.qml +++ b/applets/kicker/package/contents/ui/main.qml @@ -1,273 +1,279 @@ /*************************************************************************** * Copyright (C) 2014-2015 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.private.kicker 0.1 as Kicker Item { id: kicker anchors.fill: parent signal reset property bool isDash: (plasmoid.pluginName == "org.kde.plasma.kickerdash") Plasmoid.switchWidth: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumWidth Plasmoid.switchHeight: isDash || !Plasmoid.fullRepresentationItem ? 0 : Plasmoid.fullRepresentationItem.Layout.minimumHeight // this is a bit of a hack to prevent Plasma from spawning a dialog on its own when we're Dash Plasmoid.preferredRepresentation: isDash ? Plasmoid.fullRepresentation : null Plasmoid.compactRepresentation: isDash ? null : compactRepresentation Plasmoid.fullRepresentation: isDash ? compactRepresentation : menuRepresentation property QtObject itemListDialogComponent: Qt.createComponent("ItemListDialog.qml"); property Item dragSource: null property QtObject globalFavorites: rootModel.favoritesModel property QtObject systemFavorites: rootModel.systemFavoritesModel Plasmoid.icon: plasmoid.configuration.useCustomButtonImage ? plasmoid.configuration.customButtonImage : plasmoid.configuration.icon onSystemFavoritesChanged: { systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; } function action_menuedit() { processRunner.runMenuEditor(); } function updateSvgMetrics() { lineSvg.horLineHeight = lineSvg.elementSize("horizontal-line").height; lineSvg.vertLineWidth = lineSvg.elementSize("vertical-line").width; } Component { id: compactRepresentation CompactRepresentation {} } Component { id: menuRepresentation MenuRepresentation {} } Kicker.RootModel { id: rootModel autoPopulate: false appNameFormat: plasmoid.configuration.appNameFormat flat: isDash ? true : plasmoid.configuration.limitDepth sorted: plasmoid.configuration.alphaSort showSeparators: !isDash appletInterface: plasmoid showAllApps: isDash showRecentApps: plasmoid.configuration.showRecentApps showRecentDocs: plasmoid.configuration.showRecentDocs showRecentContacts: plasmoid.configuration.showRecentContacts recentOrdering: plasmoid.configuration.recentOrdering onShowRecentAppsChanged: { plasmoid.configuration.showRecentApps = showRecentApps; } onShowRecentDocsChanged: { plasmoid.configuration.showRecentDocs = showRecentDocs; } onShowRecentContactsChanged: { plasmoid.configuration.showRecentContacts = showRecentContacts; } onRecentOrderingChanged: { plasmoid.configuration.recentOrdering = recentOrdering; } Component.onCompleted: { - favoritesModel.favorites = plasmoid.configuration.favoriteApps; + favoritesModel.initForClient("org.kde.plasma.kicker.favorites.instance-" + plasmoid.id) + + if (!plasmoid.configuration.favoritesPortedToKAstats) { + favoritesModel.portOldFavorites(plasmoid.configuration.favoriteApps); + plasmoid.configuration.favoritesPortedToKAstats = true; + } + rootModel.refresh(); } } Connections { target: globalFavorites onFavoritesChanged: { plasmoid.configuration.favoriteApps = target.favorites; } } Connections { target: systemFavorites onFavoritesChanged: { plasmoid.configuration.favoriteSystemActions = target.favorites; } } Connections { target: plasmoid.configuration onFavoriteAppsChanged: { globalFavorites.favorites = plasmoid.configuration.favoriteApps; } onFavoriteSystemActionsChanged: { systemFavorites.favorites = plasmoid.configuration.favoriteSystemActions; } } Kicker.RunnerModel { id: runnerModel appletInterface: plasmoid favoritesModel: globalFavorites runners: { var runners = new Array("services"); if (isDash) { runners = runners.concat(new Array("desktopsessions", "PowerDevil")); } if (plasmoid.configuration.useExtraRunners) { runners = runners.concat(plasmoid.configuration.extraRunners); } return runners; } deleteWhenEmpty: isDash } Kicker.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium } Kicker.ProcessRunner { id: processRunner; } Kicker.WindowSystem { id: windowSystem; } PlasmaCore.FrameSvgItem { id : highlightItemSvg visible: false imagePath: "widgets/viewitem" prefix: "hover" } PlasmaCore.FrameSvgItem { id : listItemSvg visible: false imagePath: "widgets/listitem" prefix: "normal" } PlasmaCore.Svg { id: arrows imagePath: "widgets/arrows" size: "16x16" } PlasmaCore.Svg { id: lineSvg imagePath: "widgets/line" property int horLineHeight property int vertLineWidth } PlasmaComponents.Label { id: toolTipDelegate width: contentWidth height: contentHeight property Item toolTip text: (toolTip != null) ? toolTip.text : "" } Timer { id: justOpenedTimer repeat: false interval: 600 } Connections { target: plasmoid onExpandedChanged: { if (expanded) { windowSystem.monitorWindowVisibility(plasmoid.fullRepresentationItem); justOpenedTimer.start(); } else { kicker.reset(); } } } function resetDragSource() { dragSource = null; } function enableHideOnWindowDeactivate() { plasmoid.hideOnWindowDeactivate = true; } Component.onCompleted: { if (plasmoid.hasOwnProperty("activationTogglesExpanded")) { plasmoid.activationTogglesExpanded = !isDash } windowSystem.focusOut.connect(enableHideOnWindowDeactivate); plasmoid.hideOnWindowDeactivate = true; if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { plasmoid.setAction("menuedit", i18n("Edit Applications...")); } updateSvgMetrics(); theme.themeChanged.connect(updateSvgMetrics); rootModel.refreshed.connect(reset); dragHelper.dropped.connect(resetDragSource); } } diff --git a/applets/kicker/plugin/abstractentry.cpp b/applets/kicker/plugin/abstractentry.cpp index ac3a8f0bb..5ffab3464 100644 --- a/applets/kicker/plugin/abstractentry.cpp +++ b/applets/kicker/plugin/abstractentry.cpp @@ -1,106 +1,108 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "abstractentry.h" +#include + AbstractEntry::AbstractEntry(AbstractModel *owner) : m_owner(owner) { } AbstractEntry::~AbstractEntry() { } AbstractModel *AbstractEntry::owner() const { return m_owner; } bool AbstractEntry::isValid() const { return true; } QIcon AbstractEntry::icon() const { return QIcon(); } QString AbstractEntry::name() const { return QString(); } QString AbstractEntry::group() const { return QString(); } QString AbstractEntry::description() const { return QString(); } QString AbstractEntry::id() const { return QString(); } QUrl AbstractEntry::url() const { return QUrl(); } bool AbstractEntry::hasChildren() const { return false; } AbstractModel *AbstractEntry::childModel() const { return nullptr; } bool AbstractEntry::hasActions() const { return false; } QVariantList AbstractEntry::actions() const { return QVariantList(); } bool AbstractEntry::run(const QString& actionId, const QVariant &argument) { Q_UNUSED(actionId) Q_UNUSED(argument) return false; } AbstractGroupEntry::AbstractGroupEntry(AbstractModel *owner) : AbstractEntry(owner) { } SeparatorEntry::SeparatorEntry(AbstractModel *owner) : AbstractEntry(owner) { } diff --git a/applets/kicker/plugin/appentry.cpp b/applets/kicker/plugin/appentry.cpp index 6f2cc8f8a..ca29c5748 100644 --- a/applets/kicker/plugin/appentry.cpp +++ b/applets/kicker/plugin/appentry.cpp @@ -1,297 +1,302 @@ /*************************************************************************** * Copyright (C) 201 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 #include "appentry.h" #include "actionlist.h" #include "appsmodel.h" #include "containmentinterface.h" #include #include #include #include #if HAVE_X11 #include #endif #include #include #include #include #include #include #include #include #include #include #include AppEntry::AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat) : AbstractEntry(owner) , m_service(service) { if (m_service) { init(nameFormat); } } AppEntry::AppEntry(AbstractModel *owner, const QString &id) : AbstractEntry(owner) { const QUrl url(id); if (url.scheme() == QStringLiteral("preferred")) { m_service = defaultAppByName(url.host()); m_id = id; } else { m_service = KService::serviceByStorageId(id); } if (m_service) { init((NameFormat)owner->rootModel()->property("appNameFormat").toInt()); } } void AppEntry::init(NameFormat nameFormat) { m_name = nameFromService(m_service, nameFormat); if (nameFormat == GenericNameOnly) { m_description = nameFromService(m_service, NameOnly); } else { m_description = nameFromService(m_service, GenericNameOnly); } } bool AppEntry::isValid() const { return m_service; } QIcon AppEntry::icon() const { if (m_icon.isNull()) { m_icon = QIcon::fromTheme(m_service->icon(), QIcon::fromTheme("unknown")); } return m_icon; } QString AppEntry::name() const { return m_name; } QString AppEntry::description() const { return m_description; } KService::Ptr AppEntry::service() const { return m_service; } QString AppEntry::id() const { if (!m_id.isEmpty()) { return m_id; } return m_service->storageId(); } +QString AppEntry::menuId() const +{ + return m_service->menuId(); +} + QUrl AppEntry::url() const { return QUrl::fromLocalFile(m_service->entryPath()); } bool AppEntry::hasActions() const { return true; } QVariantList AppEntry::actions() const { QVariantList actionList; actionList << Kicker::jumpListActions(m_service); if (!actionList.isEmpty()) { actionList << Kicker::createSeparatorActionItem(); } QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); const bool systemImmutable = appletInterface->property("immutability").toInt() == Plasma::Types::SystemImmutable; const QVariantList &addLauncherActions = Kicker::createAddLauncherActionList(appletInterface, m_service); if (!systemImmutable && !addLauncherActions.isEmpty()) { actionList << addLauncherActions << Kicker::createSeparatorActionItem(); } const QVariantList &recentDocuments = Kicker::recentDocumentActions(m_service); if (!recentDocuments.isEmpty()) { actionList << recentDocuments << Kicker::createSeparatorActionItem(); } // Don't allow adding launchers, editing, hiding, or uninstalling applications // when system is immutable. if (systemImmutable) { return actionList; } if (m_service->isApplication()) { actionList << Kicker::createSeparatorActionItem(); actionList << Kicker::editApplicationAction(m_service); actionList << Kicker::appstreamActions(m_service); } QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (appletConfig && appletConfig->contains("hiddenApplications") && qobject_cast(m_owner)) { const QStringList &hiddenApps = appletConfig->value("hiddenApplications").toStringList(); if (!hiddenApps.contains(m_service->menuId())) { actionList << Kicker::createActionItem(i18n("Hide Application"), "hideApplication"); } } return actionList; } bool AppEntry::run(const QString& actionId, const QVariant &argument) { if (!m_service->isValid()) { return false; } if (actionId.isEmpty()) { quint32 timeStamp = 0; #if HAVE_X11 if (QX11Info::isPlatformX11()) { timeStamp = QX11Info::appUserTime(); } #endif // TODO Once we depend on KDE Frameworks 5.24 and D1902 is merged, use KRun::runApplication instead KRun::runService(*m_service, {}, nullptr, true, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + m_service->storageId()), "org.kde.plasma.kicker"); return true; } QObject *appletInterface = m_owner->rootModel()->property("appletInterface").value(); if (Kicker::handleAddLauncherAction(actionId, appletInterface, m_service)) { return true; } else if (Kicker::handleEditApplicationAction(actionId, m_service)) { return true; } else if (Kicker::handleAppstreamActions(actionId, argument)) { return true; } else if (actionId == "_kicker_jumpListAction") { return KRun::run(argument.toString(), {}, nullptr, m_service->name(), m_service->icon()); } return Kicker::handleRecentDocumentAction(m_service, actionId, argument); } QString AppEntry::nameFromService(const KService::Ptr service, NameFormat nameFormat) { const QString &name = service->name(); QString genericName = service->genericName(); if (genericName.isEmpty()) { genericName = service->comment(); } if (nameFormat == NameOnly || genericName.isEmpty() || name == genericName) { return name; } else if (nameFormat == GenericNameOnly) { return genericName; } else if (nameFormat == NameAndGenericName) { return i18nc("App name (Generic name)", "%1 (%2)", name, genericName); } else { return i18nc("Generic name (App name)", "%1 (%2)", genericName, name); } } KService::Ptr AppEntry::defaultAppByName(const QString& name) { if (name == QLatin1String("browser")) { KConfigGroup config(KSharedConfig::openConfig(), "General"); QString browser = config.readPathEntry("BrowserApplication", QString()); if (browser.isEmpty()) { return KMimeTypeTrader::self()->preferredService(QLatin1String("text/html")); } else if (browser.startsWith('!')) { browser = browser.mid(1); } return KService::serviceByStorageId(browser); } return KService::Ptr(); } AppGroupEntry::AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat) : AbstractGroupEntry(parentModel), m_group(group) { AppsModel* model = new AppsModel(group->entryPath(), paginate, pageSize, flat, sorted, separators, parentModel); model->setAppNameFormat(appNameFormat); m_childModel = model; QObject::connect(parentModel, &AppsModel::cleared, model, &AppsModel::deleteLater); QObject::connect(model, &AppsModel::countChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); QObject::connect(model, &AppsModel::hiddenEntriesChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); } QIcon AppGroupEntry::icon() const { if (m_icon.isNull()) { m_icon = QIcon::fromTheme(m_group->icon(), QIcon::fromTheme("unknown")); } return m_icon; } QString AppGroupEntry::name() const { return m_group->caption(); } bool AppGroupEntry::hasChildren() const { return m_childModel && m_childModel->count() > 0; } AbstractModel *AppGroupEntry::childModel() const { return m_childModel; } diff --git a/applets/kicker/plugin/appentry.h b/applets/kicker/plugin/appentry.h index 4823a2bc7..fd4c3b627 100644 --- a/applets/kicker/plugin/appentry.h +++ b/applets/kicker/plugin/appentry.h @@ -1,93 +1,95 @@ /*************************************************************************** * Copyright (C) 201 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 APPENTRY_H #define APPENTRY_H #include "abstractentry.h" #include #include class AppsModel; class MenuEntryEditor; class AppEntry : public AbstractEntry { public: enum NameFormat { NameOnly = 0, GenericNameOnly, NameAndGenericName, GenericNameAndName }; explicit AppEntry(AbstractModel *owner, KService::Ptr service, NameFormat nameFormat); explicit AppEntry(AbstractModel *owner, const QString &id); EntryType type() const Q_DECL_OVERRIDE { return RunnableType; } bool isValid() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; QString name() const Q_DECL_OVERRIDE; QString description() const Q_DECL_OVERRIDE; KService::Ptr service() const; QString id() const Q_DECL_OVERRIDE; QUrl url() const Q_DECL_OVERRIDE; bool hasActions() const Q_DECL_OVERRIDE; QVariantList actions() const Q_DECL_OVERRIDE; bool run(const QString& actionId = QString(), const QVariant &argument = QVariant()) Q_DECL_OVERRIDE; + QString menuId() const; + static QString nameFromService(const KService::Ptr service, NameFormat nameFormat); static KService::Ptr defaultAppByName(const QString &name); private: void init(NameFormat nameFormat); QString m_id; QString m_name; QString m_description; mutable QIcon m_icon; KService::Ptr m_service; static MenuEntryEditor *m_menuEntryEditor; }; class AppGroupEntry : public AbstractGroupEntry { public: AppGroupEntry(AppsModel *parentModel, KServiceGroup::Ptr group, bool paginate, int pageSize, bool flat, bool sorted, bool separators, int appNameFormat); QIcon icon() const Q_DECL_OVERRIDE; QString name() const Q_DECL_OVERRIDE; bool hasChildren() const Q_DECL_OVERRIDE; AbstractModel *childModel() const Q_DECL_OVERRIDE; private: KServiceGroup::Ptr m_group; mutable QIcon m_icon; QPointer m_childModel; }; #endif diff --git a/applets/kicker/plugin/computermodel.cpp b/applets/kicker/plugin/computermodel.cpp index 41ea6b55e..bf08aa134 100644 --- a/applets/kicker/plugin/computermodel.cpp +++ b/applets/kicker/plugin/computermodel.cpp @@ -1,304 +1,305 @@ /*************************************************************************** * Copyright (C) 2007 Kevin Ottens * * Copyright (C) 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 . * ***************************************************************************/ #include "computermodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include #include #include #include #include #include #include #include "krunner_interface.h" FilteredPlacesModel::FilteredPlacesModel(QObject *parent) : QSortFilterProxyModel(parent) , m_placesModel(new KFilePlacesModel(this)) { setSourceModel(m_placesModel); sort(0); } FilteredPlacesModel::~FilteredPlacesModel() { } QUrl FilteredPlacesModel::url(const QModelIndex &index) const { return m_placesModel->url(mapToSource(index)); } bool FilteredPlacesModel::isDevice(const QModelIndex &index) const { return m_placesModel->isDevice(mapToSource(index)); } Solid::Device FilteredPlacesModel::deviceForIndex(const QModelIndex &index) const { return m_placesModel->deviceForIndex(mapToSource(index)); } bool FilteredPlacesModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const QModelIndex index = m_placesModel->index(sourceRow, 0, sourceParent); return !m_placesModel->isHidden(index) && !m_placesModel->data(index, KFilePlacesModel::FixedDeviceRole).toBool(); } bool FilteredPlacesModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { bool lDevice = m_placesModel->isDevice(left); bool rDevice = m_placesModel->isDevice(right); if (lDevice && !rDevice) { return false; } else if (!lDevice && rDevice) { return true; } return (left.row() < right.row()); } RunCommandModel::RunCommandModel(QObject *parent) : AbstractModel(parent) { } RunCommandModel::~RunCommandModel() { } QString RunCommandModel::description() const { return QString(); } QVariant RunCommandModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == Qt::DisplayRole) { return i18n("Run Command..."); } else if (role == Qt::DecorationRole) { return QIcon::fromTheme(QStringLiteral("system-run")); } else if (role == Kicker::DescriptionRole) { return i18n("Run a command or a search query"); } else if (role == Kicker::GroupRole) { return i18n("Applications"); } return QVariant(); } int RunCommandModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : (KAuthorized::authorize(QStringLiteral("run_command")) ? 1 : 0); } Q_INVOKABLE bool RunCommandModel::trigger(int row, const QString &actionId, const QVariant &argument) { Q_UNUSED(actionId) Q_UNUSED(argument) if (row == 0 && KAuthorized::authorize(QStringLiteral("run_command"))) { org::kde::krunner::App krunner(QStringLiteral("org.kde.krunner"), QStringLiteral("/App"), QDBusConnection::sessionBus()); krunner.display(); return true; } return false; } ComputerModel::ComputerModel(QObject *parent) : ForwardingModel(parent) , m_concatProxy(new KConcatenateRowsProxyModel(this)) , m_runCommandModel(new RunCommandModel(this)) -, m_systemAppsModel(new FavoritesModel(this)) +, m_systemAppsModel(new SimpleFavoritesModel(this)) , m_filteredPlacesModel(new FilteredPlacesModel(this)) , m_appNameFormat(AppEntry::NameOnly) , m_appletInterface(nullptr) { - connect(m_systemAppsModel, &FavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + connect(m_systemAppsModel, &SimpleFavoritesModel::favoritesChanged, this, &ComputerModel::systemApplicationsChanged); + m_systemAppsModel->setFavorites(QStringList() << "systemsettings.desktop"); m_concatProxy->addSourceModel(m_runCommandModel); m_concatProxy->addSourceModel(m_systemAppsModel); m_concatProxy->addSourceModel(m_filteredPlacesModel); setSourceModel(m_concatProxy); } ComputerModel::~ComputerModel() { } QString ComputerModel::description() const { return i18n("Computer"); } int ComputerModel::appNameFormat() const { return m_appNameFormat; } void ComputerModel::setAppNameFormat(int format) { if (m_appNameFormat != (AppEntry::NameFormat)format) { m_appNameFormat = (AppEntry::NameFormat)format; m_systemAppsModel->refresh(); emit appNameFormatChanged(); } } QObject *ComputerModel::appletInterface() const { return m_appletInterface; } void ComputerModel::setAppletInterface(QObject *appletInterface) { if (m_appletInterface != appletInterface) { m_appletInterface = appletInterface; emit appletInterfaceChanged(); } } QStringList ComputerModel::systemApplications() const { return m_systemAppsModel->favorites(); } void ComputerModel::setSystemApplications(const QStringList &apps) { m_systemAppsModel->setFavorites(apps); } QVariant ComputerModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(index.row(), index.column())); bool isPlace = (sourceIndex.model() == m_filteredPlacesModel); if (isPlace) { if (role == Kicker::DescriptionRole) { if (m_filteredPlacesModel->isDevice(sourceIndex)) { Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); Solid::StorageAccess *access = device.as(); if (access) { return access->filePath(); } else { return QString(); } } else { const QUrl &url = m_filteredPlacesModel->url(sourceIndex); return url.toString(QUrl::PreferLocalFile); } } else if (role == Kicker::FavoriteIdRole) { if (!m_filteredPlacesModel->isDevice(sourceIndex)) { return m_filteredPlacesModel->url(sourceIndex); } } else if (role == Kicker::UrlRole) { return m_filteredPlacesModel->url(sourceIndex); } else if (role == Kicker::GroupRole) { if (m_filteredPlacesModel->isDevice(sourceIndex)) { return i18n("Removable Storage"); } else { return i18n("Places"); } } else if (role == Qt::DisplayRole || role == Qt::DecorationRole) { return sourceIndex.data(role); } } else if (role == Kicker::GroupRole) { return i18n("Applications"); } else { return sourceIndex.data(role); } return QVariant(); } bool ComputerModel::trigger(int row, const QString &actionId, const QVariant &argument) { const QModelIndex sourceIndex = m_concatProxy->mapToSource(m_concatProxy->index(row, 0)); if (sourceIndex.model() == m_filteredPlacesModel) { const QUrl &url = m_filteredPlacesModel->url(sourceIndex); if (url.isValid()) { new KRun(url, 0); return true; } Solid::Device device = m_filteredPlacesModel->deviceForIndex(sourceIndex); Solid::StorageAccess *access = device.as(); if (access && !access->isAccessible()) { connect(access, &Solid::StorageAccess::setupDone, this, &ComputerModel::onSetupDone); access->setup(); return true; } } else { AbstractModel *model = nullptr; if (sourceIndex.model() == m_systemAppsModel) { model = m_systemAppsModel; } else { model = m_runCommandModel; } return model->trigger(sourceIndex.row(), actionId, argument); } return false; } void ComputerModel::onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi) { Q_UNUSED(errorData); if (error != Solid::NoError) { return; } Solid::Device device(udi); Solid::StorageAccess *access = device.as(); Q_ASSERT(access); new KRun(QUrl::fromLocalFile(access->filePath()), 0); } diff --git a/applets/kicker/plugin/computermodel.h b/applets/kicker/plugin/computermodel.h index aeb918e9a..b8cc96795 100644 --- a/applets/kicker/plugin/computermodel.h +++ b/applets/kicker/plugin/computermodel.h @@ -1,119 +1,119 @@ /*************************************************************************** * Copyright (C) 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 COMPUTERMODEL_H #define COMPUTERMODEL_H #include "forwardingmodel.h" #include "appentry.h" #include #include -class FavoritesModel; +class SimpleFavoritesModel; class KConcatenateRowsProxyModel; class KFilePlacesModel; namespace Solid { class Device; } class FilteredPlacesModel : public QSortFilterProxyModel { Q_OBJECT public: FilteredPlacesModel(QObject *parent = 0); ~FilteredPlacesModel(); QUrl url(const QModelIndex &index) const; bool isDevice(const QModelIndex &index) const; Solid::Device deviceForIndex(const QModelIndex &index) const; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; bool lessThan(const QModelIndex &left, const QModelIndex &right) const Q_DECL_OVERRIDE; private: KFilePlacesModel *m_placesModel; }; class RunCommandModel : public AbstractModel { Q_OBJECT public: RunCommandModel(QObject *parent = 0); ~RunCommandModel(); QString description() const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; }; class ComputerModel : public ForwardingModel { Q_OBJECT Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged) Q_PROPERTY(QStringList systemApplications READ systemApplications WRITE setSystemApplications NOTIFY systemApplicationsChanged) public: explicit ComputerModel(QObject *parent = 0); ~ComputerModel(); QString description() const Q_DECL_OVERRIDE; int appNameFormat() const; void setAppNameFormat(int format); QObject *appletInterface() const; void setAppletInterface(QObject *appletInterface); QStringList systemApplications() const; void setSystemApplications(const QStringList &apps); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; Q_SIGNALS: void appNameFormatChanged() const; void appletInterfaceChanged() const; void systemApplicationsChanged() const; private Q_SLOTS: void onSetupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); private: KConcatenateRowsProxyModel *m_concatProxy; RunCommandModel *m_runCommandModel; - FavoritesModel *m_systemAppsModel; + SimpleFavoritesModel *m_systemAppsModel; FilteredPlacesModel *m_filteredPlacesModel; AppEntry::NameFormat m_appNameFormat; QObject *m_appletInterface; }; #endif diff --git a/applets/kicker/plugin/kastatsfavoritesmodel.cpp b/applets/kicker/plugin/kastatsfavoritesmodel.cpp new file mode 100644 index 000000000..787a66764 --- /dev/null +++ b/applets/kicker/plugin/kastatsfavoritesmodel.cpp @@ -0,0 +1,717 @@ +/*************************************************************************** + * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 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) 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 "kastatsfavoritesmodel.h" +#include "appentry.h" +#include "contactentry.h" +#include "fileentry.h" +#include "actionlist.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +#define AGENT_APPLICATIONS "org.kde.plasma.favorites.applications" +#define AGENT_CONTACTS "org.kde.plasma.favorites.contacts" +#define AGENT_DOCUMENTS "org.kde.plasma.favorites.documents" + +#define DEBUG_PREFIX "\033[0;31m[KASTATSFAVS]\033[0;34m " +#define DEBUG qDebug() << DEBUG_PREFIX << (void*)q << ((void*)this) << m_clientId << "\033[0;0m " + +QString agentForUrl(const QString &url) +{ + return url.startsWith("ktp:") + ? AGENT_CONTACTS + : url.startsWith("preferred:") + ? AGENT_APPLICATIONS + : url.startsWith("applications:") + ? AGENT_APPLICATIONS + : (url.startsWith("/") && !url.endsWith(".desktop")) + ? AGENT_DOCUMENTS + : (url.startsWith("file:/") && !url.endsWith(".desktop")) + ? AGENT_DOCUMENTS + // use applications as the default + : AGENT_APPLICATIONS; +} + +class KAStatsFavoritesModel::Private: public QAbstractListModel { +public: + class NormalizedId { + public: + NormalizedId() + { + } + + NormalizedId(const Private *parent, const QString &id) + { + if (id.isEmpty()) return; + + AbstractEntry *entry = nullptr; + QScopedPointer deleter; + + if (parent->m_itemEntries.contains(id)) { + entry = parent->m_itemEntries[id]; + } else { + // This entry is not cached - it is temporary, + // so let's clean up when we exit this function + entry = parent->entryForResource(id); + deleter.reset(entry); + } + + if (!entry || !entry->isValid()) { + qWarning() << "Entry is not valid" << id << entry; + m_id = id; + return; + } + + const auto url = entry->url(); + + qDebug() << "Original id is: " << id << ", and the url is" << url; + + // Preferred applications need special handling + if (entry->id().startsWith("preferred:")) { + m_id = entry->id(); + return; + } + + // If this is an application, use the applications:-format url + auto appEntry = dynamic_cast(entry); + if (appEntry && !appEntry->menuId().isEmpty()) { + m_id = "applications:" + appEntry->menuId(); + return; + } + + // We want to resolve symbolic links not to have two paths + // refer to the same .desktop file + if (url.isLocalFile()) { + QFileInfo file(url.toLocalFile()); + + if (file.exists()) { + m_id = QUrl::fromLocalFile(file.canonicalFilePath()).toString(); + return; + } + } + + // If this is a file, we should have already covered it + if (url.scheme() == "file") { + return; + } + + m_id = url.toString(); + } + + const QString& value() const + { + return m_id; + } + + bool operator==(const NormalizedId &other) const + { + return m_id == other.m_id; + } + + private: + QString m_id; + }; + + NormalizedId normalizedId(const QString &id) const + { + return NormalizedId(this, id); + } + + AbstractEntry *entryForResource(const QString &resource) const + { + const auto agent = + agentForUrl(resource); + + if (agent == AGENT_CONTACTS) { + return new ContactEntry(q, resource); + + } else if (agent == AGENT_DOCUMENTS) { + if (resource.startsWith("/")) { + return new FileEntry(q, QUrl::fromLocalFile(resource)); + } else { + return new FileEntry(q, QUrl(resource)); + } + + } else if (agent == AGENT_APPLICATIONS) { + if (resource.startsWith("applications:")) { + return new AppEntry(q, resource.mid(13)); + } else { + return new AppEntry(q, resource); + } + + } else { + return nullptr; + } + } + + Private(KAStatsFavoritesModel *parent, QString clientId) + : q(parent) + , m_query( + LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::current() + | Activity::global() + | Limit::all() + ) + , m_watcher(m_query) + , m_clientId(clientId) + { + // Connecting the watcher + connect(&m_watcher, &ResultWatcher::resultLinked, + [this] (const QString &resource) { + addResult(resource, -1); + }); + + connect(&m_watcher, &ResultWatcher::resultUnlinked, + [this] (const QString &resource) { + removeResult(resource); + }); + + // Loading the items order + const auto cfg = KSharedConfig::openConfig("kactivitymanagerd-statsrc"); + + // We want first to check whether we have an ordering for this activity. + // If not, we will try to get a global one for this applet + + const QString thisGroupName = + "Favorites-" + clientId + "-" + m_activities.currentActivity(); + const QString globalGroupName = + "Favorites-" + clientId + "-global"; + + KConfigGroup thisCfgGroup(cfg, thisGroupName); + KConfigGroup globalCfgGroup(cfg, globalGroupName); + + QStringList ordering = + thisCfgGroup.readEntry("ordering", QStringList()) + + globalCfgGroup.readEntry("ordering", QStringList()); + + DEBUG << "Loading the ordering " << ordering; + + // Loading the results without emitting any model signals + DEBUG << "Query is" << m_query; + ResultSet results(m_query); + + for (const auto& result: results) { + DEBUG << "Got " << result.resource() << " -->"; + addResult(result.resource(), -1, false); + } + + // Normalizing all the ids + std::transform(ordering.begin(), ordering.end(), ordering.begin(), + [&] (const QString &item) { + return normalizedId(item).value(); + }); + + // Sorting the items in the cache + std::sort(m_items.begin(), m_items.end(), + [&] (const NormalizedId &left, const NormalizedId &right) { + auto leftIndex = ordering.indexOf(left.value()); + auto rightIndex = ordering.indexOf(right.value()); + + return (leftIndex == -1 && rightIndex == -1) ? + left.value() < right.value() : + + (leftIndex == -1) ? + false : + + (rightIndex == -1) ? + true : + + // otherwise + leftIndex < rightIndex; + }); + + // Debugging: + QVector itemStrings(m_items.size()); + std::transform(m_items.cbegin(), m_items.cend(), itemStrings.begin(), + [] (const NormalizedId &item) { + return item.value(); + }); + DEBUG << "After ordering: " << itemStrings; + } + + void addResult(const QString &_resource, int index, bool notifyModel = true) + { + // We want even files to have a proper URL + const auto resource = + _resource.startsWith("/") ? QUrl::fromLocalFile(_resource).toString() : _resource; + + DEBUG << "Adding result" << resource << "already present?" << m_itemEntries.contains(resource); + + if (m_itemEntries.contains(resource)) return; + + auto entry = entryForResource(resource); + + if (!entry || !entry->isValid()) { + DEBUG << "Entry is not valid!"; + return; + } + + if (index == -1) { + index = m_items.count(); + } + + if (notifyModel) { + beginInsertRows(QModelIndex(), index, index); + } + + auto url = entry->url(); + + m_itemEntries[resource] + = m_itemEntries[entry->id()] + = m_itemEntries[url.toString()] + = m_itemEntries[url.toLocalFile()] + = entry; + + auto normalized = normalizedId(resource); + m_items.insert(index, normalized); + m_itemEntries[normalized.value()] = entry; + + if (notifyModel) { + endInsertRows(); + saveOrdering(); + } + } + + void removeResult(const QString &resource) + { + auto normalized = normalizedId(resource); + + // If we know this item will not really be removed, + // but only that activities it is on have changed, + // lets leave it + if (m_ignoredItems.contains(normalized.value())) { + m_ignoredItems.removeAll(normalized.value()); + return; + } + + DEBUG << "Removing result" << resource; + + auto index = m_items.indexOf(normalizedId(resource)); + + if (index == -1) return; + + beginRemoveRows(QModelIndex(), index, index); + auto entry = m_itemEntries[resource]; + m_items.removeAt(index); + + // Removing the entry from the cache + QMutableHashIterator i(m_itemEntries); + while (i.hasNext()) { + if (i.value() == entry) { + i.remove(); + } + i.next(); + } + delete entry; + + endRemoveRows(); + } + + + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + if (parent.isValid()) return 0; + + return m_items.count(); + } + + QVariant data(const QModelIndex &item, + int role = Qt::DisplayRole) const override + { + if (item.parent().isValid()) return QVariant(); + + const auto index = item.row(); + + const auto entry = m_itemEntries[m_items[index].value()]; + + return entry == nullptr ? QVariant() + : role == Qt::DisplayRole ? entry->name() + : role == Qt::DecorationRole ? entry->icon() + : role == Kicker::DescriptionRole ? entry->description() + : role == Kicker::FavoriteIdRole ? entry->id() + : role == Kicker::UrlRole ? entry->url() + : role == Kicker::HasActionListRole ? entry->hasActions() + : role == Kicker::ActionListRole ? entry->actions() + : QVariant(); + } + + bool trigger(int row, const QString &actionId, const QVariant &argument) + { + if (row < 0 || row >= rowCount()) { + return false; + } + + const QString id = data(index(row, 0), Kicker::UrlRole).toString(); + + return m_itemEntries.contains(id) + ? m_itemEntries[id]->run(actionId, argument) + : false; + } + + void move(int from, int to) + { + if (from < 0) return; + if (from >= m_items.count()) return; + if (to < 0) return; + if (to >= m_items.count()) return; + + if (from == to) return; + + const int modelTo = to + (to > from ? 1 : 0); + + if (q->beginMoveRows(QModelIndex(), from, from, + QModelIndex(), modelTo)) { + m_items.move(from, to); + q->endMoveRows(); + + DEBUG << "Save ordering (from Private::move) -->"; + saveOrdering(); + } + } + + void saveOrdering() + { + QStringList ids; + + for (const auto& item: m_items) { + ids << item.value(); + } + + DEBUG << "Save ordering (from Private::saveOrdering) -->"; + saveOrdering(ids, m_clientId, m_activities.currentActivity()); + } + + static void saveOrdering(const QStringList &ids, const QString &clientId, const QString ¤tActivity) + { + const auto cfg = KSharedConfig::openConfig("kactivitymanagerd-statsrc"); + + QStringList activities { + currentActivity, + "global" + }; + + qDebug() << "Saving ordering for" << currentActivity << "and global" << ids; + + for (const auto& activity: activities) { + const QString groupName = + "Favorites-" + clientId + "-" + activity; + + KConfigGroup cfgGroup(cfg, groupName); + + cfgGroup.writeEntry("ordering", ids); + } + + cfg->sync(); + } + + KAStatsFavoritesModel *const q; + KActivities::Consumer m_activities; + Query m_query; + ResultWatcher m_watcher; + QString m_clientId; + + QVector m_items; + QHash m_itemEntries; + QStringList m_ignoredItems; +}; + +#undef DEBUG +#define DEBUG qDebug() << DEBUG_PREFIX << ((void*)this) << ((void*)d) << (d ? d->m_clientId : QString("no client ID yet")) << "\033[0;0m " + +KAStatsFavoritesModel::KAStatsFavoritesModel(QObject *parent) +: PlaceholderModel(parent) +, d(nullptr) // we have no client id yet +, m_enabled(true) +, m_maxFavorites(-1) +, m_activities(new KActivities::Consumer(this)) +{ + connect(m_activities, &KActivities::Consumer::currentActivityChanged, + this, [&] (const QString ¤tActivity) { + DEBUG << "Activity just got changed to" << currentActivity; + Q_UNUSED(currentActivity); + auto clientId = d->m_clientId; + initForClient(clientId); + }); +} + +KAStatsFavoritesModel::~KAStatsFavoritesModel() +{ + delete d; +} + +void KAStatsFavoritesModel::initForClient(const QString &clientId) +{ + DEBUG << "initForClient" << clientId; + + setSourceModel(nullptr); + delete d; + d = new Private( + this, + clientId + ); + + setSourceModel(d); +} + +QString KAStatsFavoritesModel::description() const +{ + return i18n("Favorites"); +} + +bool KAStatsFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + return d->trigger(row, actionId, argument); +} + +bool KAStatsFavoritesModel::enabled() const +{ + return m_enabled; +} + +int KAStatsFavoritesModel::maxFavorites() const +{ + return m_maxFavorites; +} + +void KAStatsFavoritesModel::setMaxFavorites(int max) +{ + Q_UNUSED(max); +} + +void KAStatsFavoritesModel::setEnabled(bool enable) +{ + if (m_enabled != enable) { + m_enabled = enable; + + emit enabledChanged(); + } +} + +QStringList KAStatsFavoritesModel::favorites() const +{ + qWarning() << "KAStatsFavoritesModel::favorites returns nothing, it is here just to keep the API backwards-compatible"; + return QStringList(); +} + +void KAStatsFavoritesModel::setFavorites(const QStringList& favorites) +{ + Q_UNUSED(favorites); + qWarning() << "KAStatsFavoritesModel::setFavorites is ignored"; +} + +bool KAStatsFavoritesModel::isFavorite(const QString &id) const +{ + return d && d->m_itemEntries.contains(id); +} + +void KAStatsFavoritesModel::portOldFavorites(const QStringList &ids) +{ + DEBUG << "portOldFavorites" << ids; + + const auto activityId = ":global"; + std::for_each(ids.begin(), ids.end(), [&] (const QString &id) { + addFavoriteTo(id, activityId); + }); + + // Resetting the model + auto clientId = d->m_clientId; + setSourceModel(nullptr); + delete d; + d = nullptr; + + DEBUG << "Save ordering (from portOldFavorites) -->"; + Private::saveOrdering(ids, clientId, m_activities->currentActivity()); + + QTimer::singleShot(500, + std::bind(&KAStatsFavoritesModel::initForClient, this, clientId)); +} + +void KAStatsFavoritesModel::addFavorite(const QString &id, int index) +{ + DEBUG << "addFavorite" << id << index << " -->"; + addFavoriteTo(id, Activity::current(), index); +} + +void KAStatsFavoritesModel::removeFavorite(const QString &id) +{ + DEBUG << "removeFavorite" << id << " -->"; + removeFavoriteFrom(id, Activity::current()); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const QString &activityId, int index) +{ + DEBUG << "addFavoriteTo" << id << activityId << index << " -->"; + addFavoriteTo(id, Activity(activityId), index); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const QString &activityId) +{ + DEBUG << "removeFavoriteFrom" << id << activityId << " -->"; + removeFavoriteFrom(id, Activity(activityId)); +} + +void KAStatsFavoritesModel::addFavoriteTo(const QString &id, const Activity &activity, int index) +{ + if (!d || id.isEmpty()) return; + + Q_ASSERT(!activity.values.isEmpty()); + + setDropPlaceholderIndex(-1); + + QStringList matchers { d->m_activities.currentActivity(), ":global", ":current" }; + if (std::find_first_of(activity.values.cbegin(), activity.values.cend(), + matchers.cbegin(), matchers.cend()) != activity.values.cend()) { + d->addResult(id, index); + } + + const auto url = d->normalizedId(id).value(); + + DEBUG << "addFavoriteTo" << id << activity << index << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.linkToActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::removeFavoriteFrom(const QString &id, const Activity &activity) +{ + const auto url = d->normalizedId(id).value(); + + Q_ASSERT(!activity.values.isEmpty()); + + DEBUG << "addFavoriteTo" << id << activity << url << " (actual)"; + + if (url.isEmpty()) return; + + d->m_watcher.unlinkFromActivity(QUrl(url), activity, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::setFavoriteOn(const QString &id, const QString &activityId) +{ + const auto url = d->normalizedId(id).value(); + + DEBUG << "setFavoriteOn" << id << activityId << url << " (actual)"; + + DEBUG << "%%%%%%%%%%% Activity is" << activityId; + if (activityId.isEmpty() || activityId == ":any" || + activityId == ":global" || + activityId == m_activities->currentActivity()) { + d->m_ignoredItems << url; + } + + d->m_watcher.unlinkFromActivity(QUrl(url), Activity::any(), + Agent(agentForUrl(url))); + d->m_watcher.linkToActivity(QUrl(url), activityId, + Agent(agentForUrl(url))); +} + +void KAStatsFavoritesModel::moveRow(int from, int to) +{ + d->move(from, to); +} + +AbstractModel *KAStatsFavoritesModel::favoritesModel() +{ + return this; +} + +void KAStatsFavoritesModel::refresh() +{ +} + +QObject *KAStatsFavoritesModel::activities() const +{ + return m_activities; +} + +QString KAStatsFavoritesModel::activityNameForId(const QString &activityId) const +{ + // It is safe to use a short-lived object here, + // we are always synced with KAMD in plasma + KActivities::Info info(activityId); + return info.name(); +} + +QStringList KAStatsFavoritesModel::linkedActivitiesFor(const QString &id) const +{ + if (!d) { + DEBUG << "Linked for" << id << "is empty, no Private instance"; + return {}; + } + + auto url = d->normalizedId(id).value(); + + if (url.startsWith("file:")) { + url = QUrl(url).toLocalFile(); + } + + if (url.isEmpty()) { + DEBUG << "The url for" << id << "is empty"; + return {}; + } + + auto query = LinkedResources + | Agent { + AGENT_APPLICATIONS, + AGENT_CONTACTS, + AGENT_DOCUMENTS + } + | Type::any() + | Activity::any() + | Url(url) + | Limit::all(); + + ResultSet results(query); + + for (const auto &result: results) { + DEBUG << "Returning" << result.linkedActivities() << "for" << id << url; + return result.linkedActivities(); + } + + DEBUG << "Returning empty list of activities for" << id << url; + return {}; +} + diff --git a/applets/kicker/plugin/favoritesmodel.h b/applets/kicker/plugin/kastatsfavoritesmodel.h similarity index 60% copy from applets/kicker/plugin/favoritesmodel.h copy to applets/kicker/plugin/kastatsfavoritesmodel.h index 8ed1bf921..f41e4d092 100644 --- a/applets/kicker/plugin/favoritesmodel.h +++ b/applets/kicker/plugin/kastatsfavoritesmodel.h @@ -1,91 +1,117 @@ /*************************************************************************** * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2016-2017 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) 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 FAVORITESMODEL_H #define FAVORITESMODEL_H -#include "abstractmodel.h" +#include "placeholdermodel.h" #include #include +#include -class FavoritesModel : public AbstractModel +class PlaceholderModel; + +namespace KActivities { + class Consumer; +namespace Stats { + class ResultModel; +namespace Terms { + class Activity; +} // namespace Terms +} // namespace Stats +} // namespace KActivities + +class KAStatsFavoritesModel : public PlaceholderModel { Q_OBJECT Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) - Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) - - public: - explicit FavoritesModel(QObject *parent = 0); - ~FavoritesModel(); - QString description() const Q_DECL_OVERRIDE; + Q_PROPERTY(QObject* activities READ activities CONSTANT) - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + public: + explicit KAStatsFavoritesModel(QObject *parent = 0); + ~KAStatsFavoritesModel(); - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + QString description() const; - Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument); bool enabled() const; void setEnabled(bool enable); QStringList favorites() const; void setFavorites(const QStringList &favorites); int maxFavorites() const; void setMaxFavorites(int max); Q_INVOKABLE bool isFavorite(const QString &id) const; + Q_INVOKABLE void addFavorite(const QString &id, int index = -1); Q_INVOKABLE void removeFavorite(const QString &id); + Q_INVOKABLE void addFavoriteTo(const QString &id, const QString &activityId, int index = -1); + Q_INVOKABLE void removeFavoriteFrom(const QString &id, const QString &activityId); + + Q_INVOKABLE void setFavoriteOn(const QString &id, const QString &activityId); + + Q_INVOKABLE void portOldFavorites(const QStringList &ids); + + Q_INVOKABLE QStringList linkedActivitiesFor(const QString &id) const; + Q_INVOKABLE void moveRow(int from, int to); - int dropPlaceholderIndex() const; - void setDropPlaceholderIndex(int index); + Q_INVOKABLE void initForClient(const QString &client); - AbstractModel* favoritesModel() Q_DECL_OVERRIDE; + QObject *activities() const; + Q_INVOKABLE QString activityNameForId(const QString &activityId) const; + + AbstractModel* favoritesModel(); public Q_SLOTS: - void refresh() Q_DECL_OVERRIDE; + virtual void refresh(); Q_SIGNALS: void enabledChanged() const; void favoritesChanged() const; void maxFavoritesChanged() const; - void dropPlaceholderIndexChanged(); private: - AbstractEntry *favoriteFromId(const QString &id); + class Private; + Private * d; + + AbstractEntry *favoriteFromId(const QString &id) const; + + void addFavoriteTo(const QString &id, const KActivities::Stats::Terms::Activity &activityId, int index = -1); + void removeFavoriteFrom(const QString &id, const KActivities::Stats::Terms::Activity &activityId); bool m_enabled; - QList m_entryList; - QStringList m_favorites; int m_maxFavorites; - int m_dropPlaceholderIndex; + KActivities::Consumer *m_activities; }; #endif diff --git a/applets/kicker/plugin/kickerplugin.cpp b/applets/kicker/plugin/kickerplugin.cpp index 11b780070..b41a304c9 100644 --- a/applets/kicker/plugin/kickerplugin.cpp +++ b/applets/kicker/plugin/kickerplugin.cpp @@ -1,63 +1,65 @@ /*************************************************************************** * Copyright (C) 2014 by Eike Hein * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "kickerplugin.h" #include "abstractmodel.h" #include "appsmodel.h" #include "computermodel.h" #include "containmentinterface.h" #include "draghelper.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include "dashboardwindow.h" #include "funnelmodel.h" #include "processrunner.h" #include "recentusagemodel.h" #include "rootmodel.h" #include "runnermodel.h" #include "submenu.h" #include "systemmodel.h" #include "systemsettings.h" #include "wheelinterceptor.h" #include "windowsystem.h" #include void KickerPlugin::registerTypes(const char *uri) { Q_ASSERT(uri == QLatin1String("org.kde.plasma.private.kicker")); qmlRegisterType(); qmlRegisterType(uri, 0, 1, "AppsModel"); qmlRegisterType(uri, 0, 1, "ComputerModel"); qmlRegisterType(uri, 0, 1, "ContainmentInterface"); qmlRegisterType(uri, 0, 1, "DragHelper"); - qmlRegisterType(uri, 0, 1, "FavoritesModel"); + qmlRegisterType(uri, 0, 1, "FavoritesModel"); + qmlRegisterType(uri, 0, 1, "KAStatsFavoritesModel"); qmlRegisterType(uri, 0, 1, "DashboardWindow"); qmlRegisterType(uri, 0, 1, "FunnelModel"); qmlRegisterType(uri, 0, 1, "ProcessRunner"); qmlRegisterType(uri, 0, 1, "RecentUsageModel"); qmlRegisterType(uri, 0, 1, "RootModel"); qmlRegisterType(uri, 0, 1, "RunnerModel"); qmlRegisterType(uri, 0, 1, "SubMenu"); qmlRegisterType(uri, 0, 1, "SystemModel"); qmlRegisterType(uri, 0, 1, "SystemSettings"); qmlRegisterType(uri, 0, 1, "WheelInterceptor"); qmlRegisterType(uri, 0, 1, "WindowSystem"); } diff --git a/applets/kicker/plugin/placeholdermodel.cpp b/applets/kicker/plugin/placeholdermodel.cpp new file mode 100644 index 000000000..53d98303b --- /dev/null +++ b/applets/kicker/plugin/placeholdermodel.cpp @@ -0,0 +1,407 @@ +/*************************************************************************** + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 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) 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 "placeholdermodel.h" +#include "actionlist.h" + +#include +#include + +PlaceholderModel::PlaceholderModel(QObject *parent) + : AbstractModel(parent) + , m_dropPlaceholderIndex(-1) + , m_isTriggerInhibited(false) +{ + connect(&m_triggerInhibitor, &QTimer::timeout, + this, [&] { + qDebug() << "%%% Inhibit stopped"; + m_isTriggerInhibited = false; + }); + + m_triggerInhibitor.setInterval(500); + m_triggerInhibitor.setSingleShot(true); +} + +void PlaceholderModel::inhibitTriggering() +{ + qDebug() << "%%% Inhibit started"; + m_isTriggerInhibited = true; + m_triggerInhibitor.start(); +} + +PlaceholderModel::~PlaceholderModel() +{ +} + +QString PlaceholderModel::description() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->description(); + + } else { + return QString(); + } +} + +QAbstractItemModel *PlaceholderModel::sourceModel() const +{ + return m_sourceModel; +} + +void PlaceholderModel::setSourceModel(QAbstractItemModel *sourceModel) +{ + disconnectSignals(); + + beginResetModel(); + + m_sourceModel = sourceModel; + + connectSignals(); + + endResetModel(); + + emit countChanged(); + emit sourceModelChanged(); + emit descriptionChanged(); +} + +bool PlaceholderModel::canFetchMore(const QModelIndex &parent) const +{ + return m_sourceModel && m_sourceModel->canFetchMore(indexToSourceIndex(parent)); +} + +void PlaceholderModel::fetchMore(const QModelIndex &parent) +{ + if (m_sourceModel) { + m_sourceModel->fetchMore(indexToSourceIndex(parent)); + } +} + +QModelIndex PlaceholderModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_sourceModel ? createIndex(row, column) + : QModelIndex(); +} + +QModelIndex PlaceholderModel::parent(const QModelIndex &index) const +{ + Q_UNUSED(index) + + return QModelIndex(); +} + +QVariant PlaceholderModel::data(const QModelIndex &index, int role) const +{ + const auto row = index.row(); + + if (m_dropPlaceholderIndex == row) { + switch (role) { + case Kicker::IsDropPlaceholderRole: + return true; + + // TODO: Maybe it would be nice to show something here? + // case Qt::DisplayRole: + // return "placeholder"; + // + // case Qt::DecorationRole: + // return "select"; + + default: + return QVariant(); + + } + } + + return m_sourceModel ? m_sourceModel->data(indexToSourceIndex(index), role) + : QVariant(); +} + +int PlaceholderModel::rowCount(const QModelIndex &parent) const +{ + if (!m_sourceModel || parent.isValid()) { + return 0; + } + + return m_sourceModel->rowCount() + + (m_dropPlaceholderIndex != -1 ? 1 : 0); +} + +QModelIndex PlaceholderModel::indexToSourceIndex(const QModelIndex& index) const +{ + if (!m_sourceModel || !index.isValid()) { + return QModelIndex(); + } + + const auto row = index.row(); + const auto column = index.column(); + + return index.parent().isValid() ? + // We do not support tree models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + m_sourceModel->index( + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0), + column, + QModelIndex() + ); +} + +int PlaceholderModel::sourceRowToRow(int sourceRow) const +{ + return sourceRow + + (m_dropPlaceholderIndex != -1 && sourceRow >= m_dropPlaceholderIndex ? 1 : 0); +} + +int PlaceholderModel::rowToSourceRow(int row) const +{ + return row == m_dropPlaceholderIndex ? -1 : + row - (m_dropPlaceholderIndex != -1 && row > m_dropPlaceholderIndex ? 1 : 0); +} + +QModelIndex PlaceholderModel::sourceIndexToIndex(const QModelIndex& sourceIndex) const +{ + if (!m_sourceModel || !sourceIndex.isValid()) { + return QModelIndex(); + } + + const auto sourceRow = sourceIndex.row(); + const auto sourceColumn = sourceIndex.column(); + + return sourceIndex.parent().isValid() ? + // We do not support tree-models + QModelIndex() : + + // If we are on top-level, lets add a placeholder + index( + sourceRowToRow(sourceRow), + sourceColumn, + QModelIndex() + ); +} + +bool PlaceholderModel::trigger(int row, const QString &actionId, const QVariant &argument) +{ + if (m_isTriggerInhibited) return false; + + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->trigger(rowToSourceRow(row), actionId, argument); + + } else { + return false; + } +} + +QString PlaceholderModel::labelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->labelForRow(rowToSourceRow(row)); + + } else { + return QString(); + } + +} + +AbstractModel* PlaceholderModel::modelForRow(int row) +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->modelForRow(rowToSourceRow(row)); + + } else { + return 0; + } +} + +AbstractModel* PlaceholderModel::favoritesModel() +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->favoritesModel(); + + } else { + return AbstractModel::favoritesModel(); + } +} + +int PlaceholderModel::separatorCount() const +{ + if (auto abstractModel = qobject_cast(m_sourceModel)) { + return abstractModel->separatorCount(); + + } else { + return 0; + } +} + +void PlaceholderModel::reset() +{ + emit beginResetModel(); + emit endResetModel(); + emit countChanged(); + emit separatorCountChanged(); +} + +void PlaceholderModel::connectSignals() +{ + if (!m_sourceModel) { + return; + } + + const auto sourceModelPtr = m_sourceModel.data(); + + connect(sourceModelPtr, SIGNAL(destroyed()), this, SLOT(reset())); + + connect(sourceModelPtr, &QAbstractItemModel::dataChanged, + this, [this] (const QModelIndex &from, const QModelIndex &to, const QVector &roles) { + emit dataChanged(sourceIndexToIndex(from), + sourceIndexToIndex(to), + roles); + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeInserted, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginInsertRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsInserted, + this, [this] { + endInsertRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeMoved, + this, [this] (const QModelIndex &source, int from, int to, const QModelIndex &dest, int destRow) { + if (source.isValid() || dest.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginMoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to), + QModelIndex(), + sourceRowToRow(destRow)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsMoved, + this, [this] { + endMoveRows(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::rowsAboutToBeRemoved, + this, [this] (const QModelIndex &parent, int from, int to) { + if (parent.isValid()) { + qWarning() << "We do not support tree models"; + + } else { + beginRemoveRows(QModelIndex(), + sourceRowToRow(from), + sourceRowToRow(to)); + } + }); + + connect(sourceModelPtr, &QAbstractItemModel::rowsRemoved, + this, [this] { + endRemoveRows(); + emit countChanged(); + }); + + + connect(sourceModelPtr, &QAbstractItemModel::modelAboutToBeReset, + this, [this] { + beginResetModel(); + }); + + connect(sourceModelPtr, &QAbstractItemModel::modelReset, + this, [this] { + endResetModel(); + emit countChanged(); + }); + + // We do not have persistant indices + // connect(sourceModelPtr, &QAbstractItemModel::layoutAboutToBeChanged), + // this, &PlaceholderModel::layoutAboutToBeChanged); + // connect(sourceModelPtr, &QAbstractItemModel::layoutChanged), + // this, SIGNAL(layoutChanged(QList,QAbstractItemModel::LayoutChangeHint)), + // Qt::UniqueConnection); +} + +void PlaceholderModel::disconnectSignals() +{ + if (!m_sourceModel) { + return; + } + + disconnect(m_sourceModel, 0, this, 0); +} + +int PlaceholderModel::dropPlaceholderIndex() const +{ + return m_dropPlaceholderIndex; +} + +void PlaceholderModel::setDropPlaceholderIndex(int index) +{ + if (index == m_dropPlaceholderIndex) return; + + inhibitTriggering(); + + if (index == -1 && m_dropPlaceholderIndex != -1) { + // Removing the placeholder + beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); + m_dropPlaceholderIndex = index; + endRemoveRows(); + + emit countChanged(); + + } else if (index != -1 && m_dropPlaceholderIndex == -1) { + // Creating the placeholder + beginInsertRows(QModelIndex(), index, index); + m_dropPlaceholderIndex = index; + endInsertRows(); + + emit countChanged(); + + } else if (m_dropPlaceholderIndex != index) { + // Moving the placeholder + int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); + + if (beginMoveRows( + QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, + QModelIndex(), modelTo)) { + m_dropPlaceholderIndex = index; + endMoveRows(); + } + } + + emit dropPlaceholderIndexChanged(); +} diff --git a/applets/kicker/plugin/favoritesmodel.h b/applets/kicker/plugin/placeholdermodel.h similarity index 52% copy from applets/kicker/plugin/favoritesmodel.h copy to applets/kicker/plugin/placeholdermodel.h index 8ed1bf921..c6835d8ca 100644 --- a/applets/kicker/plugin/favoritesmodel.h +++ b/applets/kicker/plugin/placeholdermodel.h @@ -1,91 +1,94 @@ /*************************************************************************** - * Copyright (C) 2014-2015 by Eike Hein * + * Copyright (C) 2015 by Eike Hein * + * Copyright (C) 2017 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) 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 FAVORITESMODEL_H -#define FAVORITESMODEL_H +#ifndef PLACEHOLDERMODEL_H +#define PLACEHOLDERMODEL_H #include "abstractmodel.h" #include +#include -#include - -class FavoritesModel : public AbstractModel +class PlaceholderModel : public AbstractModel { Q_OBJECT - Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) - Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) - Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) + Q_PROPERTY(QAbstractItemModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged); Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) public: - explicit FavoritesModel(QObject *parent = 0); - ~FavoritesModel(); + explicit PlaceholderModel(QObject *parent = 0); + ~PlaceholderModel(); + + QString description() const; + + QAbstractItemModel *sourceModel() const; + virtual void setSourceModel(QAbstractItemModel *sourceModel); - QString description() const Q_DECL_OVERRIDE; + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; - int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; + int rowCount(const QModelIndex &parent = QModelIndex()) const; - bool enabled() const; - void setEnabled(bool enable); + Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument); - QStringList favorites() const; - void setFavorites(const QStringList &favorites); + Q_INVOKABLE QString labelForRow(int row); - int maxFavorites() const; - void setMaxFavorites(int max); + Q_INVOKABLE AbstractModel *modelForRow(int row); - Q_INVOKABLE bool isFavorite(const QString &id) const; - Q_INVOKABLE void addFavorite(const QString &id, int index = -1); - Q_INVOKABLE void removeFavorite(const QString &id); + AbstractModel* favoritesModel(); - Q_INVOKABLE void moveRow(int from, int to); + int separatorCount() const; int dropPlaceholderIndex() const; void setDropPlaceholderIndex(int index); - AbstractModel* favoritesModel() Q_DECL_OVERRIDE; - public Q_SLOTS: - void refresh() Q_DECL_OVERRIDE; + void reset(); Q_SIGNALS: - void enabledChanged() const; - void favoritesChanged() const; - void maxFavoritesChanged() const; + void sourceModelChanged() const; void dropPlaceholderIndexChanged(); + protected: + void inhibitTriggering(); + private: - AbstractEntry *favoriteFromId(const QString &id); + QModelIndex indexToSourceIndex(const QModelIndex &index) const; + QModelIndex sourceIndexToIndex(const QModelIndex &index) const; + int sourceRowToRow(int sourceRow) const; + int rowToSourceRow(int row) const; - bool m_enabled; + void connectSignals(); + void disconnectSignals(); - QList m_entryList; - QStringList m_favorites; - int m_maxFavorites; + QPointer m_sourceModel; int m_dropPlaceholderIndex; + bool m_isTriggerInhibited; + QTimer m_triggerInhibitor; }; #endif diff --git a/applets/kicker/plugin/recentusagemodel.cpp b/applets/kicker/plugin/recentusagemodel.cpp index 0bab03e71..8f1711c6e 100644 --- a/applets/kicker/plugin/recentusagemodel.cpp +++ b/applets/kicker/plugin/recentusagemodel.cpp @@ -1,462 +1,462 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "recentusagemodel.h" #include "actionlist.h" #include "appsmodel.h" #include "appentry.h" -#include "favoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include #include #include #if HAVE_X11 #include #endif #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; GroupSortProxy::GroupSortProxy(QAbstractItemModel *sourceModel) : QSortFilterProxyModel(sourceModel) { sourceModel->setParent(this); setSourceModel(sourceModel); sort(0); } GroupSortProxy::~GroupSortProxy() { } InvalidAppsFilterProxy::InvalidAppsFilterProxy(AbstractModel *parentModel, QAbstractItemModel *sourceModel) : QSortFilterProxyModel(sourceModel) , m_parentModel(parentModel) { connect(parentModel, &AbstractModel::favoritesModelChanged, this, &InvalidAppsFilterProxy::connectNewFavoritesModel); connectNewFavoritesModel(); sourceModel->setParent(this); setSourceModel(sourceModel); } InvalidAppsFilterProxy::~InvalidAppsFilterProxy() { } void InvalidAppsFilterProxy::connectNewFavoritesModel() { - FavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); - connect(favoritesModel, &FavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); + KAStatsFavoritesModel* favoritesModel = static_cast(m_parentModel->favoritesModel()); + connect(favoritesModel, &KAStatsFavoritesModel::favoritesChanged, this, &QSortFilterProxyModel::invalidate); invalidate(); } bool InvalidAppsFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { Q_UNUSED(source_parent); const QString resource = sourceModel()->index(source_row, 0).data(ResultModel::ResourceRole).toString(); if (resource.startsWith(QLatin1String("applications:"))) { KService::Ptr service = KService::serviceByStorageId(resource.section(':', 1)); - FavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; + KAStatsFavoritesModel* favoritesModel = m_parentModel ? static_cast(m_parentModel->favoritesModel()) : nullptr; return (service && (!favoritesModel || !favoritesModel->isFavorite(service->storageId()))); } return true; } bool GroupSortProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { const QString &lResource = sourceModel()->data(left, ResultModel::ResourceRole).toString(); const QString &rResource = sourceModel()->data(right, ResultModel::ResourceRole).toString(); if (lResource.startsWith(QLatin1String("applications:")) && !rResource.startsWith(QLatin1String("applications:"))) { return true; } else if (!lResource.startsWith(QLatin1String("applications:")) && rResource.startsWith(QLatin1String("applications:"))) { return false; } return (left.row() < right.row()); } RecentUsageModel::RecentUsageModel(QObject *parent, IncludeUsage usage, int ordering) : ForwardingModel(parent) , m_usage(usage) , m_ordering((Ordering)ordering) , m_complete(false) { refresh(); } RecentUsageModel::~RecentUsageModel() { } RecentUsageModel::IncludeUsage RecentUsageModel::usage() const { return m_usage; } QString RecentUsageModel::description() const { switch (m_usage) { case AppsAndDocs: return i18n("Recently Used"); case OnlyApps: return i18n("Applications"); case OnlyDocs: default: return i18n("Documents"); } } QString RecentUsageModel::resourceAt(int row) const { QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); if (sourceProxy) { return sourceProxy->sourceModel()->data(sourceProxy->mapToSource(sourceProxy->index(row, 0)), ResultModel::ResourceRole).toString(); } return sourceModel()->data(index(row, 0), ResultModel::ResourceRole).toString(); } QVariant RecentUsageModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const QString &resource = resourceAt(index.row()); if (resource.startsWith(QLatin1String("applications:"))) { return appData(resource, role); } else { return docData(resource, role); } } QVariant RecentUsageModel::appData(const QString &resource, int role) const { const QString storageId = resource.section(':', 1); KService::Ptr service = KService::serviceByStorageId(storageId); QStringList allowedTypes({ QLatin1String("Service"), QLatin1String("Application") }); if (!service || !allowedTypes.contains(service->property(QLatin1String("Type")).toString()) || service->exec().isEmpty()) { return QVariant(); } if (role == Qt::DisplayRole) { AppsModel *parentModel = qobject_cast(QObject::parent()); if (parentModel) { return AppEntry::nameFromService(service, (AppEntry::NameFormat)qobject_cast(QObject::parent())->appNameFormat()); } else { return AppEntry::nameFromService(service, AppEntry::NameOnly); } } else if (role == Qt::DecorationRole) { return QIcon::fromTheme(service->icon(), QIcon::fromTheme("unknown")); } else if (role == Kicker::DescriptionRole) { return service->comment(); } else if (role == Kicker::GroupRole) { return i18n("Applications"); } else if (role == Kicker::FavoriteIdRole) { return service->storageId(); } else if (role == Kicker::HasActionListRole) { return true; } else if (role == Kicker::ActionListRole) { QVariantList actionList; const QVariantList &jumpList = Kicker::jumpListActions(service); if (jumpList.count()) { actionList << jumpList << Kicker::createSeparatorActionItem(); } const QVariantList &recentDocuments = Kicker::recentDocumentActions(service); if (recentDocuments.count()) { actionList << recentDocuments << Kicker::createSeparatorActionItem(); } const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Application"), "forget"); actionList << forgetAction; const QVariantMap &forgetAllAction = Kicker::createActionItem(forgetAllActionName(), "forgetAll"); actionList << forgetAllAction; return actionList; } return QVariant(); } QVariant RecentUsageModel::docData(const QString &resource, int role) const { QUrl url(resource); if (url.scheme().isEmpty()) { url.setScheme(QStringLiteral("file")); } const KFileItem fileItem(url); if (!url.isValid() || !(fileItem.isFile() || fileItem.isDir())) { return QVariant(); } if (role == Qt::DisplayRole) { return fileItem.text(); } else if (role == Qt::DecorationRole) { return QIcon::fromTheme(fileItem.iconName(), QIcon::fromTheme("unknown")); } else if (role == Kicker::GroupRole) { return i18n("Documents"); } else if (role == Kicker::FavoriteIdRole || role == Kicker::UrlRole) { return url.toString(); } else if (role == Kicker::UrlRole) { return url; } else if (role == Kicker::HasActionListRole) { return true; } else if (role == Kicker::ActionListRole) { QVariantList actionList = Kicker::createActionListForFileItem(fileItem); actionList << Kicker::createSeparatorActionItem(); const QVariantMap &forgetAction = Kicker::createActionItem(i18n("Forget Document"), "forget"); actionList << forgetAction; const QVariantMap &forgetAllAction = Kicker::createActionItem(forgetAllActionName(), "forgetAll"); actionList << forgetAllAction; return actionList; } return QVariant(); } bool RecentUsageModel::trigger(int row, const QString &actionId, const QVariant &argument) { Q_UNUSED(argument) bool withinBounds = row >= 0 && row < rowCount(); if (actionId.isEmpty() && withinBounds) { const QString &resource = resourceAt(row); if (!resource.startsWith(QLatin1String("applications:"))) { new KRun(docData(resource, Kicker::UrlRole).toUrl(), 0); return true; } const QString storageId = resource.section(':', 1); KService::Ptr service = KService::serviceByStorageId(storageId); if (!service) { return false; } quint32 timeStamp = 0; #if HAVE_X11 if (QX11Info::isPlatformX11()) { timeStamp = QX11Info::appUserTime(); } #endif // TODO Once we depend on KDE Frameworks 5.24 and D1902 is merged, use KRun::runApplication instead KRun::runService(*service, {}, nullptr, true, {}, KStartupInfo::createNewStartupIdForTimestamp(timeStamp)); KActivities::ResourceInstance::notifyAccessed(QUrl("applications:" + storageId), "org.kde.plasma.kicker"); return true; } else if (actionId == "forget" && withinBounds) { if (m_activitiesModel) { QModelIndex idx = sourceModel()->index(row, 0); QSortFilterProxyModel *sourceProxy = qobject_cast(sourceModel()); while (sourceProxy) { idx = sourceProxy->mapToSource(idx); sourceProxy = qobject_cast(sourceProxy->sourceModel()); } static_cast(m_activitiesModel.data())->forgetResource(idx.row()); } return false; } else if (actionId == "forgetAll") { if (m_activitiesModel) { static_cast(m_activitiesModel.data())->forgetAllResources(); } return false; } else if (withinBounds) { const QString &resource = resourceAt(row); if (resource.startsWith(QLatin1String("applications:"))) { const QString storageId = sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString().section(':', 1); KService::Ptr service = KService::serviceByStorageId(storageId); if (service) { return Kicker::handleRecentDocumentAction(service, actionId, argument); } } else { bool close = false; QUrl url(sourceModel()->data(sourceModel()->index(row, 0), ResultModel::ResourceRole).toString()); KFileItem item(url); if (Kicker::handleFileItemAction(item, actionId, argument, &close)) { return close; } } } return false; } bool RecentUsageModel::hasActions() const { return rowCount(); } QVariantList RecentUsageModel::actions() const { QVariantList actionList; if (rowCount()) { actionList << Kicker::createActionItem(forgetAllActionName(), "forgetAll"); } return actionList; } QString RecentUsageModel::forgetAllActionName() const { switch (m_usage) { case AppsAndDocs: return i18n("Forget All"); case OnlyApps: return i18n("Forget All Applications"); case OnlyDocs: default: return i18n("Forget All Documents"); } } void RecentUsageModel::setOrdering(int ordering) { if (ordering == m_ordering) return; m_ordering = (Ordering)ordering; refresh(); emit orderingChanged(ordering); } int RecentUsageModel::ordering() const { return m_ordering; } void RecentUsageModel::classBegin() { } void RecentUsageModel::componentComplete() { m_complete = true; refresh(); } void RecentUsageModel::refresh() { if (qmlEngine(this) && !m_complete) { return; } setSourceModel(nullptr); delete m_activitiesModel; auto query = UsedResources | (m_ordering == Recent ? RecentlyUsedFirst : HighScoredFirst) | Agent::any() | Type::any() | Activity::current(); switch (m_usage) { case AppsAndDocs: { query = query | Url::startsWith("applications:") | Url::file() | Limit(30); break; } case OnlyApps: { query = query | Url::startsWith("applications:") | Limit(15); break; } case OnlyDocs: default: { query = query | Url::file() | Limit(15); } } m_activitiesModel = new ResultModel(query); QAbstractItemModel *model = m_activitiesModel; QModelIndex index; if (model->canFetchMore(index)) { model->fetchMore(index); } if (m_usage != OnlyDocs) { model = new InvalidAppsFilterProxy(this, model); } if (m_usage == AppsAndDocs) { model = new GroupSortProxy(model); } setSourceModel(model); } diff --git a/applets/kicker/plugin/rootmodel.cpp b/applets/kicker/plugin/rootmodel.cpp index 2260c681f..6750d8a89 100644 --- a/applets/kicker/plugin/rootmodel.cpp +++ b/applets/kicker/plugin/rootmodel.cpp @@ -1,453 +1,453 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "rootmodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "kastatsfavoritesmodel.h" #include "recentcontactsmodel.h" #include "recentusagemodel.h" #include "systemmodel.h" #include #include GroupEntry::GroupEntry(AppsModel *parentModel, const QString &name, const QString &iconName, AbstractModel *childModel) : AbstractGroupEntry(parentModel) , m_name(name) , m_iconName(iconName) , m_childModel(childModel) { QObject::connect(parentModel, &RootModel::cleared, childModel, &AbstractModel::deleteLater); QObject::connect(childModel, &AbstractModel::countChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); } QString GroupEntry::name() const { return m_name; } QIcon GroupEntry::icon() const { return QIcon::fromTheme(m_iconName, QIcon::fromTheme("unknown")); } bool GroupEntry::hasChildren() const { return m_childModel && m_childModel->count() > 0; } AbstractModel *GroupEntry::childModel() const { return m_childModel; } RootModel::RootModel(QObject *parent) : AppsModel(QString(), parent) , m_complete(false) -, m_favorites(new FavoritesModel(this)) +, m_favorites(new KAStatsFavoritesModel(this)) , m_systemModel(nullptr) , m_autoPopulate(true) , m_showAllApps(false) , m_showRecentApps(true) , m_showRecentDocs(true) , m_showRecentContacts(false) , m_recentOrdering(RecentUsageModel::Recent) , m_showPowerSession(true) , m_recentAppsModel(0) , m_recentDocsModel(0) , m_recentContactsModel(0) { } RootModel::~RootModel() { } QVariant RootModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= m_entryList.count()) { return QVariant(); } if (role == Kicker::HasActionListRole || role == Kicker::ActionListRole) { const AbstractEntry *entry = m_entryList.at(index.row()); if (entry->type() == AbstractEntry::GroupType) { const GroupEntry *group = static_cast(entry); AbstractModel *model = group->childModel(); if (model == m_recentAppsModel || model == m_recentDocsModel || model == m_recentContactsModel) { if (role == Kicker::HasActionListRole) { return true; } else if (role == Kicker::ActionListRole) { QVariantList actionList; actionList << model->actions(); actionList << Kicker::createSeparatorActionItem(); actionList << Kicker::createActionItem(i18n("Hide %1", group->name()), "hideCategory"); return actionList; } } } } return AppsModel::data(index, role); } bool RootModel::trigger(int row, const QString& actionId, const QVariant& argument) { const AbstractEntry *entry = m_entryList.at(row); if (entry->type() == AbstractEntry::GroupType) { if (actionId == "hideCategory") { AbstractModel *model = entry->childModel(); if (model == m_recentAppsModel) { setShowRecentApps(false); return true; } else if (model == m_recentDocsModel) { setShowRecentDocs(false); return true; } else if (model == m_recentContactsModel) { setShowRecentContacts(false); return true; } } else if (entry->childModel()->hasActions()) { return entry->childModel()->trigger(-1, actionId, QVariant()); } } return AppsModel::trigger(row, actionId, argument); } bool RootModel::autoPopulate() const { return m_autoPopulate; } void RootModel::setAutoPopulate(bool populate) { if (m_autoPopulate != populate) { m_autoPopulate = populate; emit autoPopulateChanged(); } } bool RootModel::showAllApps() const { return m_showAllApps; } void RootModel::setShowAllApps(bool show) { if (m_showAllApps != show) { m_showAllApps = show; refresh(); emit showAllAppsChanged(); } } bool RootModel::showRecentApps() const { return m_showRecentApps; } void RootModel::setShowRecentApps(bool show) { if (show != m_showRecentApps) { m_showRecentApps = show; refresh(); emit showRecentAppsChanged(); } } bool RootModel::showRecentDocs() const { return m_showRecentDocs; } void RootModel::setShowRecentDocs(bool show) { if (show != m_showRecentDocs) { m_showRecentDocs = show; refresh(); emit showRecentDocsChanged(); } } bool RootModel::showRecentContacts() const { return m_showRecentContacts; } void RootModel::setShowRecentContacts(bool show) { if (show != m_showRecentContacts) { m_showRecentContacts = show; refresh(); emit showRecentContactsChanged(); } } int RootModel::recentOrdering() const { return m_recentOrdering; } void RootModel::setRecentOrdering(int ordering) { if (ordering != m_recentOrdering) { m_recentOrdering = ordering; refresh(); emit recentOrderingChanged(); } } bool RootModel::showPowerSession() const { return m_showPowerSession; } void RootModel::setShowPowerSession(bool show) { if (show != m_showPowerSession) { m_showPowerSession = show; refresh(); emit showPowerSessionChanged(); } } AbstractModel* RootModel::favoritesModel() { return m_favorites; } AbstractModel* RootModel::systemFavoritesModel() { if (m_systemModel) { return m_systemModel->favoritesModel(); } return nullptr; } void RootModel::classBegin() { } void RootModel::componentComplete() { m_complete = true; if (m_autoPopulate) { refresh(); } } void RootModel::refresh() { if (!m_complete) { return; } beginResetModel(); AppsModel::refreshInternal(); AppsModel *allModel = nullptr; m_recentAppsModel = nullptr; m_recentDocsModel = nullptr; m_recentContactsModel = nullptr; if (m_showAllApps) { QList groups; if (m_paginate) { - m_favorites = new FavoritesModel(this); + m_favorites = new KAStatsFavoritesModel(this); emit favoritesModelChanged(); QHash appsHash; QList apps; foreach (const AbstractEntry *groupEntry, m_entryList) { AbstractModel *model = groupEntry->childModel(); for (int i = 0; i < model->count(); ++i) { GroupEntry *subGroupEntry = static_cast(model->index(i, 0).internalPointer()); AbstractModel *subModel = subGroupEntry->childModel(); for (int j = 0; j < subModel->count(); ++j) { AppEntry *appEntry = static_cast(subModel->index(j, 0).internalPointer()); if (appEntry->name().isEmpty()) { continue; } appsHash.insert(appEntry->service()->menuId(), appEntry); } } } apps = appsHash.values(); QCollator c; std::sort(apps.begin(), apps.end(), [&c](AbstractEntry* a, AbstractEntry* b) { if (a->type() != b->type()) { return a->type() > b->type(); } else { return c.compare(a->name(), b->name()) < 0; } }); int at = 0; QList page; page.reserve(m_pageSize); foreach(AppEntry *app, apps) { page.append(app); if (at == (m_pageSize - 1)) { at = 0; AppsModel *model = new AppsModel(page, false, this); groups.append(new GroupEntry(this, QString(), QString(), model)); page.clear(); } else { ++at; } } if (!page.isEmpty()) { AppsModel *model = new AppsModel(page, false, this); groups.append(new GroupEntry(this, QString(), QString(), model)); } groups.prepend(new GroupEntry(this, QString(), QString(), m_favorites)); } else { QHash> m_categoryHash; foreach (const AbstractEntry *groupEntry, m_entryList) { AbstractModel *model = groupEntry->childModel(); for (int i = 0; i < model->count(); ++i) { AbstractEntry *appEntry = static_cast(model->index(i, 0).internalPointer()); if (appEntry->name().isEmpty()) { continue; } const QChar &first = appEntry->name().at(0).toUpper(); m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry); } } QHashIterator> i(m_categoryHash); while (i.hasNext()) { i.next(); AppsModel *model = new AppsModel(i.value(), false, this); model->setDescription(i.key()); groups.append(new GroupEntry(this, i.key(), QString(), model)); } } allModel = new AppsModel(groups, true, this); allModel->setDescription(QStringLiteral("KICKER_ALL_MODEL")); // Intentionally no i18n. } int separatorPosition = 0; if (allModel) { m_entryList.prepend(new GroupEntry(this, i18n("All Applications"), QString(), allModel)); ++separatorPosition; } if (m_showRecentContacts) { m_recentContactsModel = new RecentContactsModel(this); m_entryList.prepend(new GroupEntry(this, i18n("Recent Contacts"), QString(), m_recentContactsModel)); ++separatorPosition; } if (m_showRecentDocs) { m_recentDocsModel = new RecentUsageModel(this, RecentUsageModel::OnlyDocs, m_recentOrdering); m_entryList.prepend(new GroupEntry(this, m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Documents") : i18n("Often Used Documents"), QString(), m_recentDocsModel)); ++separatorPosition; } if (m_showRecentApps) { m_recentAppsModel = new RecentUsageModel(this, RecentUsageModel::OnlyApps, m_recentOrdering); m_entryList.prepend(new GroupEntry(this, m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Applications") : i18n("Often Used Applications"), QString(), m_recentAppsModel)); ++separatorPosition; } if (m_showSeparators && separatorPosition > 0) { m_entryList.insert(separatorPosition, new SeparatorEntry(this)); ++m_separatorCount; } m_systemModel = new SystemModel(this); if (m_showPowerSession) { m_entryList << new GroupEntry(this, i18n("Power / Session"), QString(), m_systemModel); } endResetModel(); m_favorites->refresh(); emit systemFavoritesModelChanged(); emit countChanged(); emit separatorCountChanged(); emit refreshed(); } diff --git a/applets/kicker/plugin/rootmodel.h b/applets/kicker/plugin/rootmodel.h index b9356fa9c..781d5cab2 100644 --- a/applets/kicker/plugin/rootmodel.h +++ b/applets/kicker/plugin/rootmodel.h @@ -1,137 +1,137 @@ /*************************************************************************** * 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 ROOTMODEL_H #define ROOTMODEL_H #include "appsmodel.h" #include -class FavoritesModel; +class KAStatsFavoritesModel; class RecentContactsModel; class RecentUsageModel; class SystemModel; class RootModel; class GroupEntry : public AbstractGroupEntry { public: GroupEntry(AppsModel *parentModel, const QString &name, const QString &iconName, AbstractModel *childModel); QIcon icon() const Q_DECL_OVERRIDE; QString name() const Q_DECL_OVERRIDE; bool hasChildren() const Q_DECL_OVERRIDE; AbstractModel *childModel() const Q_DECL_OVERRIDE; private: QString m_name; QString m_iconName; QPointer m_childModel; }; class RootModel : public AppsModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(bool autoPopulate READ autoPopulate WRITE setAutoPopulate NOTIFY autoPopulateChanged) Q_PROPERTY(QObject* systemFavoritesModel READ systemFavoritesModel NOTIFY systemFavoritesModelChanged) Q_PROPERTY(bool showAllApps READ showAllApps WRITE setShowAllApps NOTIFY showAllAppsChanged) Q_PROPERTY(bool showRecentApps READ showRecentApps WRITE setShowRecentApps NOTIFY showRecentAppsChanged) Q_PROPERTY(bool showRecentDocs READ showRecentDocs WRITE setShowRecentDocs NOTIFY showRecentDocsChanged) Q_PROPERTY(bool showRecentContacts READ showRecentContacts WRITE setShowRecentContacts NOTIFY showRecentContactsChanged) Q_PROPERTY(int recentOrdering READ recentOrdering WRITE setRecentOrdering NOTIFY recentOrderingChanged) Q_PROPERTY(bool showPowerSession READ showPowerSession WRITE setShowPowerSession NOTIFY showPowerSessionChanged) public: explicit RootModel(QObject *parent = 0); ~RootModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; Q_INVOKABLE virtual bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; bool autoPopulate() const; void setAutoPopulate(bool populate); bool showAllApps() const; void setShowAllApps(bool show); bool showRecentApps() const; void setShowRecentApps(bool show); bool showRecentDocs() const; void setShowRecentDocs(bool show); bool showRecentContacts() const; void setShowRecentContacts(bool show); int recentOrdering() const; void setRecentOrdering(int ordering); bool showPowerSession() const; void setShowPowerSession(bool show); AbstractModel* favoritesModel() Q_DECL_OVERRIDE; AbstractModel* systemFavoritesModel(); void classBegin() override; void componentComplete() override; Q_SIGNALS: void refreshed() const; void systemFavoritesModelChanged() const; void autoPopulateChanged() const; void showAllAppsChanged() const; void showRecentAppsChanged() const; void showRecentDocsChanged() const; void showRecentContactsChanged() const; void showPowerSessionChanged() const; void recentOrderingChanged() const; void recentAppsModelChanged() const; protected Q_SLOTS: void refresh() Q_DECL_OVERRIDE; private: bool m_complete; - FavoritesModel *m_favorites; + KAStatsFavoritesModel *m_favorites; SystemModel *m_systemModel; bool m_autoPopulate; bool m_showAllApps; bool m_showRecentApps; bool m_showRecentDocs; bool m_showRecentContacts; int m_recentOrdering; bool m_showPowerSession; RecentUsageModel *m_recentAppsModel; RecentUsageModel *m_recentDocsModel; RecentContactsModel *m_recentContactsModel; }; #endif diff --git a/applets/kicker/plugin/favoritesmodel.cpp b/applets/kicker/plugin/simplefavoritesmodel.cpp similarity index 84% rename from applets/kicker/plugin/favoritesmodel.cpp rename to applets/kicker/plugin/simplefavoritesmodel.cpp index 0ee6a5391..a2e68abff 100644 --- a/applets/kicker/plugin/favoritesmodel.cpp +++ b/applets/kicker/plugin/simplefavoritesmodel.cpp @@ -1,333 +1,333 @@ /*************************************************************************** * 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 . * ***************************************************************************/ -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include "appentry.h" #include "contactentry.h" #include "fileentry.h" #include "systementry.h" #include "actionlist.h" #include -FavoritesModel::FavoritesModel(QObject *parent) : AbstractModel(parent) +SimpleFavoritesModel::SimpleFavoritesModel(QObject *parent) : AbstractModel(parent) , m_enabled(true) , m_maxFavorites(-1) , m_dropPlaceholderIndex(-1) { } -FavoritesModel::~FavoritesModel() +SimpleFavoritesModel::~SimpleFavoritesModel() { qDeleteAll(m_entryList); } -QString FavoritesModel::description() const +QString SimpleFavoritesModel::description() const { return i18n("Favorites"); } -QVariant FavoritesModel::data(const QModelIndex& index, int role) const +QVariant SimpleFavoritesModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= rowCount()) { return QVariant(); } if (index.row() == m_dropPlaceholderIndex) { if (role == Kicker::IsDropPlaceholderRole) { return true; } else { return QVariant(); } } int mappedIndex = index.row(); if (m_dropPlaceholderIndex != -1 && mappedIndex > m_dropPlaceholderIndex) { --mappedIndex; } const AbstractEntry *entry = m_entryList.at(mappedIndex); if (role == Qt::DisplayRole) { return entry->name(); } else if (role == Qt::DecorationRole) { return entry->icon(); } else if (role == Kicker::DescriptionRole) { return entry->description(); } else if (role == Kicker::FavoriteIdRole) { return entry->id(); } else if (role == Kicker::UrlRole) { return entry->url(); } else if (role == Kicker::HasActionListRole) { return entry->hasActions(); } else if (role == Kicker::ActionListRole) { return entry->actions(); } return QVariant(); } -int FavoritesModel::rowCount(const QModelIndex& parent) const +int SimpleFavoritesModel::rowCount(const QModelIndex& parent) const { return parent.isValid() ? 0 : m_entryList.count() + (m_dropPlaceholderIndex != -1 ? 1 : 0); } -bool FavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) +bool SimpleFavoritesModel::trigger(int row, const QString &actionId, const QVariant &argument) { if (row < 0 || row >= m_entryList.count()) { return false; } return m_entryList.at(row)->run(actionId, argument); } -bool FavoritesModel::enabled() const +bool SimpleFavoritesModel::enabled() const { return m_enabled; } -void FavoritesModel::setEnabled(bool enable) +void SimpleFavoritesModel::setEnabled(bool enable) { if (m_enabled != enable) { m_enabled = enable; emit enabledChanged(); } } -QStringList FavoritesModel::favorites() const +QStringList SimpleFavoritesModel::favorites() const { return m_favorites; } -void FavoritesModel::setFavorites(const QStringList& favorites) +void SimpleFavoritesModel::setFavorites(const QStringList& favorites) { QStringList _favorites(favorites); _favorites.removeDuplicates(); if (_favorites != m_favorites) { m_favorites = _favorites; refresh(); } } -int FavoritesModel::maxFavorites() const +int SimpleFavoritesModel::maxFavorites() const { return m_maxFavorites; } -void FavoritesModel::setMaxFavorites(int max) +void SimpleFavoritesModel::setMaxFavorites(int max) { if (m_maxFavorites != max) { m_maxFavorites = max; if (m_maxFavorites != -1 && m_favorites.count() > m_maxFavorites) { refresh(); } emit maxFavoritesChanged(); } } -bool FavoritesModel::isFavorite(const QString &id) const +bool SimpleFavoritesModel::isFavorite(const QString &id) const { return m_favorites.contains(id); } -void FavoritesModel::addFavorite(const QString &id, int index) +void SimpleFavoritesModel::addFavorite(const QString &id, int index) { if (!m_enabled || id.isEmpty()) { return; } if (m_maxFavorites != -1 && m_favorites.count() == m_maxFavorites) { return; } AbstractEntry *entry = favoriteFromId(id); if (!entry || !entry->isValid()) { delete entry; return; } setDropPlaceholderIndex(-1); int insertIndex = (index != -1) ? index : m_entryList.count(); beginInsertRows(QModelIndex(), insertIndex, insertIndex); m_entryList.insert(insertIndex, entry); m_favorites.insert(insertIndex, entry->id()); endInsertRows(); emit countChanged(); emit favoritesChanged(); } -void FavoritesModel::removeFavorite(const QString &id) +void SimpleFavoritesModel::removeFavorite(const QString &id) { if (!m_enabled || id.isEmpty()) { return; } int index = m_favorites.indexOf(id); if (index != -1) { setDropPlaceholderIndex(-1); beginRemoveRows(QModelIndex(), index, index); delete m_entryList[index]; m_entryList.removeAt(index); m_favorites.removeAt(index); endRemoveRows(); emit countChanged(); emit favoritesChanged(); } } -void FavoritesModel::moveRow(int from, int to) +void SimpleFavoritesModel::moveRow(int from, int to) { if (from >= m_favorites.count() || to >= m_favorites.count()) { return; } if (from == to) { return; } setDropPlaceholderIndex(-1); int modelTo = to + (to > from ? 1 : 0); bool ok = beginMoveRows(QModelIndex(), from, from, QModelIndex(), modelTo); if (ok) { m_entryList.move(from, to); m_favorites.move(from, to); endMoveRows(); emit favoritesChanged(); } } -int FavoritesModel::dropPlaceholderIndex() const +int SimpleFavoritesModel::dropPlaceholderIndex() const { return m_dropPlaceholderIndex; } -void FavoritesModel::setDropPlaceholderIndex(int index) +void SimpleFavoritesModel::setDropPlaceholderIndex(int index) { if (index == -1 && m_dropPlaceholderIndex != -1) { beginRemoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex); m_dropPlaceholderIndex = index; endRemoveRows(); emit countChanged(); } else if (index != -1 && m_dropPlaceholderIndex == -1) { beginInsertRows(QModelIndex(), index, index); m_dropPlaceholderIndex = index; endInsertRows(); emit countChanged(); } else if (m_dropPlaceholderIndex != index) { int modelTo = index + (index > m_dropPlaceholderIndex ? 1 : 0); bool ok = beginMoveRows(QModelIndex(), m_dropPlaceholderIndex, m_dropPlaceholderIndex, QModelIndex(), modelTo); if (ok) { m_dropPlaceholderIndex = index; endMoveRows(); } } } -AbstractModel *FavoritesModel::favoritesModel() +AbstractModel *SimpleFavoritesModel::favoritesModel() { return this; } -void FavoritesModel::refresh() +void SimpleFavoritesModel::refresh() { beginResetModel(); setDropPlaceholderIndex(-1); int oldCount = m_entryList.count(); qDeleteAll(m_entryList); m_entryList.clear(); QStringList newFavorites; foreach(const QString &id, m_favorites) { AbstractEntry *entry = favoriteFromId(id); if (entry && entry->isValid()) { m_entryList << entry; newFavorites << entry->id(); if (m_maxFavorites != -1 && newFavorites.count() == m_maxFavorites) { break; } } else if (entry) { delete entry; } } m_favorites = newFavorites; endResetModel(); if (oldCount != m_entryList.count()) { emit countChanged(); } emit favoritesChanged(); } -AbstractEntry *FavoritesModel::favoriteFromId(const QString &id) +AbstractEntry *SimpleFavoritesModel::favoriteFromId(const QString &id) { const QUrl url(id); const QString &s = url.scheme(); if ((s.isEmpty() && id.contains(QStringLiteral(".desktop"))) || s == QStringLiteral("preferred")) { return new AppEntry(this, id); } else if (s == QStringLiteral("ktp")) { return new ContactEntry(this, id); } else if (url.isValid() && !url.scheme().isEmpty()) { return new FileEntry(this, url); } else { return new SystemEntry(this, id); } return nullptr; } diff --git a/applets/kicker/plugin/favoritesmodel.h b/applets/kicker/plugin/simplefavoritesmodel.h similarity index 94% rename from applets/kicker/plugin/favoritesmodel.h rename to applets/kicker/plugin/simplefavoritesmodel.h index 8ed1bf921..7b044760c 100644 --- a/applets/kicker/plugin/favoritesmodel.h +++ b/applets/kicker/plugin/simplefavoritesmodel.h @@ -1,91 +1,91 @@ /*************************************************************************** * 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 FAVORITESMODEL_H -#define FAVORITESMODEL_H +#ifndef SIMPLEFAVORITESMODEL_H +#define SIMPLEFAVORITESMODEL_H #include "abstractmodel.h" #include #include -class FavoritesModel : public AbstractModel +class SimpleFavoritesModel : public AbstractModel { Q_OBJECT Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) Q_PROPERTY(QStringList favorites READ favorites WRITE setFavorites NOTIFY favoritesChanged) Q_PROPERTY(int maxFavorites READ maxFavorites WRITE setMaxFavorites NOTIFY maxFavoritesChanged) Q_PROPERTY(int dropPlaceholderIndex READ dropPlaceholderIndex WRITE setDropPlaceholderIndex NOTIFY dropPlaceholderIndexChanged) public: - explicit FavoritesModel(QObject *parent = 0); - ~FavoritesModel(); + explicit SimpleFavoritesModel(QObject *parent = 0); + ~SimpleFavoritesModel(); QString description() const Q_DECL_OVERRIDE; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) Q_DECL_OVERRIDE; bool enabled() const; void setEnabled(bool enable); QStringList favorites() const; void setFavorites(const QStringList &favorites); int maxFavorites() const; void setMaxFavorites(int max); Q_INVOKABLE bool isFavorite(const QString &id) const; Q_INVOKABLE void addFavorite(const QString &id, int index = -1); Q_INVOKABLE void removeFavorite(const QString &id); Q_INVOKABLE void moveRow(int from, int to); int dropPlaceholderIndex() const; void setDropPlaceholderIndex(int index); AbstractModel* favoritesModel() Q_DECL_OVERRIDE; public Q_SLOTS: void refresh() Q_DECL_OVERRIDE; Q_SIGNALS: void enabledChanged() const; void favoritesChanged() const; void maxFavoritesChanged() const; void dropPlaceholderIndexChanged(); private: AbstractEntry *favoriteFromId(const QString &id); bool m_enabled; QList m_entryList; QStringList m_favorites; int m_maxFavorites; int m_dropPlaceholderIndex; }; #endif diff --git a/applets/kicker/plugin/systemmodel.cpp b/applets/kicker/plugin/systemmodel.cpp index 498e38058..f5dcd1d23 100644 --- a/applets/kicker/plugin/systemmodel.cpp +++ b/applets/kicker/plugin/systemmodel.cpp @@ -1,134 +1,134 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #include "systemmodel.h" #include "actionlist.h" -#include "favoritesmodel.h" +#include "simplefavoritesmodel.h" #include "systementry.h" #include #include #include SystemModel::SystemModel(QObject *parent) : AbstractModel(parent) { init(); - m_favoritesModel = new FavoritesModel(this); + m_favoritesModel = new SimpleFavoritesModel(this); const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/ksmserverrc"; KDirWatch *watch = new KDirWatch(this); watch->addFile(configFile); connect(watch, &KDirWatch::dirty, this, &SystemModel::refresh); connect(watch, &KDirWatch::created, this, &SystemModel::refresh); } SystemModel::~SystemModel() { qDeleteAll(m_entryList); } void SystemModel::init() { QList actions; actions << new SystemEntry(this, SystemEntry::LockSession); actions << new SystemEntry(this, SystemEntry::LogoutSession); actions << new SystemEntry(this, SystemEntry::SaveSession); actions << new SystemEntry(this, SystemEntry::SwitchUser); actions << new SystemEntry(this, SystemEntry::SuspendToRam); actions << new SystemEntry(this, SystemEntry::SuspendToDisk); actions << new SystemEntry(this, SystemEntry::Reboot); actions << new SystemEntry(this, SystemEntry::Shutdown); foreach(SystemEntry *entry, actions) { if (entry->isValid()) { m_entryList << entry; } else { delete entry; } } } QString SystemModel::description() const { return i18n("System actions"); } QVariant SystemModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_entryList.count()) { return QVariant(); } const SystemEntry *entry = m_entryList.at(index.row()); if (role == Qt::DisplayRole) { return entry->name(); } else if (role == Qt::DecorationRole) { return entry->iconName(); } else if (role == Kicker::DescriptionRole) { return entry->description(); } else if (role == Kicker::GroupRole) { return entry->group(); } else if (role == Kicker::FavoriteIdRole) { return entry->id(); } return QVariant(); } int SystemModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_entryList.count(); } bool SystemModel::trigger(int row, const QString &actionId, const QVariant &argument) { Q_UNUSED(actionId) Q_UNUSED(argument) if (row >= 0 && row < m_entryList.count()) { m_entryList.at(row)->run(); return true; } return false; } void SystemModel::refresh() { beginResetModel(); qDeleteAll(m_entryList); m_entryList.clear(); init(); endResetModel(); emit countChanged(); m_favoritesModel->refresh(); } diff --git a/applets/kickoff/package/contents/code/tools.js b/applets/kickoff/package/contents/code/tools.js index 0b4c5e31e..545d7afff 100644 --- a/applets/kickoff/package/contents/code/tools.js +++ b/applets/kickoff/package/contents/code/tools.js @@ -1,97 +1,200 @@ /*************************************************************************** * Copyright (C) 2013 by Aurélien Gâteau * * Copyright (C) 2013-2015 by Eike Hein * + * Copyright (C) 2017 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) 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 . * ***************************************************************************/ function fillActionMenu(actionMenu, actionList, favoriteModel, favoriteId) { // Accessing actionList can be a costly operation, so we don't // access it until we need the menu. - var action = createFavoriteAction(favoriteModel, favoriteId); + var actions = createFavoriteActions(favoriteModel, favoriteId); - if (action) { + if (actions) { if (actionList && actionList.length > 0) { var separator = { "type": "separator" }; - actionList.push(separator, action); + actionList.unshift(separator); + actionList.unshift.apply(actionList, actions); } else { - actionList = [action]; + actionList = actions; } } actionMenu.actionList = actionList; } -function createFavoriteAction(favoriteModel, favoriteId) { +function createFavoriteActions(favoriteModel, favoriteId) { // Don't allow changes to favorites when system is immutable. if (plasmoid.immutability === PlasmaCore.Types.SystemImmutable) { return null; } if (favoriteModel === null || !favoriteModel.enabled || favoriteId == null) { return null; } - var action = {}; + var activities = favoriteModel.activities.runningActivities; + + if (activities.length <= 1) { + var action = {}; + + if (favoriteModel.isFavorite(favoriteId)) { + action.text = i18n("Remove from Favorites"); + action.icon = "list-remove"; + action.actionId = "_kicker_favorite_remove"; + } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { + action.text = i18n("Add to Favorites"); + action.icon = "bookmark-new"; + action.actionId = "_kicker_favorite_add"; + } else { + return null; + } + + action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + + return [action]; - if (favoriteModel.isFavorite(favoriteId)) { - action.text = i18n("Remove from Favorites"); - action.icon = "list-remove"; - action.actionId = "_kicker_favorite_remove"; - } else if (favoriteModel.maxFavorites == -1 || favoriteModel.count < favoriteModel.maxFavorites) { - action.text = i18n("Add to Favorites"); - action.icon = "bookmark-new"; - action.actionId = "_kicker_favorite_add"; } else { - return null; - } + var actions = []; + + var linkedActivities = favoriteModel.linkedActivitiesFor(favoriteId); + + // Adding the item to link/unlink to all activities + + var linkedToAllActivities = + !(linkedActivities.indexOf(":global") === -1); + + actions.push({ + text : i18n("On All Activities"), + checkable : true, + + actionId : linkedToAllActivities ? + "_kicker_favorite_remove_from_activity" : + "_kicker_favorite_set_to_activity", + checked : linkedToAllActivities, + + actionArgument : { + favoriteModel: favoriteModel, + favoriteId: favoriteId, + favoriteActivity: "" + } + }); + + + // Adding items for each activity separately + + var addActivityItem = function(activityId, activityName) { + var linkedToThisActivity = + !(linkedActivities.indexOf(activityId) === -1); - action.actionArgument = { favoriteModel: favoriteModel, favoriteId: favoriteId }; + actions.push({ + text : activityName, + checkable : true, + checked : linkedToThisActivity && !linkedToAllActivities, - return action; + actionId : + // If we are on all activities, and the user clicks just one + // specific activity, unlink from everything else + linkedToAllActivities ? "_kicker_favorite_set_to_activity" : + + // If we are linked to the current activity, just unlink from + // that single one + linkedToThisActivity ? "_kicker_favorite_remove_from_activity" : + + // Otherwise, link to this activity, but do not unlink from + // other ones + "_kicker_favorite_add_to_activity", + + actionArgument : { + favoriteModel : favoriteModel, + favoriteId : favoriteId, + favoriteActivity : activityId + } + }); + }; + + // Adding the item to link/unlink to the current activity + + addActivityItem(favoriteModel.activities.currentActivity, i18n("On The Current Activity")); + + actions.push({ + type: "separator", + actionId: "_kicker_favorite_separator" + }); + + // Adding the items for each activity + + activities.forEach(function(activityId) { + addActivityItem(activityId, favoriteModel.activityNameForId(activityId)); + }); + + return [{ + text : i18n("Show In Favorites"), + icon : "favorite", + subActions : actions + }]; + } } function triggerAction(model, index, actionId, actionArgument) { function startsWith(txt, needle) { return txt.substr(0, needle.length) === needle; } if (startsWith(actionId, "_kicker_favorite_")) { handleFavoriteAction(actionId, actionArgument); return; } var closeRequested = model.trigger(index, actionId, actionArgument); if (closeRequested) { plasmoid.expanded = false; } } function handleFavoriteAction(actionId, actionArgument) { var favoriteId = actionArgument.favoriteId; var favoriteModel = actionArgument.favoriteModel; if (favoriteModel === null || favoriteId == null) { return null; } + if (actionId == "_kicker_favorite_remove") { - favoriteModel.removeFavorite(favoriteId); + console.log("Removing from all activities"); + favoriteModel.removeFavoriteFrom(favoriteId, ":any"); + } else if (actionId == "_kicker_favorite_add") { - favoriteModel.addFavorite(favoriteId); + console.log("Adding to global activity"); + favoriteModel.addFavoriteTo(favoriteId, ":global"); + + } else if (actionId == "_kicker_favorite_remove_from_activity") { + console.log("Removing from a specific activity"); + favoriteModel.removeFavoriteFrom(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_add_to_activity") { + console.log("Adding to another activity"); + favoriteModel.addFavoriteTo(favoriteId, actionArgument.favoriteActivity); + + } else if (actionId == "_kicker_favorite_set_to_activity") { + console.log("Removing the item from the favourites, and re-adding it just to be on a specific activity"); + favoriteModel.setFavoriteOn(favoriteId, actionArgument.favoriteActivity); + } } diff --git a/applets/kickoff/package/contents/config/main.xml b/applets/kickoff/package/contents/config/main.xml index 4e2e6d77b..be031dcab 100644 --- a/applets/kickoff/package/contents/config/main.xml +++ b/applets/kickoff/package/contents/config/main.xml @@ -1,47 +1,51 @@ true false start-here-kde preferred://browser,kontact.desktop,systemsettings.desktop,org.kde.dolphin.desktop,ktp-contactlist.desktop,org.kde.kate.desktop,org.kde.discover.desktop + + + false + systemsettings.desktop,org.kde.kinfocenter.desktop,org.kde.discover.desktop true shell,bookmarks,recentdocuments,locations,baloosearch bookmark:t,application:t,computer:t,used:t,oftenUsed:f,leave:t false diff --git a/applets/kickoff/package/contents/ui/ActionMenu.qml b/applets/kickoff/package/contents/ui/ActionMenu.qml index 6941059dd..3f624c14b 100644 --- a/applets/kickoff/package/contents/ui/ActionMenu.qml +++ b/applets/kickoff/package/contents/ui/ActionMenu.qml @@ -1,99 +1,144 @@ /*************************************************************************** * 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 . * ***************************************************************************/ import QtQuick 2.0 import org.kde.plasma.components 2.0 as PlasmaComponents Item { id: root property QtObject menu property Item visualParent property variant actionList property bool opened: menu ? (menu.status != PlasmaComponents.DialogStatus.Closed) : false signal actionClicked(string actionId, variant actionArgument) signal closed onActionListChanged: refreshMenu(); onOpenedChanged: { if (!opened) { closed(); } } function open(x, y) { if (!actionList || !actionList.length) { return; } if (x && y) { menu.open(x, y); } else { menu.open(); } } function refreshMenu() { if (menu) { menu.destroy(); } if (!actionList) { return; } menu = contextMenuComponent.createObject(root); - actionList.forEach(function(actionItem) { - var item = contextMenuItemComponent.createObject(menu, { - "actionItem": actionItem, - }); + // actionList.forEach(function(actionItem) { + // var item = contextMenuItemComponent.createObject(menu, { + // "actionItem": actionItem, + // }); + // }); + + fillMenu(menu, actionList); + } + + function fillMenu(menu, items) { + items.forEach(function(actionItem) { + if (actionItem.subActions) { + // This is a menu + var submenuItem = contextSubmenuItemComponent.createObject( + menu, { "actionItem" : actionItem }); + + fillMenu(submenuItem.submenu, actionItem.subActions); + + } else { + var item = contextMenuItemComponent.createObject( + menu, + { + "actionItem": actionItem, + } + ); + } }); + } Component { id: contextMenuComponent PlasmaComponents.ContextMenu { visualParent: root.visualParent } } Component { - id: contextMenuItemComponent + id: contextSubmenuItemComponent PlasmaComponents.MenuItem { + id: submenuItem + property variant actionItem text: actionItem.text ? actionItem.text : "" - enabled: actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) - separator: actionItem.type == "separator" - section: actionItem.type == "title" icon: actionItem.icon ? actionItem.icon : null + property variant submenu : submenu_ + + PlasmaComponents.ContextMenu { + id: submenu_ + visualParent: submenuItem.action + } + } + } + + Component { + id: contextMenuItemComponent + + PlasmaComponents.MenuItem { + property variant actionItem + + text : actionItem.text ? actionItem.text : "" + enabled : actionItem.type != "title" && ("enabled" in actionItem ? actionItem.enabled : true) + separator : actionItem.type == "separator" + section : actionItem.type == "title" + icon : actionItem.icon ? actionItem.icon : null + checkable : actionItem.checkable ? actionItem.checkable : false + checked : actionItem.checked ? actionItem.checked : false + onClicked: { actionClicked(actionItem.actionId, actionItem.actionArgument); } } } } diff --git a/applets/kickoff/package/contents/ui/FullRepresentation.qml b/applets/kickoff/package/contents/ui/FullRepresentation.qml index 9d9ebd5cd..a795cce02 100644 --- a/applets/kickoff/package/contents/ui/FullRepresentation.qml +++ b/applets/kickoff/package/contents/ui/FullRepresentation.qml @@ -1,610 +1,621 @@ /* 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" focus: true function switchToInitial() { if(firstButton != null) { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; header.query = "" header.state = "hint"; root.state = "Normal"; } } Kicker.AppsModel { id: rootModel appletInterface: plasmoid appNameFormat: plasmoid.configuration.showAppsByName ? 0 : 1 flat: false sorted: plasmoid.configuration.alphaSort showSeparators: false - favoritesModel: Kicker.FavoritesModel { + 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(); + } } Kicker.RunnerModel { id: runnerModel appletInterface: plasmoid runners: { var runners = ["services", "places", "desktopsessions", "PowerDevil"]; if (plasmoid.configuration.useExtraRunners) { runners = runners.concat(plasmoid.configuration.runners); } return runners; } mergeResults: true favoritesModel: globalFavorites } PlasmaCore.DataSource { id: pmSource engine: "powermanagement" connectedSources: ["PowerDevil"] } PlasmaCore.Svg { id: lineSvg imagePath: "widgets/line" } PlasmaCore.Svg { id: arrowsSvg imagePath: "widgets/arrows" size: "16x16" } Timer { id: clickTimer property Item pendingButton interval: 250 onTriggered: pendingButton.clicked() } 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 width: { if (!visible) { return 0; } switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: case PlasmaCore.Types.RightEdge: return units.gridUnit * 5; default: return 0; } } height: { if (!visible) { 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: root.forceActiveFocus(); Connections { target: plasmoid onExpandedChanged: { if(menuItemsChanged()) { createButtons(); } if (!expanded) { switchToInitial() } } } } // tabBar 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) { 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) { 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 { switchToInitial(); } event.accepted = true; break; } case Qt.Key_Menu: { currentView.openContextMenu(); event.accepted = true; break; } default: { // forward key to searchView //header.query += event.text will break if the key is backspace, //since if the user continues to type, it will produce an invalid query, //having backspace as the first character if (event.key == Qt.Key_Backspace && header.query == "") { return; } if (event.text != "" && !header.input.focus) { root.currentView.listView.currentIndex = -1; if (event.matches(StandardKey.Paste) ) { header.input.paste(); } else if (! (event.key & Qt.Key_Escape)) { //if special key, do nothing. Qt.Escape is 0x10000000 which happens to be a mask used for all special keys in Qt. header.query = ""; header.query += event.text; } header.input.forceActiveFocus(); event.accepted = true; } } } } states: [ State { name: "Normal" PropertyChanges { target: root Keys.forwardTo: [tabBar.layout] } PropertyChanges { target: tabBar visible: true } }, State { name: "Applications" PropertyChanges { target: root Keys.forwardTo: [root] } PropertyChanges { target: tabBar visible: true } }, State { name: "Search" PropertyChanges { target: tabBar visible: false } PropertyChanges { target: mainTabGroup currentTab: searchPage } } ] // 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); // 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; } }