[graphics/koko] src: Add paste action

Oliver Beard <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 6abd9f6134d95f6819bc50e4e33d4ed50b3772f2 by Oliver Beard.
Committed on 23/07/2026 at 08:00.
Pushed by olib into branch 'master'.

Add paste action

When nothing is selected, show a paste action. Since the gallery context menu selects the item, this is only shown as a page action.

Pasted items are selected, and the last pasted item is set as the current index.

In future, if we want to show actions for the current directory (by right-clicking empty area), we can have no selection and act on `FileMenuManager::rootUrl`.

M  +50   -0    src/filemenumanager.cpp
M  +12   -0    src/filemenumanager.h
M  +3    -0    src/qml/ActionCollection.qml
M  +45   -0    src/qml/gallery/GalleryPage.qml

https://invent.kde.org/graphics/koko/-/commit/6abd9f6134d95f6819bc50e4e33d4ed50b3772f2

diff --git a/src/filemenumanager.cpp b/src/filemenumanager.cpp
index 02a01f91..e3e66c10 100644
--- a/src/filemenumanager.cpp
+++ b/src/filemenumanager.cpp
@@ -40,6 +40,8 @@
 #include <KIO/JobUiDelegate>
 #include <KIO/JobUiDelegateFactory>
 #include <KIO/OpenFileManagerWindowJob>
+#include <KIO/Paste>
+#include <KIO/PasteJob>
 #include <KIO/RenameFileDialog>
 #include <KIO/WidgetsAskUserActionHandler>
 
@@ -48,6 +50,7 @@ using namespace Qt::StringLiterals;
 FileMenuManager::FileMenuManager(QObject *parent)
     : QObject(parent)
 {
+    connect(QApplication::clipboard(), &QClipboard::dataChanged, this, &FileMenuManager::updateActions);
 }
 
 QList<QUrl> FileMenuManager::urls() const
@@ -68,6 +71,24 @@ void FileMenuManager::setUrls(const QList<QUrl> &urls)
     Q_EMIT urlsChanged();
 }
 
+QUrl FileMenuManager::rootUrl() const
+{
+    return m_rootUrl;
+}
+
+void FileMenuManager::setRootUrl(const QUrl &url)
+{
+    if (m_rootUrl == url) {
+        return;
+    }
+
+    m_rootUrl = url;
+
+    updateActions();
+
+    Q_EMIT rootUrlChanged();
+}
+
 bool FileMenuManager::enabled() const
 {
     return m_enabled;
@@ -111,6 +132,11 @@ bool FileMenuManager::canCopyPath() const
     return m_canCopyPath;
 }
 
+bool FileMenuManager::canPaste() const
+{
+    return m_canPaste;
+}
+
 bool FileMenuManager::canRenameFile() const
 {
     return m_canRenameFile;
@@ -347,6 +373,30 @@ void FileMenuManager::updateActions()
     }
     Q_EMIT canCopyPathChanged();
 
+    // Paste action
+    m_canPaste = false;
+    disconnectStandardAction(KStandardActions::Paste);
+    if (m_enabled && !hasFile && KFileItem(m_rootUrl).isWritable()) {
+        m_canPaste = true;
+        auto pasteLambda = [this] {
+            if (!m_enabled) {
+                return;
+            }
+
+            auto pastedUrls = std::make_shared<QList<QUrl>>();
+
+            auto pasteJob = KIO::paste(QApplication::clipboard()->mimeData(), m_rootUrl);
+            connect(pasteJob, &KIO::PasteJob::itemCreated, this, [pastedUrls](const QUrl &url) {
+                *pastedUrls << url;
+            });
+            connect(pasteJob, &KJob::finished, this, [this, pastedUrls]() {
+                Q_EMIT this->pastedUrls(*pastedUrls);
+            });
+        };
+        connectStandardAction(KStandardActions::Paste, pasteLambda);
+    }
+    Q_EMIT canPasteChanged();
+
     // Rename File action
     m_canRenameFile = false;
     disconnectStandardAction(KStandardActions::RenameFile);
diff --git a/src/filemenumanager.h b/src/filemenumanager.h
index bae9962a..98ea6e41 100644
--- a/src/filemenumanager.h
+++ b/src/filemenumanager.h
@@ -20,6 +20,7 @@ class FileMenuManager : public QObject
     Q_OBJECT
 
     Q_PROPERTY(QList<QUrl> urls READ urls WRITE setUrls NOTIFY urlsChanged FINAL)
+    Q_PROPERTY(QUrl rootUrl READ rootUrl WRITE setRootUrl NOTIFY rootUrlChanged FINAL)
     Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL)
     Q_PROPERTY(QQuickWindow *window MEMBER m_window FINAL)
 
@@ -28,6 +29,7 @@ class FileMenuManager : public QObject
     Q_PROPERTY(bool canOpenWith READ canOpenWith NOTIFY canOpenWithChanged FINAL)
     Q_PROPERTY(bool canCopy READ canCopy NOTIFY canCopyChanged FINAL)
     Q_PROPERTY(bool canCopyPath READ canCopyPath NOTIFY canCopyPathChanged FINAL)
