mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-21 10:35:26 -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:
@@ -107,6 +107,7 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
property bool clipboardClickToPaste: false
|
||||
property bool clipboardEnterToPaste: false
|
||||
property bool clipboardRememberTypeFilter: false
|
||||
property string clipboardTypeFilter: "all"
|
||||
|
||||
@@ -600,6 +600,7 @@ var SPEC = {
|
||||
desktopWidgetGroups: { def: [] },
|
||||
|
||||
builtInPluginSettings: { def: {} },
|
||||
clipboardClickToPaste: { def: false },
|
||||
clipboardEnterToPaste: { def: false },
|
||||
clipboardRememberTypeFilter: { def: false },
|
||||
clipboardTypeFilter: { def: "all" },
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: clipboardContent
|
||||
@@ -19,8 +20,62 @@ Item {
|
||||
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
|
||||
|
||||
ClipboardContextMenu {
|
||||
id: contextMenu
|
||||
modal: clipboardContent.modal
|
||||
parentHandler: clipboardContent
|
||||
}
|
||||
|
||||
Column {
|
||||
id: headerColumn
|
||||
anchors.top: parent.top
|
||||
@@ -64,6 +119,12 @@ Item {
|
||||
onTextChanged: {
|
||||
modal.searchText = text;
|
||||
modal.updateFilteredModel();
|
||||
ClipboardService.selectedIndex = 0;
|
||||
ClipboardService.keyboardNavigationActive = true;
|
||||
Qt.callLater(function () {
|
||||
clipboardListView.positionViewAtBeginning();
|
||||
savedListView.positionViewAtBeginning();
|
||||
});
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: function (event) {
|
||||
@@ -202,10 +263,12 @@ Item {
|
||||
modal: clipboardContent.modal
|
||||
listView: clipboardListView
|
||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||
onPasteRequested: clipboardContent.modal.pasteEntry(modelData)
|
||||
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
|
||||
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
||||
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
||||
onEditRequested: clipboardContent.modal.editEntry(modelData)
|
||||
onContextMenuRequested: (mouseX, mouseY) => clipboardContent.showContextMenu(modelData, mouseX, mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,10 +339,12 @@ Item {
|
||||
modal: clipboardContent.modal
|
||||
listView: savedListView
|
||||
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
|
||||
onPasteRequested: clipboardContent.modal.pasteEntry(modelData)
|
||||
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
|
||||
onPinRequested: targetEntry => clipboardContent.modal.pinEntry(targetEntry)
|
||||
onUnpinRequested: targetEntry => clipboardContent.modal.unpinEntry(targetEntry)
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,12 @@ Rectangle {
|
||||
required property var listView
|
||||
|
||||
signal copyRequested
|
||||
signal pasteRequested
|
||||
signal deleteRequested
|
||||
signal pinRequested(var targetEntry)
|
||||
signal unpinRequested(var targetEntry)
|
||||
signal editRequested
|
||||
signal contextMenuRequested(real mouseX, real mouseY)
|
||||
|
||||
readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
|
||||
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
|
||||
@@ -25,11 +27,13 @@ Rectangle {
|
||||
readonly property bool hasPinnedDuplicate: pinnedDuplicateEntry !== null
|
||||
readonly property bool effectivePinned: entry.pinned || hasPinnedDuplicate
|
||||
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 showEditAction: visibleEntryActions.includes("edit")
|
||||
readonly property bool showDeleteAction: visibleEntryActions.includes("delete")
|
||||
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
|
||||
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 {
|
||||
iconName: "push_pin"
|
||||
iconSize: Theme.iconSize - 6
|
||||
@@ -199,10 +219,28 @@ Rectangle {
|
||||
anchors.bottom: parent.bottom
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressed: mouse => {
|
||||
const pos = mouseArea.mapToItem(root, mouse.x, mouse.y);
|
||||
rippleLayer.trigger(pos.x, pos.y);
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
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
|
||||
|
||||
property var clearConfirmDialog: null
|
||||
property var surfaceHost: null
|
||||
|
||||
property string activeTab: "recents"
|
||||
property bool showKeyboardHints: false
|
||||
@@ -32,6 +33,11 @@ FocusScope {
|
||||
property alias searchField: historyContent.searchField
|
||||
property alias editorView: editorView
|
||||
property alias keyboardController: keyboardController
|
||||
readonly property alias contextMenuActive: historyContent.contextMenuActive
|
||||
|
||||
function closeContextMenu() {
|
||||
historyContent.closeContextMenu();
|
||||
}
|
||||
|
||||
signal closeRequested
|
||||
signal instantCloseRequested
|
||||
@@ -42,7 +48,7 @@ FocusScope {
|
||||
return;
|
||||
}
|
||||
ClipboardService.selectedIndex = 0;
|
||||
ClipboardService.keyboardNavigationActive = false;
|
||||
ClipboardService.keyboardNavigationActive = true;
|
||||
}
|
||||
onPinnedCountChanged: {
|
||||
if (activeTab === "saved" && pinnedCount === 0) {
|
||||
@@ -54,7 +60,7 @@ FocusScope {
|
||||
onActiveFilterChanged: {
|
||||
ClipboardService.activeFilter = activeFilter;
|
||||
ClipboardService.selectedIndex = 0;
|
||||
ClipboardService.keyboardNavigationActive = false;
|
||||
ClipboardService.keyboardNavigationActive = true;
|
||||
ClipboardService.updateFilteredModel();
|
||||
if (SettingsData.clipboardRememberTypeFilter) {
|
||||
SettingsData.set("clipboardTypeFilter", activeFilter);
|
||||
@@ -92,6 +98,10 @@ FocusScope {
|
||||
ClipboardService.pasteEntry(entry, () => root.requestClose(true));
|
||||
}
|
||||
|
||||
function pasteEntry(entry) {
|
||||
ClipboardService.pasteEntry(entry, () => root.requestClose(true));
|
||||
}
|
||||
|
||||
function copyEntry(entry) {
|
||||
ClipboardService.copyEntry(entry, () => root.requestClose(false));
|
||||
}
|
||||
@@ -159,6 +169,7 @@ FocusScope {
|
||||
function resetState() {
|
||||
activeImageLoads = 0;
|
||||
mode = "history";
|
||||
historyContent.closeContextMenu();
|
||||
historyContent.closeFilterMenu();
|
||||
activeFilter = SettingsData.clipboardRememberTypeFilter ? SettingsData.clipboardTypeFilter : "all";
|
||||
ClipboardService.reset();
|
||||
|
||||
@@ -129,6 +129,7 @@ DankModal {
|
||||
|
||||
content: Component {
|
||||
ClipboardHistoryContent {
|
||||
surfaceHost: clipboardHistoryModal
|
||||
clearConfirmDialog: clearConfirmDialog
|
||||
onCloseRequested: clipboardHistoryModal.hide()
|
||||
onInstantCloseRequested: clipboardHistoryModal.instantHide()
|
||||
|
||||
@@ -140,6 +140,7 @@ DankPopout {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
surfaceHost: root
|
||||
clearConfirmDialog: clearConfirmDialog
|
||||
onCloseRequested: root.hide()
|
||||
onInstantCloseRequested: root.hide()
|
||||
|
||||
@@ -9,7 +9,7 @@ QtObject {
|
||||
|
||||
function reset() {
|
||||
ClipboardService.selectedIndex = 0;
|
||||
ClipboardService.keyboardNavigationActive = false;
|
||||
ClipboardService.keyboardNavigationActive = true;
|
||||
modal.showKeyboardHints = false;
|
||||
}
|
||||
|
||||
@@ -89,13 +89,16 @@ QtObject {
|
||||
return;
|
||||
}
|
||||
|
||||
if (modal.contextMenuActive) {
|
||||
if (event.key === Qt.Key_Escape)
|
||||
modal.closeContextMenu();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_Escape:
|
||||
if (ClipboardService.keyboardNavigationActive) {
|
||||
ClipboardService.keyboardNavigationActive = false;
|
||||
} else {
|
||||
modal.hide();
|
||||
}
|
||||
modal.hide();
|
||||
event.accepted = true;
|
||||
return;
|
||||
case Qt.Key_Down:
|
||||
|
||||
@@ -1883,10 +1883,10 @@ Item {
|
||||
}
|
||||
if (!selectedItem)
|
||||
return;
|
||||
executeItem(selectedItem);
|
||||
executeItem(selectedItem, true);
|
||||
}
|
||||
|
||||
function executeItem(item) {
|
||||
function executeItem(item, isKeyboard = false) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
@@ -1929,7 +1929,8 @@ Item {
|
||||
AppSearchService.executeBuiltInLauncherItem(item.data);
|
||||
break;
|
||||
case "clipboard":
|
||||
if (SettingsData.clipboardEnterToPaste) {
|
||||
var shouldPaste = isKeyboard ? SettingsData.clipboardEnterToPaste : SettingsData.clipboardClickToPaste;
|
||||
if (shouldPaste) {
|
||||
ClipboardService.pasteEntry(item.data, function () {
|
||||
root.itemExecuted();
|
||||
});
|
||||
|
||||
@@ -152,8 +152,8 @@ Item {
|
||||
}
|
||||
]
|
||||
|
||||
readonly property var entryActionKeys: ["pin", "edit", "delete"]
|
||||
readonly property var entryActionLabels: [I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
|
||||
readonly property var entryActionKeys: ["copy", "paste", "pin", "edit", "delete"]
|
||||
readonly property var entryActionLabels: [I18n.tr("Copy"), I18n.tr("Paste"), I18n.tr("Pin"), I18n.tr("Edit"), I18n.tr("Delete")]
|
||||
|
||||
function getMaxHistoryText(value) {
|
||||
if (value <= 0)
|
||||
@@ -454,6 +454,16 @@ Item {
|
||||
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 {
|
||||
tab: "clipboard"
|
||||
tags: ["clipboard", "enter", "paste", "behavior"]
|
||||
@@ -476,7 +486,7 @@ Item {
|
||||
|
||||
SettingsButtonGroupRow {
|
||||
tab: "clipboard"
|
||||
tags: ["clipboard", "actions", "buttons", "hide", "density", "pin", "edit", "delete"]
|
||||
tags: ["clipboard", "actions", "buttons", "hide", "density", "copy", "paste", "pin", "edit", "delete"]
|
||||
settingKey: "clipboardVisibleEntryActions"
|
||||
text: I18n.tr("Visible Entry Actions")
|
||||
description: I18n.tr("Choose which action buttons appear on clipboard entries")
|
||||
|
||||
@@ -202,7 +202,7 @@ StyledRect {
|
||||
id: rightButtonsRow
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS + root.rightAccessoryWidth
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
visible: showPasswordToggle || (showClearButton && text.length > 0)
|
||||
|
||||
@@ -7130,6 +7130,25 @@
|
||||
"icon": "settings",
|
||||
"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",
|
||||
"label": "Clipboard",
|
||||
|
||||
Reference in New Issue
Block a user