1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-28 14:05:21 -04:00

Enhance/clipboard history interactions (#2668)

* feat(clipboard): add entry context menu

* feat(clipboard): add click to paste option

* feat(clipboard): add optional copy and paste action buttons

* fix(clipboard): default selection and esc behavior

* fix(clipboard): prevent clear and filter overlap

* Update to remove dead kb nav & add escape action on context menu

---------

Co-authored-by: purian23 <purian23@gmail.com>
This commit is contained in:
jbwfu
2026-06-21 15:50:51 +08:00
committed by GitHub
parent 465cf7355b
commit 59fd6db83e
13 changed files with 570 additions and 19 deletions
+1
View File
@@ -107,6 +107,7 @@ Singleton {
saveSettings(); saveSettings();
} }
property bool clipboardClickToPaste: false
property bool clipboardEnterToPaste: false property bool clipboardEnterToPaste: false
property bool clipboardRememberTypeFilter: false property bool clipboardRememberTypeFilter: false
property string clipboardTypeFilter: "all" property string clipboardTypeFilter: "all"
@@ -600,6 +600,7 @@ var SPEC = {
desktopWidgetGroups: { def: [] }, desktopWidgetGroups: { def: [] },
builtInPluginSettings: { def: {} }, builtInPluginSettings: { def: {} },
clipboardClickToPaste: { def: false },
clipboardEnterToPaste: { def: false }, clipboardEnterToPaste: { def: false },
clipboardRememberTypeFilter: { def: false }, clipboardRememberTypeFilter: { def: false },
clipboardTypeFilter: { def: "all" }, clipboardTypeFilter: { def: "all" },
@@ -2,6 +2,7 @@ import QtQuick
import Quickshell import Quickshell
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import qs.Services
Item { Item {
id: clipboardContent id: clipboardContent
@@ -19,8 +20,62 @@ Item {
filterMenuLoader.active = true; filterMenuLoader.active = true;
} }
function showContextMenu(entry, sceneX, sceneY) {
const localPos = mapFromItem(null, sceneX, sceneY);
contextMenu.show(localPos.x, localPos.y, entry);
}
function contextEntryAtScreen(screenX, screenY) {
const host = modal.surfaceHost ?? null;
const hostX = host?.alignedX;
const hostY = host?.renderedAlignedY ?? host?.alignedY;
if (!isNaN(hostX) && !isNaN(hostY))
return contextEntryAtLocal(screenX - hostX, screenY - hostY);
const screenRef = host?.effectiveScreen ?? host?.screen ?? modal.Window?.window?.screen ?? null;
const globalOrigin = mapToGlobal(0, 0);
const screenOriginX = screenRef?.x || 0;
const screenOriginY = screenRef?.y || 0;
return contextEntryAtLocal(screenOriginX + screenX - globalOrigin.x, screenOriginY + screenY - globalOrigin.y);
}
function contextEntryAtLocal(localX, localY) {
const listView = modal.activeTab === "saved" ? savedListView : clipboardListView;
const entries = modal.activeTab === "saved" ? modal.pinnedEntries : modal.unpinnedEntries;
if (!listView.visible || !entries)
return null;
const listPos = mapToItem(listView, localX, localY);
if (listPos.x < 0 || listPos.x > listView.width || listPos.y < 0 || listPos.y > listView.height)
return null;
const index = listView.indexAt(listPos.x + listView.contentX, listPos.y + listView.contentY);
if (index < 0 || index >= entries.length)
return null;
return {
entry: entries[index],
x: localX,
y: localY
};
}
function closeContextMenu() {
contextMenu.hide();
}
readonly property bool contextMenuActive: contextMenu.openState
anchors.fill: parent anchors.fill: parent
ClipboardContextMenu {
id: contextMenu
modal: clipboardContent.modal
parentHandler: clipboardContent
}
Column { Column {
id: headerColumn id: headerColumn
anchors.top: parent.top anchors.top: parent.top
@@ -64,6 +119,12 @@ Item {
onTextChanged: { onTextChanged: {
modal.searchText = text; modal.searchText = text;
modal.updateFilteredModel(); modal.updateFilteredModel();
ClipboardService.selectedIndex = 0;
ClipboardService.keyboardNavigationActive = true;
Qt.callLater(function () {
clipboardListView.positionViewAtBeginning();
savedListView.positionViewAtBeginning();
});
} }
Keys.onEscapePressed: function (event) { Keys.onEscapePressed: function (event) {
@@ -202,10 +263,12 @@ Item {
modal: clipboardContent.modal modal: clipboardContent.modal
listView: clipboardListView listView: clipboardListView
onCopyRequested: clipboardContent.modal.copyEntry(modelData) onCopyRequested: clipboardContent.modal.copyEntry(modelData)
onPasteRequested: clipboardContent.modal.pasteEntry(modelData)
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData) onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry) onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry) onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
onEditRequested: clipboardContent.modal.editEntry(modelData) onEditRequested: clipboardContent.modal.editEntry(modelData)
onContextMenuRequested: (mouseX, mouseY) => clipboardContent.showContextMenu(modelData, mouseX, mouseY)
} }
} }
@@ -276,10 +339,12 @@ Item {
modal: clipboardContent.modal modal: clipboardContent.modal
listView: savedListView listView: savedListView
onCopyRequested: clipboardContent.modal.copyEntry(modelData) onCopyRequested: clipboardContent.modal.copyEntry(modelData)
onPasteRequested: clipboardContent.modal.pasteEntry(modelData)
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData) onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry) onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry) onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
onEditRequested: clipboardContent.modal.editEntry(modelData) onEditRequested: clipboardContent.modal.editEntry(modelData)
onContextMenuRequested: (mouseX, mouseY) => clipboardContent.showContextMenu(modelData, mouseX, mouseY)
} }
} }
@@ -0,0 +1,400 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
visible: false
width: 0
height: 0
property var entry: null
property var modal: null
property var parentHandler: null
property real menuMargin: 8
property var targetScreen: null
property real anchorX: 0
property real anchorY: 0
property bool openState: false
property bool renderActive: false
readonly property bool blurActive: renderActive && openState && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
readonly property bool hasPinnedDuplicate: !!entry && !entry.pinned && ClipboardService.getPinnedEntryByHash(entry.hash) !== null
readonly property bool canEditEntry: !!entry && !(entry.isImage ?? false)
readonly property string pinText: entry?.pinned || hasPinnedDuplicate ? I18n.tr("Unpin") : I18n.tr("Pin")
readonly property string pinIcon: entry?.pinned || hasPinnedDuplicate ? "keep_off" : "push_pin"
readonly property var menuItems: {
const items = [
{
type: "item",
icon: "content_copy",
text: I18n.tr("Copy"),
action: copyEntry
},
{
type: "item",
icon: pinIcon,
text: pinText,
action: togglePin
}
];
if (canEditEntry) {
items.push({
type: "item",
icon: "edit",
text: I18n.tr("Edit"),
action: editEntry
});
}
items.push({
type: "item",
icon: "delete",
text: I18n.tr("Delete"),
action: deleteEntry
}, {
type: "separator"
}, {
type: "item",
icon: "content_paste",
text: I18n.tr("Paste"),
action: pasteEntry
});
return items;
}
readonly property real minMenuWidth: 160
readonly property real maxMenuWidth: Math.max(0, (targetScreen?.width ?? 500) - menuMargin * 2)
readonly property real maxMenuHeight: Math.max(0, (targetScreen?.height ?? 600) - menuMargin * 2)
readonly property string longestMenuText: {
let longest = "";
for (let i = 0; i < menuItems.length; i++) {
const text = menuItems[i].text || "";
if (text.length > longest.length)
longest = text;
}
return longest;
}
readonly property real naturalMenuWidth: Math.max(minMenuWidth, menuTextMetrics.width + Theme.iconSize + Theme.spacingS * 5)
readonly property real effectiveMenuWidth: Math.max(0, Math.min(maxMenuWidth, naturalMenuWidth))
readonly property real naturalMenuHeight: menuItemsHeight() + Theme.spacingS * 2
readonly property real effectiveMenuHeight: Math.min(maxMenuHeight, naturalMenuHeight)
readonly property bool menuScrolls: naturalMenuHeight > effectiveMenuHeight + 0.5
TextMetrics {
id: menuTextMetrics
text: root.longestMenuText
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
}
function menuItemsHeight() {
let h = 0;
for (let i = 0; i < menuItems.length; i++) {
h += menuItems[i].type === "separator" ? 5 : 32;
}
if (menuItems.length > 1)
h += menuItems.length - 1;
return h;
}
function show(x, y, targetEntry) {
if (!targetEntry)
return;
entry = targetEntry;
const host = modal?.surfaceHost ?? null;
const modalWindow = modal?.Window?.window ?? null;
const screenRef = host?.effectiveScreen ?? host?.screen ?? modalWindow?.screen ?? parentHandler?.Window?.window?.screen ?? null;
const screenX = screenRef?.x || 0;
const screenY = screenRef?.y || 0;
const hostX = host?.alignedX;
const hostY = host?.renderedAlignedY ?? host?.alignedY;
const globalPos = (!isNaN(hostX) && !isNaN(hostY)) ? ({
x: screenX + hostX + x,
y: screenY + hostY + y
}) : (parentHandler ? parentHandler.mapToGlobal(x, y) : ({
x: screenX + x,
y: screenY + y
}));
targetScreen = screenRef;
anchorX = globalPos.x - screenX + 4;
anchorY = globalPos.y - screenY + 4;
renderActive = true;
openState = true;
Qt.callLater(() => menuFlickable.contentY = 0);
}
function hide() {
if (!renderActive)
return;
openState = false;
}
function showFromWindowPoint(x, y) {
if (!parentHandler || typeof parentHandler.contextEntryAtScreen !== "function") {
hide();
return;
}
const hit = parentHandler.contextEntryAtScreen(x, y);
if (!hit || !hit.entry) {
hide();
return;
}
show(hit.x, hit.y, hit.entry);
}
function copyEntry() {
if (!entry)
return;
modal?.copyEntry(entry);
hide();
}
function togglePin() {
if (!entry)
return;
if (entry.pinned) {
modal?.unpinEntry(entry);
} else {
const duplicate = ClipboardService.getPinnedEntryByHash(entry.hash);
if (duplicate)
modal?.unpinEntry(duplicate);
else
modal?.pinEntry(entry);
}
hide();
}
function editEntry() {
if (!entry || !canEditEntry)
return;
modal?.editEntry(entry);
hide();
}
function deleteEntry() {
if (!entry)
return;
if (entry.pinned)
modal?.deletePinnedEntry(entry);
else
modal?.deleteEntry(entry);
hide();
}
function pasteEntry() {
if (!entry)
return;
modal?.pasteEntry(entry);
hide();
}
PanelWindow {
id: menuWindow
screen: root.targetScreen
visible: root.renderActive
color: "transparent"
WlrLayershell.namespace: "dms:clipboard-context-menu"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
WindowBlur {
targetWindow: menuWindow
blurX: root.blurActive ? menuContainer.x : 0
blurY: root.blurActive ? menuContainer.y : 0
blurWidth: root.blurActive ? menuContainer.width : 0
blurHeight: root.blurActive ? menuContainer.height : 0
blurRadius: Theme.cornerRadius
}
MouseArea {
anchors.fill: parent
z: -1
enabled: root.renderActive
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
root.showFromWindowPoint(mouse.x, mouse.y);
return;
}
root.hide();
}
}
Item {
anchors.fill: parent
Rectangle {
id: menuContainer
x: Math.max(root.menuMargin, Math.min(menuWindow.width - width - root.menuMargin, root.anchorX))
y: Math.max(root.menuMargin, Math.min(menuWindow.height - height - root.menuMargin, root.anchorY))
width: root.effectiveMenuWidth
height: root.effectiveMenuHeight
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: BlurService.enabled ? BlurService.borderColor : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: BlurService.enabled ? BlurService.borderWidth : 1
opacity: root.openState ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.emphasizedEasing
onRunningChanged: {
if (!running && !root.openState) {
root.renderActive = false;
}
}
}
}
Rectangle {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 2
anchors.rightMargin: -2
anchors.bottomMargin: -4
radius: parent.radius
color: Qt.rgba(0, 0, 0, 0.15)
z: -1
}
Flickable {
id: menuFlickable
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
contentWidth: width
contentHeight: menuColumn.implicitHeight
boundsBehavior: Flickable.StopAtBounds
interactive: root.menuScrolls
Column {
id: menuColumn
width: menuFlickable.width
spacing: 1
Repeater {
model: root.menuItems
Item {
id: menuItemDelegate
required property var modelData
width: menuColumn.width
height: modelData.type === "separator" ? 5 : 32
Rectangle {
visible: menuItemDelegate.modelData.type === "separator"
width: parent.width - Theme.spacingS * 2
height: parent.height
anchors.horizontalCenter: parent.horizontalCenter
color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
}
Rectangle {
visible: menuItemDelegate.modelData.type === "item"
width: parent.width
height: parent.height
radius: Theme.cornerRadius
color: itemMouseArea.containsMouse ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Item {
width: Theme.iconSize - 2
height: Theme.iconSize - 2
anchors.verticalCenter: parent.verticalCenter
DankIcon {
visible: (menuItemDelegate.modelData?.icon ?? "").length > 0
name: menuItemDelegate.modelData?.icon ?? ""
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.7
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: menuItemDelegate.modelData.text || ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: parent.width - (Theme.iconSize - 2) - Theme.spacingS
}
}
DankRipple {
id: menuItemRipple
rippleColor: Theme.surfaceText
cornerRadius: Theme.cornerRadius
}
MouseArea {
id: itemMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: mouse => menuItemRipple.trigger(mouse.x, mouse.y)
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
root.hide();
return;
}
const menuItem = menuItemDelegate.modelData;
if (menuItem.action)
menuItem.action();
}
}
}
}
}
}
}
}
}
}
}
+42 -4
View File
@@ -14,10 +14,12 @@ Rectangle {
required property var listView required property var listView
signal copyRequested signal copyRequested
signal pasteRequested
signal deleteRequested signal deleteRequested
signal pinRequested(var targetEntry) signal pinRequested(var targetEntry)
signal unpinRequested(var targetEntry) signal unpinRequested(var targetEntry)
signal editRequested signal editRequested
signal contextMenuRequested(real mouseX, real mouseY)
readonly property string entryType: modal ? modal.getEntryType(entry) : "text" readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : "" readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
@@ -25,11 +27,13 @@ Rectangle {
readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null
readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate
readonly property var visibleEntryActions: SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"] readonly property var visibleEntryActions: SettingsData.clipboardVisibleEntryActions || ["pin", "edit", "delete"]
readonly property bool showCopyAction: visibleEntryActions.includes("copy")
readonly property bool showPasteAction: visibleEntryActions.includes("paste")
readonly property bool showPinAction: visibleEntryActions.includes("pin") readonly property bool showPinAction: visibleEntryActions.includes("pin")
readonly property bool showEditAction: visibleEntryActions.includes("edit") readonly property bool showEditAction: visibleEntryActions.includes("edit")
readonly property bool showDeleteAction: visibleEntryActions.includes("delete") readonly property bool showDeleteAction: visibleEntryActions.includes("delete")
readonly property bool showPinnedIndicator: hasPinnedDuplicate && !showPinAction readonly property bool showPinnedIndicator: hasPinnedDuplicate && !showPinAction
readonly property bool showAnyAction: showPinAction || showEditAction || showDeleteAction || showPinnedIndicator readonly property bool showAnyAction: showCopyAction || showPasteAction || showPinAction || showEditAction || showDeleteAction || showPinnedIndicator
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
@@ -86,6 +90,22 @@ Rectangle {
} }
} }
DankActionButton {
iconName: "content_copy"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
visible: root.showCopyAction
onClicked: copyRequested()
}
DankActionButton {
iconName: "content_paste"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
visible: root.showPasteAction
onClicked: pasteRequested()
}
DankActionButton { DankActionButton {
iconName: "push_pin" iconName: "push_pin"
iconSize: Theme.iconSize - 6 iconSize: Theme.iconSize - 6
@@ -199,10 +219,28 @@ Rectangle {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton
onPressed: mouse => { onPressed: mouse => {
const pos = mouseArea.mapToItem(root, mouse.x, mouse.y); if (mouse.button === Qt.LeftButton) {
rippleLayer.trigger(pos.x, pos.y); const pos = mouseArea.mapToItem(root, mouse.x, mouse.y);
rippleLayer.trigger(pos.x, pos.y);
}
}
onClicked: {
if (SettingsData.clipboardClickToPaste) {
pasteRequested()
} else {
copyRequested()
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: mouse => {
const scenePos = mapToItem(null, mouse.x, mouse.y);
contextMenuRequested(scenePos.x, scenePos.y);
} }
onClicked: copyRequested()
} }
} }
@@ -8,6 +8,7 @@ FocusScope {
id: root id: root
property var clearConfirmDialog: null property var clearConfirmDialog: null
property var surfaceHost: null
property string activeTab: "recents" property string activeTab: "recents"
property bool showKeyboardHints: false property bool showKeyboardHints: false
@@ -32,6 +33,11 @@ FocusScope {
property alias searchField: historyContent.searchField property alias searchField: historyContent.searchField
property alias editorView: editorView property alias editorView: editorView
property alias keyboardController: keyboardController property alias keyboardController: keyboardController
readonly property alias contextMenuActive: historyContent.contextMenuActive
function closeContextMenu() {
historyContent.closeContextMenu();
}
signal closeRequested signal closeRequested
signal instantCloseRequested signal instantCloseRequested
@@ -42,7 +48,7 @@ FocusScope {
return; return;
} }
ClipboardService.selectedIndex = 0; ClipboardService.selectedIndex = 0;
ClipboardService.keyboardNavigationActive = false; ClipboardService.keyboardNavigationActive = true;
} }
onPinnedCountChanged: { onPinnedCountChanged: {
if (activeTab === "saved" && pinnedCount === 0) { if (activeTab === "saved" && pinnedCount === 0) {
@@ -54,7 +60,7 @@ FocusScope {
onActiveFilterChanged: { onActiveFilterChanged: {
ClipboardService.activeFilter = activeFilter; ClipboardService.activeFilter = activeFilter;
ClipboardService.selectedIndex = 0; ClipboardService.selectedIndex = 0;
ClipboardService.keyboardNavigationActive = false; ClipboardService.keyboardNavigationActive = true;
ClipboardService.updateFilteredModel(); ClipboardService.updateFilteredModel();
if (SettingsData.clipboardRememberTypeFilter) { if (SettingsData.clipboardRememberTypeFilter) {
SettingsData.set("clipboardTypeFilter", activeFilter); SettingsData.set("clipboardTypeFilter", activeFilter);
@@ -92,6 +98,10 @@ FocusScope {
ClipboardService.pasteEntry(entry, () => root.requestClose(true)); ClipboardService.pasteEntry(entry, () => root.requestClose(true));
} }
function pasteEntry(entry) {
ClipboardService.pasteEntry(entry, () => root.requestClose(true));
}
function copyEntry(entry) { function copyEntry(entry) {
ClipboardService.copyEntry(entry, () => root.requestClose(false)); ClipboardService.copyEntry(entry, () => root.requestClose(false));
} }
@@ -159,6 +169,7 @@ FocusScope {
function resetState() { function resetState() {
activeImageLoads = 0; activeImageLoads = 0;
mode = "history"; mode = "history";
historyContent.closeContextMenu();
historyContent.closeFilterMenu(); historyContent.closeFilterMenu();
activeFilter = SettingsData.clipboardRememberTypeFilter ? SettingsData.clipboardTypeFilter : "all"; activeFilter = SettingsData.clipboardRememberTypeFilter ? SettingsData.clipboardTypeFilter : "all";
ClipboardService.reset(); ClipboardService.reset();
@@ -129,6 +129,7 @@ DankModal {
content: Component { content: Component {
ClipboardHistoryContent { ClipboardHistoryContent {
surfaceHost: clipboardHistoryModal
clearConfirmDialog: clearConfirmDialog clearConfirmDialog: clearConfirmDialog
onCloseRequested: clipboardHistoryModal.hide() onCloseRequested: clipboardHistoryModal.hide()
onInstantCloseRequested: clipboardHistoryModal.instantHide() onInstantCloseRequested: clipboardHistoryModal.instantHide()
@@ -140,6 +140,7 @@ DankPopout {
LayoutMirroring.enabled: I18n.isRtl LayoutMirroring.enabled: I18n.isRtl
LayoutMirroring.childrenInherit: true LayoutMirroring.childrenInherit: true
surfaceHost: root
clearConfirmDialog: clearConfirmDialog clearConfirmDialog: clearConfirmDialog
onCloseRequested: root.hide() onCloseRequested: root.hide()
onInstantCloseRequested: root.hide() onInstantCloseRequested: root.hide()
@@ -9,7 +9,7 @@ QtObject {
function reset() { function reset() {
ClipboardService.selectedIndex = 0; ClipboardService.selectedIndex = 0;
ClipboardService.keyboardNavigationActive = false; ClipboardService.keyboardNavigationActive = true;
modal.showKeyboardHints = false; modal.showKeyboardHints = false;
} }
@@ -89,13 +89,16 @@ QtObject {
return; return;
} }
if (modal.contextMenuActive) {
if (event.key === Qt.Key_Escape)
modal.closeContextMenu();
event.accepted = true;
return;
}
switch (event.key) { switch (event.key) {
case Qt.Key_Escape: case Qt.Key_Escape:
if (ClipboardService.keyboardNavigationActive) { modal.hide();
ClipboardService.keyboardNavigationActive = false;
} else {
modal.hide();
}
event.accepted = true; event.accepted = true;
return; return;
case Qt.Key_Down: case Qt.Key_Down:
@@ -1883,10 +1883,10 @@ Item {
} }
if (!selectedItem) if (!selectedItem)
return; return;
executeItem(selectedItem); executeItem(selectedItem, true);
} }
function executeItem(item) { function executeItem(item, isKeyboard = false) {
if (!item) if (!item)
return; return;
@@ -1929,7 +1929,8 @@ Item {
AppSearchService.executeBuiltInLauncherItem(item.data); AppSearchService.executeBuiltInLauncherItem(item.data);
break; break;
case "clipboard": case "clipboard":
if (SettingsData.clipboardEnterToPaste) { var shouldPaste = isKeyboard ? SettingsData.clipboardEnterToPaste : SettingsData.clipboardClickToPaste;
if (shouldPaste) {
ClipboardService.pasteEntry(item.data, function () { ClipboardService.pasteEntry(item.data, function () {
root.itemExecuted(); root.itemExecuted();
}); });
+13 -3
View File
@@ -152,8 +152,8 @@ Item {
} }
] ]
readonly property var entryActionKeys: ["pin", "edit", "delete"] readonly property var entryActionKeys: ["copy", "paste", "pin", "edit", "delete"]
readonly property var entryActionLabels: [I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")] readonly property var entryActionLabels: [I18n.tr("Copy"), I18n.tr("Paste"), I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
function getMaxHistoryText(value) { function getMaxHistoryText(value) {
if (value <= 0) if (value <= 0)
@@ -454,6 +454,16 @@ Item {
onToggled: checked => root.saveConfig("clearAtStartup", checked) onToggled: checked => root.saveConfig("clearAtStartup", checked)
} }
SettingsToggleRow {
tab: "clipboard"
tags: ["clipboard", "click", "paste", "behavior"]
settingKey: "clipboardClickToPaste"
text: I18n.tr("Click to Paste")
description: I18n.tr("Click an entry to paste directly instead of copying", "Clipboard behavior setting description")
checked: SettingsData.clipboardClickToPaste
onToggled: checked => SettingsData.set("clipboardClickToPaste", checked)
}
SettingsToggleRow { SettingsToggleRow {
tab: "clipboard" tab: "clipboard"
tags: ["clipboard", "enter", "paste", "behavior"] tags: ["clipboard", "enter", "paste", "behavior"]
@@ -476,7 +486,7 @@ Item {
SettingsButtonGroupRow { SettingsButtonGroupRow {
tab: "clipboard" tab: "clipboard"
tags: ["clipboard", "actions", "buttons", "hide", "density", "pin", "edit", "delete"] tags: ["clipboard", "actions", "buttons", "hide", "density", "copy", "paste", "pin", "edit", "delete"]
settingKey: "clipboardVisibleEntryActions" settingKey: "clipboardVisibleEntryActions"
text: I18n.tr("Visible Entry Actions") text: I18n.tr("Visible Entry Actions")
description: I18n.tr("Choose which action buttons appear on clipboard entries") description: I18n.tr("Choose which action buttons appear on clipboard entries")
+1 -1
View File
@@ -202,7 +202,7 @@ StyledRect {
id: rightButtonsRow id: rightButtonsRow
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS + root.rightAccessoryWidth
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
visible: showPasswordToggle || (showClearButton && text.length > 0) visible: showPasswordToggle || (showClearButton && text.length > 0)
@@ -7130,6 +7130,25 @@
"icon": "settings", "icon": "settings",
"description": "Clear all history when server starts" "description": "Clear all history when server starts"
}, },
{
"section": "clipboardClickToPaste",
"label": "Click to Paste",
"tabIndex": 23,
"category": "System",
"keywords": [
"behavior",
"click",
"clipboard",
"copying",
"directly",
"entry",
"linux",
"os",
"paste",
"system"
],
"description": "Click an entry to paste directly instead of copying"
},
{ {
"section": "_tab_23", "section": "_tab_23",
"label": "Clipboard", "label": "Clipboard",