mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 00:12:50 -05:00
keyboard navigation on clipboard history
This commit is contained in:
@@ -8,7 +8,6 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
|
|
||||||
id: clipboardHistoryModal
|
id: clipboardHistoryModal
|
||||||
|
|
||||||
property int totalCount: 0
|
property int totalCount: 0
|
||||||
@@ -16,122 +15,110 @@ DankModal {
|
|||||||
property bool showClearConfirmation: false
|
property bool showClearConfirmation: false
|
||||||
property var clipboardEntries: []
|
property var clipboardEntries: []
|
||||||
property string searchText: ""
|
property string searchText: ""
|
||||||
|
property int selectedIndex: 0
|
||||||
|
property bool keyboardNavigationActive: false
|
||||||
|
property bool showKeyboardHints: false
|
||||||
|
property Component clipboardContent
|
||||||
|
|
||||||
function updateFilteredModel() {
|
function updateFilteredModel() {
|
||||||
filteredClipboardModel.clear()
|
filteredClipboardModel.clear();
|
||||||
for (var i = 0; i < clipboardModel.count; i++) {
|
for (var i = 0; i < clipboardModel.count; i++) {
|
||||||
const entry = clipboardModel.get(i).entry
|
const entry = clipboardModel.get(i).entry;
|
||||||
if (searchText.trim().length === 0) {
|
if (searchText.trim().length === 0) {
|
||||||
filteredClipboardModel.append({
|
filteredClipboardModel.append({
|
||||||
"entry": entry
|
"entry": entry
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
const content = getEntryPreview(entry).toLowerCase()
|
const content = getEntryPreview(entry).toLowerCase();
|
||||||
if (content.includes(searchText.toLowerCase()))
|
if (content.includes(searchText.toLowerCase()))
|
||||||
filteredClipboardModel.append({
|
filteredClipboardModel.append({
|
||||||
"entry": entry
|
"entry": entry
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clipboardHistoryModal.totalCount = filteredClipboardModel.count
|
clipboardHistoryModal.totalCount = filteredClipboardModel.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (visible)
|
if (visible)
|
||||||
hide()
|
hide();
|
||||||
else
|
else
|
||||||
show()
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
clipboardHistoryModal.visible = true
|
clipboardHistoryModal.visible = true;
|
||||||
initializeThumbnailSystem()
|
initializeThumbnailSystem();
|
||||||
refreshClipboard()
|
refreshClipboard();
|
||||||
|
keyboardController.reset();
|
||||||
Qt.callLater(function() {
|
|
||||||
if (contentLoader.item) {
|
|
||||||
const content = contentLoader.item
|
|
||||||
if (content.children && content.children.length > 1) {
|
|
||||||
const searchField = content.children[1]
|
|
||||||
if (searchField && searchField.forceActiveFocus) {
|
|
||||||
searchField.text = ""
|
|
||||||
searchField.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
clipboardHistoryModal.visible = false
|
clipboardHistoryModal.visible = false;
|
||||||
clipboardHistoryModal.searchText = ""
|
clipboardHistoryModal.searchText = "";
|
||||||
cleanupTempFiles()
|
keyboardController.reset();
|
||||||
|
cleanupTempFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeThumbnailSystem() {// No initialization needed - using direct image display
|
function initializeThumbnailSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupTempFiles() {
|
function cleanupTempFiles() {
|
||||||
Quickshell.execDetached(["sh", "-c", "rm -f /tmp/clipboard_*.png"])
|
Quickshell.execDetached(["sh", "-c", "rm -f /tmp/clipboard_*.png"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateThumbnails() {// No thumbnail generation needed - using direct image display
|
function generateThumbnails() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshClipboard() {
|
function refreshClipboard() {
|
||||||
clipboardProcess.running = true
|
clipboardProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyEntry(entry) {
|
function copyEntry(entry) {
|
||||||
const entryId = entry.split('\t')[0]
|
const entryId = entry.split('\t')[0];
|
||||||
Quickshell.execDetached(
|
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]);
|
||||||
["sh", "-c", `cliphist decode ${entryId} | wl-copy`])
|
ToastService.showInfo("Copied to clipboard");
|
||||||
|
clipboardHistoryModal.hide();
|
||||||
ToastService.showInfo("Copied to clipboard")
|
|
||||||
clipboardHistoryModal.hide()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteEntry(entry) {
|
function deleteEntry(entry) {
|
||||||
|
|
||||||
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(
|
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(
|
||||||
/'/g, "'\\''")}' | cliphist delete`]
|
/'/g, "'\\''")}' | cliphist delete`];
|
||||||
deleteProcess.running = true
|
deleteProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAll() {
|
function clearAll() {
|
||||||
clearProcess.running = true
|
clearProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntryPreview(entry) {
|
function getEntryPreview(entry) {
|
||||||
let content = entry.replace(/^\s*\d+\s+/, "")
|
let content = entry.replace(/^\s*\d+\s+/, "");
|
||||||
if (content.includes("image/") || content.includes("binary data")
|
if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
|
||||||
|| /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
|
const dimensionMatch = content.match(/(\d+)x(\d+)/);
|
||||||
const dimensionMatch = content.match(/(\d+)x(\d+)/)
|
|
||||||
if (dimensionMatch)
|
if (dimensionMatch)
|
||||||
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`
|
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`;
|
||||||
|
|
||||||
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i)
|
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i);
|
||||||
if (typeMatch)
|
if (typeMatch)
|
||||||
return `Image (${typeMatch[1].toUpperCase()})`
|
return `Image (${typeMatch[1].toUpperCase()})`;
|
||||||
|
|
||||||
return "Image"
|
return "Image";
|
||||||
}
|
}
|
||||||
if (content.length > 100)
|
if (content.length > 100)
|
||||||
return content.substring(0, 100) + "..."
|
return content.substring(0, 100) + "...";
|
||||||
|
|
||||||
return content
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntryType(entry) {
|
function getEntryType(entry) {
|
||||||
if (entry.includes("image/") || entry.includes("binary data")
|
if (entry.includes("image/") || entry.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry) || /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry))
|
||||||
|| /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry)
|
return "image";
|
||||||
|| /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry))
|
|
||||||
return "image"
|
|
||||||
|
|
||||||
if (entry.length > 200)
|
if (entry.length > 200)
|
||||||
return "long_text"
|
return "long_text";
|
||||||
|
|
||||||
return "text"
|
return "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: false
|
visible: false
|
||||||
@@ -144,7 +131,125 @@ DankModal {
|
|||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
enableShadow: true
|
enableShadow: true
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
hide()
|
hide();
|
||||||
|
}
|
||||||
|
modalFocusScope.Keys.onPressed: function(event) {
|
||||||
|
keyboardController.handleKey(event);
|
||||||
|
}
|
||||||
|
content: clipboardContent
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: keyboardController
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
selectedIndex = 0;
|
||||||
|
keyboardNavigationActive = false;
|
||||||
|
showKeyboardHints = false;
|
||||||
|
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||||
|
clipboardListView.keyboardActive = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
if (filteredClipboardModel.count === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, filteredClipboardModel.count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrevious() {
|
||||||
|
if (filteredClipboardModel.count === 0)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function copySelected() {
|
||||||
|
if (filteredClipboardModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredClipboardModel.count)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry;
|
||||||
|
copyEntry(selectedEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteSelected() {
|
||||||
|
if (filteredClipboardModel.count === 0 || selectedIndex < 0 || selectedIndex >= filteredClipboardModel.count)
|
||||||
|
return ;
|
||||||
|
|
||||||
|
var selectedEntry = filteredClipboardModel.get(selectedIndex).entry;
|
||||||
|
deleteEntry(selectedEntry);
|
||||||
|
if (selectedIndex >= filteredClipboardModel.count && filteredClipboardModel.count > 0)
|
||||||
|
selectedIndex = filteredClipboardModel.count - 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureVisible() {
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKey(event) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
if (keyboardNavigationActive) {
|
||||||
|
keyboardNavigationActive = false;
|
||||||
|
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||||
|
clipboardListView.keyboardActive = false;
|
||||||
|
|
||||||
|
event.accepted = true;
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
} else if (event.key === Qt.Key_Down) {
|
||||||
|
if (!keyboardNavigationActive) {
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = 0;
|
||||||
|
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||||
|
clipboardListView.keyboardActive = true;
|
||||||
|
|
||||||
|
event.accepted = true;
|
||||||
|
} else {
|
||||||
|
selectNext();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
} else if (event.key === Qt.Key_Up) {
|
||||||
|
if (!keyboardNavigationActive) {
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = 0;
|
||||||
|
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||||
|
clipboardListView.keyboardActive = true;
|
||||||
|
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (selectedIndex === 0) {
|
||||||
|
keyboardNavigationActive = false;
|
||||||
|
if (typeof clipboardListView !== 'undefined' && clipboardListView)
|
||||||
|
clipboardListView.keyboardActive = false;
|
||||||
|
|
||||||
|
event.accepted = true;
|
||||||
|
} else {
|
||||||
|
selectPrevious();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
} else if (keyboardNavigationActive) {
|
||||||
|
if (event.key === Qt.Key_C && (event.modifiers & Qt.ControlModifier)) {
|
||||||
|
copySelected();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key === Qt.Key_Delete) {
|
||||||
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
|
showClearConfirmation = true;
|
||||||
|
event.accepted = true;
|
||||||
|
} else {
|
||||||
|
deleteSelected();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (event.key === Qt.Key_F10) {
|
||||||
|
showKeyboardHints = !showKeyboardHints;
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankModal {
|
DankModal {
|
||||||
@@ -155,7 +260,7 @@ DankModal {
|
|||||||
height: 150
|
height: 150
|
||||||
keyboardFocus: "ondemand"
|
keyboardFocus: "ondemand"
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
showClearConfirmation = false
|
showClearConfirmation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
@@ -211,6 +316,7 @@ DankModal {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: showClearConfirmation = false
|
onClicked: showClearConfirmation = false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -234,16 +340,22 @@ DankModal {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
clearAll()
|
clearAll();
|
||||||
showClearConfirmation = false
|
showClearConfirmation = false;
|
||||||
hide()
|
hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
@@ -262,28 +374,30 @@ DankModal {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
clipboardModel.clear()
|
clipboardModel.clear();
|
||||||
const lines = text.trim().split('\n')
|
const lines = text.trim().split('\n');
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.trim().length > 0)
|
if (line.trim().length > 0)
|
||||||
clipboardModel.append({
|
clipboardModel.append({
|
||||||
"entry": line
|
"entry": line
|
||||||
})
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
updateFilteredModel()
|
updateFilteredModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: deleteProcess
|
id: deleteProcess
|
||||||
|
|
||||||
running: false
|
running: false
|
||||||
onExited: exitCode => {
|
onExited: (exitCode) => {
|
||||||
if (exitCode === 0)
|
if (exitCode === 0)
|
||||||
refreshClipboard()
|
refreshClipboard();
|
||||||
else
|
else
|
||||||
console.warn("Failed to delete clipboard entry")
|
console.warn("Failed to delete clipboard entry");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,51 +406,44 @@ DankModal {
|
|||||||
|
|
||||||
command: ["cliphist", "wipe"]
|
command: ["cliphist", "wipe"]
|
||||||
running: false
|
running: false
|
||||||
onExited: exitCode => {
|
onExited: (exitCode) => {
|
||||||
if (exitCode === 0) {
|
if (exitCode === 0) {
|
||||||
clipboardModel.clear()
|
clipboardModel.clear();
|
||||||
filteredClipboardModel.clear()
|
filteredClipboardModel.clear();
|
||||||
totalCount = 0
|
totalCount = 0;
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function open() {
|
function open() {
|
||||||
|
clipboardHistoryModal.show();
|
||||||
clipboardHistoryModal.show()
|
return "CLIPBOARD_OPEN_SUCCESS";
|
||||||
return "CLIPBOARD_OPEN_SUCCESS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
clipboardHistoryModal.hide();
|
||||||
clipboardHistoryModal.hide()
|
return "CLIPBOARD_CLOSE_SUCCESS";
|
||||||
return "CLIPBOARD_CLOSE_SUCCESS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
|
clipboardHistoryModal.toggle();
|
||||||
clipboardHistoryModal.toggle()
|
return "CLIPBOARD_TOGGLE_SUCCESS";
|
||||||
return "CLIPBOARD_TOGGLE_SUCCESS"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: "clipboard"
|
target: "clipboard"
|
||||||
}
|
}
|
||||||
|
|
||||||
property Component clipboardContent: Component {
|
clipboardContent: Component {
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingL
|
anchors.margins: Theme.spacingL
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
focus: true
|
focus: false
|
||||||
Keys.onPressed: function (event) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
hide()
|
|
||||||
event.accepted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -361,6 +468,7 @@ DankModal {
|
|||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -368,13 +476,23 @@ DankModal {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "help"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: showKeyboardHints ? Theme.primary : Theme.surfaceText
|
||||||
|
hoverColor: Theme.primaryHover
|
||||||
|
onClicked: {
|
||||||
|
showKeyboardHints = !showKeyboardHints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
iconName: "delete_sweep"
|
iconName: "delete_sweep"
|
||||||
iconSize: Theme.iconSize
|
iconSize: Theme.iconSize
|
||||||
iconColor: Theme.error
|
iconColor: Theme.error
|
||||||
hoverColor: Theme.errorHover
|
hoverColor: Theme.errorHover
|
||||||
onClicked: {
|
onClicked: {
|
||||||
showClearConfirmation = true
|
showClearConfirmation = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +503,9 @@ DankModal {
|
|||||||
hoverColor: Theme.errorHover
|
hoverColor: Theme.errorHover
|
||||||
onClicked: hide()
|
onClicked: hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTextField {
|
DankTextField {
|
||||||
@@ -395,9 +515,21 @@ DankModal {
|
|||||||
placeholderText: "Search clipboard history..."
|
placeholderText: "Search clipboard history..."
|
||||||
leftIconName: "search"
|
leftIconName: "search"
|
||||||
showClearButton: true
|
showClearButton: true
|
||||||
|
focus: true
|
||||||
|
ignoreLeftRightKeys: true
|
||||||
|
keyForwardTargets: [modalFocusScope]
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
clipboardHistoryModal.searchText = text
|
clipboardHistoryModal.searchText = text;
|
||||||
updateFilteredModel()
|
updateFilteredModel();
|
||||||
|
}
|
||||||
|
Keys.onEscapePressed: function(event) {
|
||||||
|
clipboardHistoryModal.hide();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
Qt.callLater(function() {
|
||||||
|
forceActiveFocus();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,17 +549,17 @@ DankModal {
|
|||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
clip: true
|
clip: true
|
||||||
model: filteredClipboardModel
|
model: filteredClipboardModel
|
||||||
|
currentIndex: selectedIndex
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
// Qt 6.9+ scrolling: flickDeceleration/maximumFlickVelocity only affect touch now
|
|
||||||
interactive: true
|
interactive: true
|
||||||
flickDeceleration: 1500 // Touch only in Qt 6.9+ // Lower = more momentum, longer scrolling
|
flickDeceleration: 1500
|
||||||
maximumFlickVelocity: 2000 // Touch only in Qt 6.9+ // Higher = faster maximum scroll speed
|
maximumFlickVelocity: 2000
|
||||||
boundsBehavior: Flickable.DragAndOvershootBounds
|
boundsBehavior: Flickable.DragAndOvershootBounds
|
||||||
boundsMovement: Flickable.FollowBoundsBehavior
|
boundsMovement: Flickable.FollowBoundsBehavior
|
||||||
pressDelay: 0
|
pressDelay: 0
|
||||||
flickableDirection: Flickable.VerticalFlick
|
flickableDirection: Flickable.VerticalFlick
|
||||||
|
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "No clipboard entries found"
|
text: "No clipboard entries found"
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -452,17 +584,26 @@ DankModal {
|
|||||||
property alias thumbnailImageSource: thumbnailImageSource
|
property alias thumbnailImageSource: thumbnailImageSource
|
||||||
|
|
||||||
width: clipboardListView.width
|
width: clipboardListView.width
|
||||||
height: Math.max(entryType === "image" ? 72 : 60,
|
height: Math.max(entryType === "image" ? 72 : 60, contentText.contentHeight + Theme.spacingL)
|
||||||
contentText.contentHeight + Theme.spacingL)
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground
|
color: {
|
||||||
border.color: Theme.outlineStrong
|
if (keyboardNavigationActive && index === selectedIndex)
|
||||||
border.width: 1
|
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
|
||||||
|
|
||||||
|
return mouseArea.containsMouse ? Theme.primaryHover : Theme.primaryBackground;
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (keyboardNavigationActive && index === selectedIndex)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
|
||||||
|
|
||||||
|
return Theme.outlineStrong;
|
||||||
|
}
|
||||||
|
border.width: keyboardNavigationActive && index === selectedIndex ? 1.5 : 1
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
anchors.rightMargin: Theme.spacingS // Reduced right margin
|
anchors.rightMargin: Theme.spacingS
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -479,11 +620,12 @@ DankModal {
|
|||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width - 68 // Account for index (24) + spacing (16) + delete button (32) - small margin
|
width: parent.width - 68
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -493,18 +635,20 @@ DankModal {
|
|||||||
|
|
||||||
CachingImage {
|
CachingImage {
|
||||||
id: thumbnailImageSource
|
id: thumbnailImageSource
|
||||||
anchors.fill: parent
|
|
||||||
property string entryId: model.entry.split('\t')[0]
|
property string entryId: model.entry.split('\t')[0]
|
||||||
source: entryType === "image"
|
|
||||||
&& imageLoader.imageData ? `data:image/png;base64,${imageLoader.imageData}` : ""
|
anchors.fill: parent
|
||||||
|
source: entryType === "image" && imageLoader.imageData ? `data:image/png;base64,${imageLoader.imageData}` : ""
|
||||||
fillMode: Image.PreserveAspectCrop
|
fillMode: Image.PreserveAspectCrop
|
||||||
smooth: true
|
smooth: true
|
||||||
cache: true
|
cache: true
|
||||||
visible: false // Hide the original image
|
visible: false
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: imageLoader
|
id: imageLoader
|
||||||
|
|
||||||
property string imageData: ""
|
property string imageData: ""
|
||||||
|
|
||||||
command: ["sh", "-c", `cliphist decode ${thumbnailImageSource.entryId} | base64 -w 0`]
|
command: ["sh", "-c", `cliphist decode ${thumbnailImageSource.entryId} | base64 -w 0`]
|
||||||
@@ -512,10 +656,12 @@ DankModal {
|
|||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
imageLoader.imageData = text.trim()
|
imageLoader.imageData = text.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiEffect {
|
MultiEffect {
|
||||||
@@ -524,8 +670,7 @@ DankModal {
|
|||||||
source: thumbnailImageSource
|
source: thumbnailImageSource
|
||||||
maskEnabled: true
|
maskEnabled: true
|
||||||
maskSource: clipboardCircularMask
|
maskSource: clipboardCircularMask
|
||||||
visible: entryType === "image"
|
visible: entryType === "image" && thumbnailImageSource.status === Image.Ready
|
||||||
&& thumbnailImageSource.status === Image.Ready
|
|
||||||
maskThresholdMin: 0.5
|
maskThresholdMin: 0.5
|
||||||
maskSpreadAtMin: 1
|
maskSpreadAtMin: 1
|
||||||
}
|
}
|
||||||
@@ -545,41 +690,41 @@ DankModal {
|
|||||||
color: "black"
|
color: "black"
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
visible: !(entryType === "image"
|
visible: !(entryType === "image" && thumbnailImageSource.status === Image.Ready)
|
||||||
&& thumbnailImageSource.status === Image.Ready)
|
|
||||||
name: {
|
name: {
|
||||||
if (entryType === "image")
|
if (entryType === "image")
|
||||||
return "image"
|
return "image";
|
||||||
|
|
||||||
if (entryType === "long_text")
|
if (entryType === "long_text")
|
||||||
return "subject"
|
return "subject";
|
||||||
|
|
||||||
return "content_copy"
|
return "content_copy";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: parent.width - (entryType === "image" ? 48 : Theme.iconSize)
|
width: parent.width - (entryType === "image" ? 48 : Theme.iconSize) - Theme.spacingM
|
||||||
- Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
switch (entryType) {
|
switch (entryType) {
|
||||||
case "image":
|
case "image":
|
||||||
return "Image • " + entryPreview
|
return "Image • " + entryPreview;
|
||||||
case "long_text":
|
case "long_text":
|
||||||
return "Long Text"
|
return "Long Text";
|
||||||
default:
|
default:
|
||||||
return "Text"
|
return "Text";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
@@ -600,8 +745,11 @@ DankModal {
|
|||||||
maximumLineCount: entryType === "long_text" ? 3 : 1
|
maximumLineCount: entryType === "long_text" ? 3 : 1
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
@@ -613,8 +761,7 @@ DankModal {
|
|||||||
iconColor: Theme.error
|
iconColor: Theme.error
|
||||||
hoverColor: Theme.errorHover
|
hoverColor: Theme.errorHover
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
deleteEntry(model.entry);
|
||||||
deleteEntry(model.entry)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,22 +769,79 @@ DankModal {
|
|||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.rightMargin: 40 // Enough space to avoid delete button (32 + 8 margin)
|
anchors.rightMargin: 40
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: copyEntry(model.entry)
|
onClicked: copyEntry(model.entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content: clipboardContent
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: showKeyboardHints ? 80 + Theme.spacingL : 0
|
||||||
|
|
||||||
|
Behavior on height {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Theme.spacingL
|
||||||
|
height: 80
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||||
|
border.color: Theme.primary
|
||||||
|
border.width: 2
|
||||||
|
opacity: showKeyboardHints ? 1 : 0
|
||||||
|
z: 100
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "↑/↓: Navigate • Ctrl+C: Copy • Del: Delete • F10: Help"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "Shift+Del: Clear All • Esc: Close"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -478,7 +478,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Question || event.key === Qt.Key_H) {
|
if (event.key === Qt.Key_F10) {
|
||||||
showKeyboardHints = !showKeyboardHints
|
showKeyboardHints = !showKeyboardHints
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,13 +15,6 @@ Rectangle {
|
|||||||
opacity: showHints ? 1 : 0
|
opacity: showHints ? 1 : 0
|
||||||
z: 100
|
z: 100
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: 2
|
spacing: 2
|
||||||
@@ -34,10 +27,20 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: "Del: Clear • Shift+Del: Clear All • 1-9: Actions • ?: Help • Esc: Close"
|
text: "Del: Clear • Shift+Del: Clear All • 1-9: Actions • F10: Help • Esc: Close"
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user