Commit a22c44da authored by Kai Uwe Broulik's avatar Kai Uwe Broulik 🍇
Browse files

[Notifications] Add draggable file icon when single file job finishes

This adds a file icon to a finished job for a single total file which can be dragged anywhere for convenience.

Differential Revision: https://phabricator.kde.org/D25782
parent 523ef71c
......@@ -25,6 +25,8 @@
#include <QDrag>
#include <QGuiApplication>
#include <QMimeData>
#include <QMimeDatabase>
#include <QMimeType>
#include <QQuickItem>
#include <QQuickWindow>
#include <QScreen>
......@@ -69,11 +71,29 @@ bool NotificationApplet::dragActive() const
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 @@ bool NotificationApplet::isPrimaryScreen(const QRect &rect) const
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"
......@@ -32,6 +32,7 @@ class NotificationApplet : public Plasma::Applet
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,7 +44,12 @@ public:
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;
......@@ -52,8 +58,11 @@ public:
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:
......@@ -61,5 +70,6 @@ private slots:
private:
bool m_dragActive = false;
int m_dragPixmapSize = 48; // Bound to units.iconSizes.large in main.qml
};
......@@ -44,6 +44,26 @@ ColumnLayout {
// 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 @@ ColumnLayout {
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,7 +171,6 @@ ColumnLayout {
}
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
......@@ -122,22 +178,6 @@ ColumnLayout {
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 @@ ColumnLayout {
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 @@ ColumnLayout {
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
}
}
......
......@@ -85,7 +85,8 @@ ColumnLayout {
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 @@ ColumnLayout {
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 @@ ColumnLayout {
active: notificationItem.notificationType === NotificationManager.Notifications.JobType
visible: active
sourceComponent: JobItem {
iconContainerItem: iconContainer
jobState: notificationItem.jobState
jobError: notificationItem.jobError
percentage: notificationItem.percentage
......
......@@ -132,6 +132,12 @@ Item {
}
}
Binding {
target: plasmoid.nativeInterface
property: "dragPixmapSize"
value: units.iconSizes.large
}
function action_clearHistory() {
historyModel.clear(NotificationManager.Notifications.ClearExpired);
if (historyModel.count === 0) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment