diff --git a/containments/homescreen2/applicationlistmodel.cpp b/containments/homescreen2/applicationlistmodel.cpp index 8fb7175..80e61b7 100644 --- a/containments/homescreen2/applicationlistmodel.cpp +++ b/containments/homescreen2/applicationlistmodel.cpp @@ -1,308 +1,311 @@ /* * Copyright (C) 2014 Antonis Tsiapaliokas * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "applicationlistmodel.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include ApplicationListModel::ApplicationListModel(QObject *parent) : QAbstractListModel(parent) { //can't use the new syntax as this signal is overloaded connect(KSycoca::self(), SIGNAL(databaseChanged(const QStringList &)), this, SLOT(sycocaDbChanged(const QStringList &))); //here or delayed? loadApplications(); } ApplicationListModel::~ApplicationListModel() = default; QHash ApplicationListModel::roleNames() const { QHash roleNames; roleNames[ApplicationNameRole] = "ApplicationNameRole"; roleNames[ApplicationIconRole] = "ApplicationIconRole"; roleNames[ApplicationStorageIdRole] = "ApplicationStorageIdRole"; roleNames[ApplicationEntryPathRole] = "ApplicationEntryPathRole"; roleNames[ApplicationOriginalRowRole] = "ApplicationOriginalRowRole"; roleNames[ApplicationFavoriteRole] = "ApplicationFavoriteRole"; roleNames[ApplicationOnDesktopRole] = "ApplicationOnDesktopRole"; return roleNames; } void ApplicationListModel::sycocaDbChanged(const QStringList &changes) { if (!changes.contains("apps") && !changes.contains("xdgdata-apps")) { return; } m_applicationList.clear(); loadApplications(); } bool appNameLessThan(const ApplicationData &a1, const ApplicationData &a2) { return a1.name.toLower() < a2.name.toLower(); } void ApplicationListModel::loadApplications() { auto cfg = KSharedConfig::openConfig("applications-blacklistrc"); auto blgroup = KConfigGroup(cfg, QStringLiteral("Applications")); // This is only temporary to get a clue what those apps' desktop files are called // I'll remove it once I've done a blacklist QStringList bl; QStringList blacklist = blgroup.readEntry("blacklist", QStringList()); beginResetModel(); m_applicationList.clear(); KServiceGroup::Ptr group = KServiceGroup::root(); if (!group || !group->isValid()) return; KServiceGroup::List subGroupList = group->entries(true); QMap orderedList; QList unorderedList; // Iterate over all entries in the group while (!subGroupList.isEmpty()) { KSycocaEntry::Ptr groupEntry = subGroupList.first(); subGroupList.pop_front(); if (groupEntry->isType(KST_KServiceGroup)) { KServiceGroup::Ptr serviceGroup(static_cast(groupEntry.data())); if (!serviceGroup->noDisplay()) { KServiceGroup::List entryGroupList = serviceGroup->entries(true); for(KServiceGroup::List::ConstIterator it = entryGroupList.constBegin(); it != entryGroupList.constEnd(); it++) { KSycocaEntry::Ptr entry = (*it); if (entry->isType(KST_KServiceGroup)) { KServiceGroup::Ptr serviceGroup(static_cast(entry.data())); subGroupList << serviceGroup; } else if (entry->property("Exec").isValid()) { KService::Ptr service(static_cast(entry.data())); qDebug() << " desktopEntryName: " << service->desktopEntryName(); if (service->isApplication() && !blacklist.contains(service->desktopEntryName()) && service->showOnCurrentPlatform() && !service->property("Terminal", QVariant::Bool).toBool()) { bl << service->desktopEntryName(); ApplicationData data; data.name = service->name(); data.icon = service->icon(); data.storageId = service->storageId(); data.entryPath = service->exec(); auto it = m_appPositions.constFind(service->storageId()); if (it != m_appPositions.constEnd()) { orderedList[*it] = data; } else { unorderedList << data; } } } } } } } blgroup.writeEntry("allapps", bl); blgroup.writeEntry("blacklist", blacklist); cfg->sync(); std::sort(unorderedList.begin(), unorderedList.end(), appNameLessThan); m_applicationList << orderedList.values(); m_applicationList << unorderedList; endResetModel(); emit countChanged(); } QVariant ApplicationListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Qt::DisplayRole: case ApplicationNameRole: return m_applicationList.at(index.row()).name; case ApplicationIconRole: return m_applicationList.at(index.row()).icon; case ApplicationStorageIdRole: return m_applicationList.at(index.row()).storageId; case ApplicationEntryPathRole: return m_applicationList.at(index.row()).entryPath; case ApplicationOriginalRowRole: return index.row(); + case ApplicationOnDesktopRole: + return m_applicationList.at(index.row()).desktop; default: return QVariant(); } } Qt::ItemFlags ApplicationListModel::flags(const QModelIndex &index) const { if (!index.isValid()) return nullptr; return Qt::ItemIsDragEnabled|QAbstractItemModel::flags(index); } int ApplicationListModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_applicationList.count(); } void ApplicationListModel::moveRow(const QModelIndex& /* sourceParent */, int sourceRow, const QModelIndex& /* destinationParent */, int destinationChild) { moveItem(sourceRow, destinationChild); } void ApplicationListModel::setFavoriteItem(int row, bool favorite) { if (row < 0 || row >= m_applicationList.length()) { return; } ApplicationData &data = m_applicationList[row]; if (data.favorite == favorite) { return; } data.favorite = favorite; -qWarning()<= m_applicationList.length()) { return; } ApplicationData &data = m_applicationList[row]; if (data.desktop == desktop) { return; } data.desktop = desktop; +qWarning()<= m_applicationList.length() || destination >= m_applicationList.length() || row == destination) { return; } if (destination > row) { ++destination; } beginMoveRows(QModelIndex(), row, row, QModelIndex(), destination); if (destination > row) { ApplicationData data = m_applicationList.at(row); m_applicationList.insert(destination, data); m_applicationList.takeAt(row); } else { ApplicationData data = m_applicationList.takeAt(row); m_applicationList.insert(destination, data); } m_appOrder.clear(); m_appPositions.clear(); int i = 0; for (auto app : m_applicationList) { m_appOrder << app.storageId; m_appPositions[app.storageId] = i; ++i; } emit appOrderChanged(); endMoveRows(); } void ApplicationListModel::runApplication(const QString &storageId) { if (storageId.isEmpty()) { return; } KService::Ptr service = KService::serviceByStorageId(storageId); KRun::runService(*service, QList(), nullptr); } QStringList ApplicationListModel::appOrder() const { return m_appOrder; } void ApplicationListModel::setAppOrder(const QStringList &order) { if (m_appOrder == order) { return; } m_appOrder = order; m_appPositions.clear(); int i = 0; for (auto app : m_appOrder) { m_appPositions[app] = i; ++i; } emit appOrderChanged(); } diff --git a/containments/homescreen2/package/contents/ui/launcher/Delegate.qml b/containments/homescreen2/package/contents/ui/launcher/Delegate.qml index 849d91c..67d458e 100644 --- a/containments/homescreen2/package/contents/ui/launcher/Delegate.qml +++ b/containments/homescreen2/package/contents/ui/launcher/Delegate.qml @@ -1,136 +1,153 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager ContainmentLayoutManager.ItemContainer { id: delegate z: dragging ? 1 : 0 property var modelData: typeof model !== "undefined" ? model : null property bool dragging property ContainmentLayoutManager.ItemContainer dragDelegate leftPadding: units.smallSpacing * 2 topPadding: units.smallSpacing * 2 rightPadding: units.smallSpacing * 2 bottomPadding: units.smallSpacing * 2 opacity: dragging ? 0.4 : 1 + editModeCondition: model.ApplicationOnDesktopRole ? ContainmentLayoutManager.ItemContainer.AfterPressAndHold: ContainmentLayoutManager.ItemContainer.Manual + onDraggingChanged: { if (dragging) { var pos = dragDelegate.parent.mapFromItem(delegate, 0, 0); dragDelegate.parent = delegate.parent.parent; dragDelegate.x = delegate.x dragDelegate.y = delegate.y dragDelegate.modelData = model; root.reorderingApps = true; } else { dragDelegate.modelData = null; root.reorderingApps = false; } } contentItem: MouseArea { drag.target: dragging ? dragDelegate : null onClicked: { if (modelData.ApplicationStartupNotifyRole) { clickFedbackAnimation.target = delegate; clickFedbackAnimation.running = true; feedbackWindow.title = modelData.ApplicationNameRole; feedbackWindow.state = "open"; } plasmoid.nativeInterface.applicationListModel.runApplication(modelData.ApplicationStorageIdRole); } onPressAndHold: { + if (model.ApplicationOnDesktopRole) { + mouse.accepted = false + return + } delegate.dragging = true; } - onReleased: delegate.dragging = false; + onReleased: { + delegate.dragging = false; + + } onCanceled: delegate.dragging = false; onPositionChanged: { if (!dragging || !dragDelegate) { return; } var newRow = 0; + // Put it in the favorites strip if (favoriteStrip.contains(favoriteStrip.mapFromItem(dragDelegate, dragDelegate.width/2, dragDelegate.height/2))) { newRow = Math.floor((dragDelegate.x + dragDelegate.width/2) / dragDelegate.width); + // Put it on desktop + } else if (appletsLayout.contains(appletsLayout.mapFromItem(dragDelegate, dragDelegate.width/2, dragDelegate.height/2))) { + plasmoid.nativeInterface.applicationListModel.setDesktopItem(index, true); + return; + // Put it in the general view } else { newRow = Math.round(applicationsFlow.width / dragDelegate.width) * Math.floor((dragDelegate.y + dragDelegate.height/2) / dragDelegate.height) + Math.floor((dragDelegate.x + dragDelegate.width/2) / dragDelegate.width) + favoriteStrip.count; } + plasmoid.nativeInterface.applicationListModel.setDesktopItem(index, false); + plasmoid.nativeInterface.applicationListModel.moveItem(modelData.index, newRow); } ColumnLayout { anchors.fill: parent spacing: 0 PlasmaCore.IconItem { id: icon Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.fillWidth: true Layout.preferredHeight: parent.height - root.reservedSpaceForLabel source: modelData ? modelData.ApplicationIconRole : "" scale: root.reorderingApps && dragDelegate && !dragging ? 0.6 : 1 Behavior on scale { NumberAnimation { duration: units.longDuration easing.type: Easing.InOutQuad } } } PlasmaComponents.Label { id: label visible: text.length > 0 Layout.fillWidth: true Layout.fillHeight: true wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignTop maximumLineCount: 2 elide: Text.ElideRight text: modelData ? modelData.ApplicationNameRole : "" font.pixelSize: theme.defaultFont.pixelSize color: PlasmaCore.ColorScope.textColor } } } } diff --git a/containments/homescreen2/package/contents/ui/launcher/LauncherGrid.qml b/containments/homescreen2/package/contents/ui/launcher/LauncherGrid.qml index 0432691..727af73 100644 --- a/containments/homescreen2/package/contents/ui/launcher/LauncherGrid.qml +++ b/containments/homescreen2/package/contents/ui/launcher/LauncherGrid.qml @@ -1,118 +1,128 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.4 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.3 as Controls import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager Controls.Control { id: root readonly property bool dragging: applicationsFlow.dragData property bool reorderingApps: false property int availableCellHeight: units.iconSizes.huge + reservedSpaceForLabel readonly property int reservedSpaceForLabel: metrics.height readonly property int cellWidth: applicationsFlow.width / Math.floor(applicationsFlow.width / ((availableCellHeight - reservedSpaceForLabel) + units.smallSpacing*4)) readonly property int cellHeight: availableCellHeight - topPadding + property ContainmentLayoutManager.AppletsLayout appletsLayout property FavoriteStrip favoriteStrip signal externalDragStarted signal dragPositionChanged(point pos) implicitHeight: applicationsFlow.implicitHeight + frame.margins.top + frame.margins.bottom leftPadding: frame.margins.left topPadding: frame.margins.top rightPadding: frame.margins.right bottomPadding: frame.margins.bottom background: PlasmaCore.FrameSvgItem { id: frame imagePath: "widgets/background" anchors.fill: parent } contentItem: Item { //NOTE: TextMetrics can't handle multi line Controls.Label { id: metrics text: "M\nM" visible: false } //This Delegate is the placeholder for the "drag" //delegate (that is not actual drag and drop Delegate { id: dragDelegateItem z: 999 width: root.cellWidth height: root.cellHeight onYChanged: dragPositionChanged(Qt.point(x, y)) opacity: 1 visible: modelData !== null } Flow { id: applicationsFlow anchors.fill: parent spacing: 0 property var dragData property int startContentYDrag property bool viewHasBeenDragged NumberAnimation { id: scrollAnim target: applicationsFlow properties: "contentY" duration: units.longDuration easing.type: Easing.InOutQuad } move: Transition { NumberAnimation { duration: units.longDuration easing.type: Easing.InOutQuad properties: "x,y" } } Repeater { model: plasmoid.nativeInterface.applicationListModel delegate: Delegate { width: root.cellWidth height: root.cellHeight dragDelegate: dragDelegateItem - parent: index < favoriteStrip.count ? favoriteStrip.contentItem : applicationsFlow + parent: { + if (model.ApplicationOnDesktopRole) { + return appletsLayout; + } + if (index < favoriteStrip.count) { + return favoriteStrip.contentItem; + } + return applicationsFlow; + } } } } } } diff --git a/containments/homescreen2/package/contents/ui/main.qml b/containments/homescreen2/package/contents/ui/main.qml index 98d14f7..5ccdbd7 100644 --- a/containments/homescreen2/package/contents/ui/main.qml +++ b/containments/homescreen2/package/contents/ui/main.qml @@ -1,157 +1,172 @@ /* * Copyright 2019 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.12 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.2 as Controls import QtGraphicalEffects 1.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.draganddrop 2.0 as DragDrop import "launcher" as Launcher import org.kde.plasma.private.containmentlayoutmanager 1.0 as ContainmentLayoutManager Item { id: root width: 640 height: 480 property Item toolBox Text { text:"Edit Mode" color: "white" visible: plasmoid.editMode } Connections { target: plasmoid onEditModeChanged: { appletsLayout.editMode = plasmoid.editMode - if (plasmoid.editMode) { - menuRepeater.freeLayout(); - } else { - menuRepeater.relayout(); - } } } Flickable { id: mainFlickable - anchors.fill: parent + anchors { + fill: parent + // bottomMargin: favoriteStrip.height + } bottomMargin: favoriteStrip.height contentWidth: width - contentHeight: appletsLayout.height + contentHeight: flickableContents.height interactive: !plasmoid.editMode - DragDrop.DropArea { + ColumnLayout { + id: flickableContents width: parent.width - height: mainFlickable.height + launcher.height + DragDrop.DropArea { + Layout.fillWidth: true + Layout.preferredHeight: mainFlickable.height //TODO: multiple widgets pages - onDragEnter: { - event.accept(event.proposedAction); - } - onDragMove: { - appletsLayout.showPlaceHolderAt( - Qt.rect(event.x - appletsLayout.defaultItemWidth / 2, - event.y - appletsLayout.defaultItemHeight / 2, - appletsLayout.defaultItemWidth, - appletsLayout.defaultItemHeight) - ); - } + onDragEnter: { + event.accept(event.proposedAction); + } + onDragMove: { + appletsLayout.showPlaceHolderAt( + Qt.rect(event.x - appletsLayout.defaultItemWidth / 2, + event.y - appletsLayout.defaultItemHeight / 2, + appletsLayout.defaultItemWidth, + appletsLayout.defaultItemHeight) + ); + } - onDragLeave: { - appletsLayout.hidePlaceHolder(); - } + onDragLeave: { + appletsLayout.hidePlaceHolder(); + } - preventStealing: true + preventStealing: true - onDrop: { - plasmoid.processMimeData(event.mimeData, - event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2); - event.accept(event.proposedAction); - appletsLayout.hidePlaceHolder(); - } + onDrop: { + plasmoid.processMimeData(event.mimeData, + event.x - appletsLayout.placeHolder.width / 2, event.y - appletsLayout.placeHolder.height / 2); + event.accept(event.proposedAction); + appletsLayout.hidePlaceHolder(); + } - ContainmentLayoutManager.AppletsLayout { - id: appletsLayout + PlasmaCore.Svg { + id: arrowsSvg + imagePath: "widgets/arrows" + colorGroup: PlasmaCore.Theme.ComplementaryColorGroup + } + PlasmaCore.SvgItem { + anchors { + horizontalCenter: parent.horizontalCenter + bottom: parent.bottom + bottomMargin: favoriteStrip.height + } + z: 2 + svg: arrowsSvg + elementId: "up-arrow" + width: units.iconSizes.large + height: width + } - anchors.fill: parent + ContainmentLayoutManager.AppletsLayout { + id: appletsLayout - configKey: width > height ? "ItemGeometries" : "ItemGeometriesVertical" - containment: plasmoid - editModeCondition: plasmoid.immutable - ? ContainmentLayoutManager.AppletsLayout.Manual - : ContainmentLayoutManager.AppletsLayout.AfterPressAndHold + anchors.fill: parent - // Sets the containment in edit mode when we go in edit mode as well - onEditModeChanged: plasmoid.editMode = editMode + configKey: width > height ? "ItemGeometriesHorizontal" : "ItemGeometriesVertical" + containment: plasmoid + editModeCondition: plasmoid.immutable + ? ContainmentLayoutManager.AppletsLayout.Manual + : ContainmentLayoutManager.AppletsLayout.AfterPressAndHold - minimumItemWidth: units.gridUnit * 3 - minimumItemHeight: minimumItemWidth + // Sets the containment in edit mode when we go in edit mode as well + onEditModeChanged: plasmoid.editMode = editMode - defaultItemWidth: units.gridUnit * 6 - defaultItemHeight: defaultItemWidth + minimumItemWidth: units.gridUnit * 3 + minimumItemHeight: minimumItemWidth - cellWidth: units.iconSizes.small - cellHeight: cellWidth + defaultItemWidth: units.gridUnit * 6 + defaultItemHeight: defaultItemWidth - acceptsAppletCallback: function(applet, x, y) { - print("Applet: "+applet+" "+x+" "+y) - return true; - } + cellWidth: units.iconSizes.small + cellHeight: cellWidth - appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer { - id: appletContainer - configOverlayComponent: ConfigOverlay {} - onEditModeChanged: { - if (editMode) { - plasmoid.editMode = true; - } + acceptsAppletCallback: function(applet, x, y) { + print("Applet: "+applet+" "+x+" "+y) + return true; } - } - - placeHolder: ContainmentLayoutManager.PlaceHolder {} - Launcher.LauncherGrid { - id: launcher - favoriteStrip: favoriteStrip - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom + appletContainerComponent: ContainmentLayoutManager.BasicAppletContainer { + id: appletContainer + configOverlayComponent: ConfigOverlay {} + onEditModeChanged: { + if (editMode) { + plasmoid.editMode = true; + } + } } + + placeHolder: ContainmentLayoutManager.PlaceHolder {} } } + Launcher.LauncherGrid { + id: launcher + Layout.fillWidth: true + + favoriteStrip: favoriteStrip + appletsLayout: appletsLayout + } } } Launcher.FavoriteStrip { id: favoriteStrip + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(root.width, units.gridUnit * 30) launcherGrid: launcher - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } + y: Math.max(0, root.height - height - mainFlickable.contentY) } }