diff --git a/applets/notifications/notificationapplet.h b/applets/notifications/notificationapplet.h --- a/applets/notifications/notificationapplet.h +++ b/applets/notifications/notificationapplet.h @@ -32,6 +32,7 @@ Q_OBJECT Q_PROPERTY(bool dragActive READ dragActive NOTIFY dragActiveChanged) + Q_PROPERTY(int dragPixmapSize READ dragPixmapSize WRITE setDragPixmapSize NOTIFY dragPixmapSizeChanged) Q_PROPERTY(QWindow *focussedPlasmaDialog READ focussedPlasmaDialog NOTIFY focussedPlasmaDialogChanged) @@ -43,23 +44,32 @@ void configChanged() override; bool dragActive() const; + + int dragPixmapSize() const; + void setDragPixmapSize(int dragPixmapSize); + Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; + Q_INVOKABLE void startDrag(QQuickItem *item, const QUrl &url, const QString &iconName); Q_INVOKABLE void startDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap); QWindow *focussedPlasmaDialog() const; Q_INVOKABLE void setSelectionClipboardText(const QString &text); Q_INVOKABLE bool isPrimaryScreen(const QRect &rect) const; + Q_INVOKABLE QString iconNameForUrl(const QUrl &url) const; + signals: void dragActiveChanged(); + void dragPixmapSizeChanged(); void focussedPlasmaDialogChanged(); private slots: void doDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap); private: bool m_dragActive = false; + int m_dragPixmapSize = 48; // Bound to units.iconSizes.large in main.qml }; diff --git a/applets/notifications/notificationapplet.cpp b/applets/notifications/notificationapplet.cpp --- a/applets/notifications/notificationapplet.cpp +++ b/applets/notifications/notificationapplet.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include #include #include @@ -69,11 +71,29 @@ return m_dragActive; } +int NotificationApplet::dragPixmapSize() const +{ + return m_dragPixmapSize; +} + +void NotificationApplet::setDragPixmapSize(int dragPixmapSize) +{ + if (m_dragPixmapSize != dragPixmapSize) { + m_dragPixmapSize = dragPixmapSize; + emit dragPixmapSizeChanged(); + } +} + bool NotificationApplet::isDrag(int oldX, int oldY, int newX, int newY) const { return ((QPoint(oldX, oldY) - QPoint(newX, newY)).manhattanLength() >= qApp->styleHints()->startDragDistance()); } +void NotificationApplet::startDrag(QQuickItem *item, const QUrl &url, const QString &iconName) +{ + startDrag(item, url, QIcon::fromTheme(iconName).pixmap(m_dragPixmapSize, m_dragPixmapSize)); +} + void NotificationApplet::startDrag(QQuickItem *item, const QUrl &url, const QPixmap &pixmap) { // This allows the caller to return, making sure we don't crash if @@ -135,6 +155,16 @@ return rect == screen->geometry(); } +QString NotificationApplet::iconNameForUrl(const QUrl &url) const +{ + QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); + if (mime.isDefault()) { + return QString(); + } + + return mime.iconName(); +} + K_EXPORT_PLASMA_APPLET_WITH_JSON(icon, NotificationApplet, "metadata.json") #include "notificationapplet.moc" diff --git a/applets/notifications/package/contents/ui/JobItem.qml b/applets/notifications/package/contents/ui/JobItem.qml --- a/applets/notifications/package/contents/ui/JobItem.qml +++ b/applets/notifications/package/contents/ui/JobItem.qml @@ -44,6 +44,26 @@ // TOOD make an alias on visible if we're not doing an animation property bool showDetails + readonly property int totalFiles: jobItem.jobDetails && jobItem.jobDetails.totalFiles || 0 + readonly property var url: { + if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped + || jobItem.jobError + || totalFiles <= 0) { + return null; + } + + // For a single file show actions for it + if (totalFiles === 1) { + return jobItem.jobDetails.descriptionUrl; + // Otherwise the destination folder all of them were copied into + } else { + return jobItem.jobDetails.destUrl; + } + } + + property alias iconContainerItem: jobDragIcon.parent + + readonly property alias dragging: jobDragArea.dragging readonly property alias menuOpen: otherFileActionsMenu.visible signal suspendJobClicked @@ -55,6 +75,43 @@ spacing: 0 + // This item is parented to the NotificationItem iconContainer + PlasmaCore.IconItem { + id: jobDragIcon + width: parent ? parent.width : 0 + height: parent ? parent.height : 0 + usesPlasmaTheme: false + visible: valid + active: jobDragArea.containsMouse + source: jobItem.totalFiles === 1 && jobItem.url ? plasmoid.nativeInterface.iconNameForUrl(jobItem.url) : "" + + Binding { + target: jobDragIcon.parent + property: "visible" + value: true + when: jobDragIcon.valid + } + + DraggableFileArea { + id: jobDragArea + anchors.fill: parent + + hoverEnabled: true + dragParent: jobDragIcon + dragUrl: jobItem.url || "" + dragPixmap: jobDragIcon.source + + onActivated: jobItem.openUrl(jobItem.url) + onContextMenuRequested: { + // avoid menu button glowing if we didn't actually press it + otherFileActionsButton.checked = false; + + otherFileActionsMenu.visualParent = this; + otherFileActionsMenu.open(x, y); + } + } + } + RowLayout { id: progressRow Layout.fillWidth: true @@ -114,30 +171,13 @@ } Flow { // it's a Flow so it can wrap if too long - id: jobDoneActions Layout.fillWidth: true spacing: units.smallSpacing // We want the actions to be right-aligned but Flow also reverses // the order of items, so we put them in reverse order layoutDirection: Qt.RightToLeft visible: url && url.toString() !== "" - property var url: { - if (jobItem.jobState !== NotificationManager.Notifications.JobStateStopped - || jobItem.jobError - || !jobItem.jobDetails - || jobItem.jobDetails.totalFiles <= 0) { - return null; - } - - // For a single file show actions for it - if (jobItem.jobDetails.totalFiles === 1) { - return jobItem.jobDetails.descriptionUrl; - } else { - return jobItem.jobDetails.destUrl; - } - } - PlasmaComponents.Button { id: otherFileActionsButton height: Math.max(implicitHeight, openButton.implicitHeight) @@ -149,14 +189,15 @@ checked = Qt.binding(function() { return otherFileActionsMenu.visible; }); + otherFileActionsMenu.visualParent = this; + // -1 tells it to "align bottom left of visualParent (this)" otherFileActionsMenu.open(-1, -1); } } Notifications.FileMenu { id: otherFileActionsMenu - url: jobDoneActions.url || "" - visualParent: otherFileActionsButton + url: jobItem.url || "" onActionTriggered: jobItem.fileActionInvoked() } } @@ -168,7 +209,7 @@ text: jobItem.jobDetails && jobItem.jobDetails.totalFiles > 1 ? i18nd("plasma_applet_org.kde.plasma.notifications", "Open Containing Folder") : i18nd("plasma_applet_org.kde.plasma.notifications", "Open") - onClicked: jobItem.openUrl(jobDoneActions.url) + onClicked: jobItem.openUrl(jobItem.url) width: minimumWidth } } diff --git a/applets/notifications/package/contents/ui/NotificationItem.qml b/applets/notifications/package/contents/ui/NotificationItem.qml --- a/applets/notifications/package/contents/ui/NotificationItem.qml +++ b/applets/notifications/package/contents/ui/NotificationItem.qml @@ -85,7 +85,8 @@ readonly property bool menuOpen: bodyLabel.contextMenu !== null || (thumbnailStripLoader.item && thumbnailStripLoader.item.menuOpen) || (jobLoader.item && jobLoader.item.menuOpen) - readonly property bool dragging: thumbnailStripLoader.item && thumbnailStripLoader.item.dragging + readonly property bool dragging: (thumbnailStripLoader.item && thumbnailStripLoader.item.dragging) + || (jobLoader.item && jobLoader.item.dragging) signal bodyClicked(var mouse) signal closeClicked @@ -247,6 +248,8 @@ visible: active image: typeof notificationItem.icon === "object" ? notificationItem.icon : undefined } + + // JobItem reparents a file icon here for finished jobs with one total file } } @@ -257,6 +260,8 @@ active: notificationItem.notificationType === NotificationManager.Notifications.JobType visible: active sourceComponent: JobItem { + iconContainerItem: iconContainer + jobState: notificationItem.jobState jobError: notificationItem.jobError percentage: notificationItem.percentage diff --git a/applets/notifications/package/contents/ui/main.qml b/applets/notifications/package/contents/ui/main.qml --- a/applets/notifications/package/contents/ui/main.qml +++ b/applets/notifications/package/contents/ui/main.qml @@ -132,6 +132,12 @@ } } + Binding { + target: plasmoid.nativeInterface + property: "dragPixmapSize" + value: units.iconSizes.large + } + function action_clearHistory() { historyModel.clear(NotificationManager.Notifications.ClearExpired); if (historyModel.count === 0) {