Any way to remove entries from "move/copy to" menu's?

As the title suggests. I use the “move to” context menu option to move files around, and it’s something I do frequently. The menu is currently cluttered with old/unused/non-existant folders and it’s a pain to sort thru them.

Is there no option to clear or forget saved selections? I couldn’t find it in any option anywhere I looked

I don’t think the non-existent folder part is possible.
The menu lists your actual filesystem, if you have too many entries that’s your $HOME folder that needs cleaning.

Indeed. Things can get complicated…

I had issues with my Audio stuff.

I have some ‘Resources’ on my T4 disk (system sounds and interesting audio). I also have Audiobooks which live on my W4 disk.

Then in Home, I have some local stuff, and some work in progress.

I consolidate these by creating an essentially empty ‘Audio’ folder in ~/home which contains essential (favourite) system sounds, but then simply links to all the other sources.

It is TRUE that it can be a pain to sort through them… but let’s ask the difficult question.

Who is to blame for that? We need to manage our own S41T and clean it up. It’s easy to remove a folder and consolidate the data somewhere else, or simply move that folder to a more suitable location.

There are two excellent ways to manage moving files around aside from this.

  1. Places
  2. zoxide

If I download a TV show, I can hit F3 for dualpane, then in my Places I can select the (parent) media folders on each of my disks - then just Shift_ F5/F6 to copy or move across (or hold Shift/Ctrl/Shift+Ctrl whilst dragging to move, copy, or link).

But sometimes it’s complicated - I have a ‘less organised’ TV folder, then I have an auto organised ‘TV_Shows’ folder in each of my media folders… with two main media disks, that’s four folders.

So then zoxide works well… in Dolphin I hit F3 for a dualpane, then F4 to pull up konsole where I type zi TV.** notify “Bluetooth Connected” “$DEVICE_NAME was connected” “$CONNECT_SOUND”**

This brings up a list (from commonly to less commonly visited) of the actual total 5 folders (I forgot the long term archive on W2) to choose from.

zoxide remembers locations, and you can add or delete those (as with Places and Bookmarks).

I think the entries in Copy To and Move To context menus you want to cleanup, if I didn’t misunderstood, are in the file ~/.config/dolphinrc under headings [kuick-copy] and [kuick-move], except the top ones, i.e. Home Folder, Root Folder and Browse…, which seem stationary.

Excellent - now I know where I can edit to add a path if I need it :vulcan_salute:

It is possible. I’m not talking about the “browse” section, it’s the remembered locations that have been used before that are the issue. I have entries on there from drives that have been shredded, and disconnected media.

My file system is organized, thanks.

Much appreciated. For some reason I couldn’t find the answer for where that was stored on google. Either I’m getting dumber or google is getting worse.

I manage my stuff just fine thanks. Your “solution” is pointlessly convoluted when “move to” is a simple right click option. But it appears you just like to hear yourself talk, so enjoy.

Exactly that. It’s simple and whilst it can become cluttered with old folders, it rarely includes any new/recently visited folders.

To state that it is so simple… your perspective is fascinating.

So tell me, the ‘simple’ way to do this when the folder in question is something like this:

/mnt/W4/backintime/SteelLegend/ben/1/20260729-200401-837/backup/home/ben/.icons/GoldenXMod/cursors/h_double_arrow

Are you suggesting that to use the context menu and ‘browse’ is more efficient and simpler than using a Places entry or a Bookmark (together with dual pane?).

The point here being that the context menu is simply another iteration of such tools…, helping remembering and managing locations.

In some ways less useful because it is obviously less simple to edit and change such locations to suit without editing a config file.

Of course, neither you nor Google is getting dumber; it’s just me getting sharper at 68. :grinning_face:
Joke aside, maybe developers might like to consider the feasibility of checking those entries to remove those which are no longer applicable when Dolphin starts? @meven

Can this be use to add entries? Sometimes it’s hard to mouse to the location in the uppermost section and would be nice to just edit a file and drop a list of say the 20 most use locations in for copy / move.

Guess
:person_shrugging:

Of course! I’ve just tried (although I never needed) adding all standard user directories, and re-arranging both two lists, and the new list was displayed after restarting Dolphin.
One can even write a simple shell script that makes editing these two lists easier.

Worth a bug report. https://bugs.kde.org/

That’s a very easy thing to fix, not saying i will be the one doing it.

I didn’t now there was an history for this menu, I don’t use it myself.
I have been developing Dolphin for years and I still don’t know all of its features.

Done.

After a brief interaction with AI, I now have a very handy Python (PyQt6) script which lists current user-added paths for “Copy To” and “Move To” commands from ~/.config/dolphinrc, enabling the user to add/remove any as well as verifying all at the end, and asking the user to remove or keep them.
I’ve added this script to top-level Dolphin context menu as a service under the name “Manage Quick Paths…
EDIT: The script has been updated to include automatic sorting of paths alphabetically.

#!/usr/bin/env python3
import sys
import os
import shutil
import re
from pathlib import Path
from PyQt6.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel,
    QListWidget, QListWidgetItem, QPushButton, QLineEdit,
    QCheckBox, QFileDialog, QGroupBox, QMessageBox, QDialog
)
from PyQt6.QtCore import Qt

CONFIG_PATH = Path.home() / ".config" / "dolphinrc"

class DolphinPathManager(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dolphin Quick-Paths Manager")
        self.resize(600, 500)

        self.copy_paths = []
        self.move_paths = []

        self.init_ui()
        self.load_config()

    def init_ui(self):
        main_layout = QVBoxLayout(self)

        # --- EXISTING PATHS SECTION ---
        list_group = QGroupBox("Existing Quick-Paths (Check items to REMOVE)")
        list_layout = QVBoxLayout(list_group)

        self.path_list = QListWidget()
        list_layout.addWidget(self.path_list)

        btn_remove = QPushButton("Remove Selected Items")
        btn_remove.clicked.connect(self.remove_checked_items)
        list_layout.addWidget(btn_remove)

        main_layout.addWidget(list_group)

        # --- ADD NEW PATH SECTION ---
        add_group = QGroupBox("Add New Path")
        add_layout = QVBoxLayout(add_group)

        path_input_layout = QHBoxLayout()
        self.path_input = QLineEdit()
        self.path_input.setPlaceholderText("Select or enter a folder path...")
        btn_browse = QPushButton("Browse...")
        btn_browse.clicked.connect(self.browse_folder)

        path_input_layout.addWidget(self.path_input)
        path_input_layout.addWidget(btn_browse)
        add_layout.addLayout(path_input_layout)

        options_layout = QHBoxLayout()
        self.chk_copy = QCheckBox("Add to 'Copy To'")
        self.chk_copy.setChecked(True)
        self.chk_move = QCheckBox("Add to 'Move To'")
        self.chk_move.setChecked(True)

        options_layout.addWidget(self.chk_copy)
        options_layout.addWidget(self.chk_move)
        options_layout.addStretch()

        btn_add = QPushButton("Add Path")
        btn_add.clicked.connect(self.add_new_path)
        options_layout.addWidget(btn_add)

        add_layout.addLayout(options_layout)
        main_layout.addWidget(add_group)

        # --- SAVE / CLOSE BUTTONS ---
        action_layout = QHBoxLayout()
        action_layout.addStretch()

        btn_save = QPushButton("Save && Exit")
        btn_save.setStyleSheet("font-weight: bold;")
        btn_save.clicked.connect(self.save_and_exit)
        action_layout.addWidget(btn_save)

        btn_cancel = QPushButton("Cancel")
        btn_cancel.clicked.connect(self.close)
        action_layout.addWidget(btn_cancel)

        main_layout.addLayout(action_layout)

    def sort_paths(self):
        """Sorts copy and move path lists alphabetically (case-insensitive)."""
        self.copy_paths.sort(key=str.lower)
        self.move_paths.sort(key=str.lower)

    def load_config(self):
        """Reads paths from dolphinrc."""
        if not CONFIG_PATH.exists():
            QMessageBox.warning(self, "Warning", f"Configuration file not found:\n{CONFIG_PATH}")
            return

        with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        current_section = None
        for line in lines:
            line_str = line.strip()
            if line_str.startswith('[') and line_str.endswith(']'):
                current_section = line_str
                continue

            if current_section in ('[kuick-copy]', '[kuick-move]') and line_str.startswith('Paths'):
                if '=' in line_str:
                    raw_paths = line_str.split('=', 1)[1].split(',')
                    parsed_paths = [
                        os.path.abspath(os.path.expanduser(p.strip()))
                        for p in raw_paths if p.strip()
                    ]
                    if current_section == '[kuick-copy]':
                        self.copy_paths = parsed_paths
                    else:
                        self.move_paths = parsed_paths

        # Store initial state for change detection
        self.initial_copy_paths = list(self.copy_paths)
        self.initial_move_paths = list(self.move_paths)

        # Re-order paths alphabetically
        self.sort_paths()

        self.refresh_list_widget()

    def refresh_list_widget(self):
        """Populates the list widget with existing unique paths."""
        self.path_list.clear()
        all_unique = sorted(list(set(self.copy_paths + self.move_paths)))

        for path in all_unique:
            in_copy = path in self.copy_paths
            in_move = path in self.move_paths

            tags = []
            if in_copy: tags.append("Copy To")
            if in_move: tags.append("Move To")
            label = f"{path}  [{', '.join(tags)}]"

            item = QListWidgetItem(label)
            item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
            item.setCheckState(Qt.CheckState.Unchecked)
            item.setData(Qt.ItemDataRole.UserRole, path)
            self.path_list.addItem(item)

    def browse_folder(self):
        folder = QFileDialog.getExistingDirectory(self, "Select Folder", str(Path.home()))
        if folder:
            self.path_input.setText(folder)

    def remove_checked_items(self):
        """Removes checked items from memory."""
        for i in range(self.path_list.count() - 1, -1, -1):
            item = self.path_list.item(i)
            if item.checkState() == Qt.CheckState.Checked:
                path = item.data(Qt.ItemDataRole.UserRole)
                if path in self.copy_paths: self.copy_paths.remove(path)
                if path in self.move_paths: self.move_paths.remove(path)

        self.refresh_list_widget()

    def add_new_path(self):
        path = self.path_input.text().strip()
        if not path:
            return

        path = os.path.abspath(os.path.expanduser(path))
        if not path.endswith('/'):
            path += '/'

        if not self.chk_copy.isChecked() and not self.chk_move.isChecked():
            QMessageBox.warning(self, "Selection Required", "Please select at least one menu item ('Copy To' or 'Move To').")
            return

        if self.chk_copy.isChecked() and path not in self.copy_paths:
            self.copy_paths.append(path)

        if self.chk_move.isChecked() and path not in self.move_paths:
            self.move_paths.append(path)

        # Re-order paths alphabetically after adding
        self.sort_paths()

        self.path_input.clear()
        self.refresh_list_widget()

    def get_invalid_paths(self):
        """Returns a sorted list of unique paths that do not exist on disk."""
        all_paths = set(self.copy_paths + self.move_paths)
        return sorted([p for p in all_paths if not os.path.isdir(p)])

    def save_and_exit(self):
        """Verifies paths, prompts for removal of invalid ones, and saves to dolphinrc."""
        # --- CHECK IF ANY CHANGES WERE MADE ---
        if self.copy_paths == getattr(self, 'initial_copy_paths', []) and \
            self.move_paths == getattr(self, 'initial_move_paths', []):
            self.close()
            return

        # --- VERIFY PATHS ---
        invalid_paths = self.get_invalid_paths()

        if invalid_paths:
            # Create a custom dialog to present invalid paths with checkboxes
            dialog = QDialog(self)
            dialog.setWindowTitle("Invalid Paths Detected")
            dialog.resize(500, 300)

            d_layout = QVBoxLayout(dialog)
            d_layout.addWidget(QLabel("<b>The following paths do not exist or are unreachable:</b>"))
            d_layout.addWidget(QLabel("Select any paths you wish to remove before saving:"))

            invalid_list = QListWidget()
            for path in invalid_paths:
                item = QListWidgetItem(path)
                item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable)
                item.setCheckState(Qt.CheckState.Checked)  # Checked by default for easy cleanup
                invalid_list.addItem(item)
            d_layout.addWidget(invalid_list)

            btn_box = QHBoxLayout()
            btn_remove_selected = QPushButton("Remove Selected & Save")
            btn_keep_all = QPushButton("Keep Invalid Paths & Save")

            btn_box.addWidget(btn_remove_selected)
            btn_box.addWidget(btn_keep_all)
            d_layout.addLayout(btn_box)

            # Dialog action handling (kept inside 'if invalid_paths:')
            def on_remove_selected():
                for i in range(invalid_list.count()):
                    item = invalid_list.item(i)
                    if item.checkState() == Qt.CheckState.Checked:
                        bad_path = item.text()
                        if bad_path in self.copy_paths: self.copy_paths.remove(bad_path)
                        if bad_path in self.move_paths: self.move_paths.remove(bad_path)
                dialog.accept()

            btn_remove_selected.clicked.connect(on_remove_selected)
            btn_keep_all.clicked.connect(dialog.reject)

            # Show dialog
            dialog.exec()

        # --- SAVE TO FILE & BACKUP ---
        if CONFIG_PATH.exists():
            backup_path = CONFIG_PATH.with_suffix('.bak')
            shutil.copy2(CONFIG_PATH, backup_path)

        copy_str = ",".join([p if p.endswith('/') else p + '/' for p in self.copy_paths])
        move_str = ",".join([p if p.endswith('/') else p + '/' for p in self.move_paths])

        content = []
        if CONFIG_PATH.exists():
            with open(CONFIG_PATH, 'r', encoding='utf-8') as f:
                content = f.readlines()

        def update_or_append_section(section_name, path_value):
            nonlocal content
            sec_idx = -1
            for idx, line in enumerate(content):
                if line.strip() == section_name:
                    sec_idx = idx
                    break

            if sec_idx != -1:
                paths_idx = -1
                for j in range(sec_idx + 1, len(content)):
                    if content[j].strip().startswith('['):
                        break
                    if content[j].strip().startswith('Paths'):
                        paths_idx = j
                        break
                if paths_idx != -1:
                    content[paths_idx] = f"Paths[$e]={path_value}\n"
                else:
                    content.insert(sec_idx + 1, f"Paths[$e]={path_value}\n")
            else:
                content.append(f"\n{section_name}\nPaths[$e]={path_value}\n")

        update_or_append_section("[kuick-copy]", copy_str)
        update_or_append_section("[kuick-move]", move_str)

        with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
            f.writelines(content)

        QMessageBox.information(
            self,
            "Saved",
            "Configuration updated successfully.<br>"
            "Backup saved to <i>dolphinrc.bak</i>.<br>"
            "<b>Dolphin should be restarted to reflect any changes made.</b>"
        )
        self.close()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = DolphinPathManager()
    window.show()
    sys.exit(app.exec())