+    Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged FINAL)
     Q_PROPERTY(bool canRenameFile READ canRenameFile NOTIFY canRenameFileChanged FINAL)
     Q_PROPERTY(bool canMoveToTrash READ canMoveToTrash NOTIFY canMoveToTrashChanged FINAL)
     Q_PROPERTY(bool canDeleteFile READ canDeleteFile NOTIFY canDeleteFileChanged FINAL)
@@ -42,6 +44,9 @@ public:
     QList<QUrl> urls() const;
     void setUrls(const QList<QUrl> &urls);
 
+    QUrl rootUrl() const;
+    void setRootUrl(const QUrl &url);
+
     bool enabled() const;
     void setEnabled(const bool enabled);
 
@@ -50,6 +55,7 @@ public:
     bool canOpenWith() const;
     bool canCopy() const;
     bool canCopyPath() const;
+    bool canPaste() const;
     bool canRenameFile() const;
     bool canMoveToTrash() const;
     bool canDeleteFile() const;
@@ -58,20 +64,25 @@ public:
 
 Q_SIGNALS:
     void urlsChanged();
+    void rootUrlChanged();
     void enabledChanged();
     void canSaveAsChanged();
     void canOpenFolderChanged();
     void canOpenWithChanged();
     void canCopyChanged();
     void canCopyPathChanged();
+    void canPasteChanged();
     void canRenameFileChanged();
     void canMoveToTrashChanged();
     void canDeleteFileChanged();
     void canPrintChanged();
     void canPropertiesChanged();
 
+    void pastedUrls(QList<QUrl> urls);
+
 private:
     QList<QUrl> m_urls;
+    QUrl m_rootUrl;
     bool m_enabled = false;
     QQuickWindow *m_window = nullptr;
 
@@ -80,6 +91,7 @@ private:
     bool m_canOpenWith = false;
     bool m_canCopy = false;
     bool m_canCopyPath = false;
+    bool m_canPaste = false;
     bool m_canRenameFile = false;
     bool m_canMoveToTrash = false;
     bool m_canDeleteFile = false;
diff --git a/src/qml/ActionCollection.qml b/src/qml/ActionCollection.qml
index ef2c5661..20ec3a45 100644
--- a/src/qml/ActionCollection.qml
+++ b/src/qml/ActionCollection.qml
@@ -121,6 +121,9 @@ AC.ActionCollectionManager {
             text: i18nc("@action:inmenu", "Copy Location")
             icon.name: "edit-copy-path"
         }
+        AC.StandardActionData {
+            standardAction: AC.StandardActionData.Paste
+        }
         AC.StandardActionData {
             standardAction: AC.StandardActionData.RenameFile
         }
diff --git a/src/qml/gallery/GalleryPage.qml b/src/qml/gallery/GalleryPage.qml
index 565a54d9..b26e347b 100644
--- a/src/qml/gallery/GalleryPage.qml
+++ b/src/qml/gallery/GalleryPage.qml
@@ -362,8 +362,46 @@ Kirigami.ScrollablePage {
     Koko.FileMenuManager {
         id: fileMenuManager
         urls: selectionModel.selectedIndexes.map(index => selectionModel.model.data(index, AbstractGalleryModel.UrlRole))
+        rootUrl: page.galleryModel.path
         enabled: page.visible && page.enabled
         window: page.Window.window
+
+        // Handle selection of pasted items:
+
+        property url lastPastedUrl
+        property var pastedUrls: new Set()
+
+        onPastedUrls: (urls) => {
+            urls.forEach(url => fileMenuManager.pastedUrls.add(url));
+            fileMenuManager.lastPastedUrl = urls[urls.length - 1];
+            selectionModel.clearSelection();
+
+            // If it turns out we get told about pasted urls after the model includes them:
+            // onRowsInserted(undefined, 0, gridView.model.rowCount() - 1);
+        }
+
+        function onRowsInserted(parent, first, last) {
+            if (pastedUrls.size === 0) {
+                return;
+            }
+
+            for (let row = first; row <= last; ++row) {
+                const index = gridView.model.index(row, 0);
+                const url = gridView.model.data(index, AbstractGalleryModel.UrlRole);
+
+                if (fileMenuManager.pastedUrls.has(url)) {
+                    fileMenuManager.pastedUrls.delete(url);
+                    selectionModel.select(index, ItemSelectionModel.Select);
+
+                    if (url === fileMenuManager.lastPastedUrl) {
+                        gridView.currentIndex = row;
+                        fileMenuManager.lastPastedUrl = undefined;
+                    }
+                }
+            }
+        }
+
+        Component.onCompleted: gridView.model.rowsInserted.connect(onRowsInserted)
     }
 
     readonly property list<Kirigami.Action> fileMenuActions: [
@@ -402,6 +440,13 @@ Kirigami.ScrollablePage {
             AC.ActionCollection.action: "CopyPath"
             AC.ActionCollection.collection: "org.kde.koko.file"
         },
+        Kirigami.Action {
+            displayHint: Kirigami.DisplayHint.AlwaysHide
+            enabled: fileMenuManager.enabled && fileMenuManager.canPaste && !page.showingCollections
+            visible: enabled
+            AC.ActionCollection.action: AC.StandardActionData.Paste
+            AC.ActionCollection.collection: "org.kde.koko.file"
+        },
         Kirigami.Action {
             displayHint: Kirigami.DisplayHint.AlwaysHide
             enabled: fileMenuManager.enabled && fileMenuManager.canRenameFile && !page.showingCollections
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.