NICE, feel like doing a small video to show it in action, and perhaps a tut for adding that in Dolphin?

It would be very nice if you take this from here, and maybe publish a Dolphin Service at KDE Store (note my recent update to the script above).
The script has only one Python dependence: PyQt6

I know nothing about coding nor posting to the KDE Store, hence my above request.

Oh, apparently I didn’t see the question mark at the end! :smiley:
Unfortunately, I can’t…
But I can just provide a brief step-by-step guide to use it via Dolphin context menu:

  1. Make sure you have “python3-pyqt6” installed, otherwise install it (e.g. sudo apt install python3-pyqt6)

  2. Copy the Python script to, for example, ~/.local/share/kio/servicemenus/bin/manage-quick-paths.py (creating any necessary sub-directories).

  3. Make sure it’s executable (chmod +x ~/.local/share/kio/servicemenus/bin/manage-quick-paths.py)

  4. Create a plain-text file, for example, ~/.local/share/kio/servicemenus/dolphin-manage-quick-paths_servicemenu.desktop with following contents -or add that “Desktop Action” to any appropraite “…_servicemenu.desktop” file:

    [Desktop Entry]
    Type=Service
    MimeType=all/allfiles;inode/directory;
    Icon=document-open
    X-KDE-Priority=TopLevel
    X-KDE-StartupNotify=false
    Actions=manage-quick-paths;
    
    [Desktop Action manage-quick-paths]
    Name=Manage Quick Paths...
    Icon=kt-queue-manager
    Exec=$HOME/.local/share/kio/servicemenus/bin/manage-quick-paths.py
    
  5. Make sure it’s also executable (chmod +x ~/.local/share/kio/servicemenus/dolphin-manage-quick-paths_servicemenu.desktop)