1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Addidtional clipboard improvements

This commit is contained in:
purian23
2025-07-11 22:09:37 -04:00
parent 962c56c0ce
commit ccc3b1fa35

View File

@@ -19,6 +19,9 @@ PanelWindow {
color: "transparent" color: "transparent"
visible: isVisible visible: isVisible
// Confirmation dialog state
property bool showClearConfirmation: false
anchors { anchors {
top: true top: true
left: true left: true
@@ -77,6 +80,14 @@ PanelWindow {
clipboardHistory.isVisible = false clipboardHistory.isVisible = false
searchField.focus = false searchField.focus = false
searchField.text = "" searchField.text = ""
// Clean up temporary image files
cleanupTempFiles()
}
function cleanupTempFiles() {
cleanupProcess.command = ["sh", "-c", "rm -f /tmp/clipboard_preview_*.png"]
cleanupProcess.running = true
} }
function refreshClipboard() { function refreshClipboard() {
@@ -87,12 +98,16 @@ PanelWindow {
const entryId = entry.split('\t')[0] const entryId = entry.split('\t')[0]
copyProcess.command = ["sh", "-c", `cliphist decode ${entryId} | wl-copy`] copyProcess.command = ["sh", "-c", `cliphist decode ${entryId} | wl-copy`]
copyProcess.running = true copyProcess.running = true
// Simply hide the clipboard interface
console.log("ClipboardHistory: Entry copied, hiding interface")
hide() hide()
} }
function deleteEntry(entry) { function deleteEntry(entry) {
const entryId = entry.split('\t')[0] // Use the full entry line for deletion
deleteProcess.command = ["cliphist", "delete-query", entryId] console.log("Deleting entry:", entry)
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(/'/g, "'\\''")}' | cliphist delete`]
deleteProcess.running = true deleteProcess.running = true
} }
@@ -105,9 +120,20 @@ PanelWindow {
let content = entry.replace(/^\s*\d+\s+/, "") let content = entry.replace(/^\s*\d+\s+/, "")
// Handle different content types // Handle different content types
if (content.includes("image/")) { if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
const match = content.match(/(\d+)x(\d+)/) // Extract dimensions if available
return match ? `Image ${match[1]}×${match[2]}` : "Image" const dimensionMatch = content.match(/(\d+)x(\d+)/)
if (dimensionMatch) {
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`
}
// Extract file type if available
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i)
if (typeMatch) {
return `Image (${typeMatch[1].toUpperCase()})`
}
return "Image"
} }
// Truncate long text // Truncate long text
@@ -119,7 +145,13 @@ PanelWindow {
} }
function getEntryType(entry) { function getEntryType(entry) {
if (entry.includes("image/")) return "image" // Improved image detection
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)) {
return "image"
}
if (entry.length > 200) return "long_text" if (entry.length > 200) return "long_text"
return "text" return "text"
} }
@@ -148,7 +180,7 @@ PanelWindow {
// Main clipboard container // Main clipboard container
Rectangle { Rectangle {
id: clipboardContainer id: clipboardContainer
width: Math.min(600, parent.width - 200) width: Math.min(500, parent.width - 200)
height: Math.min(500, parent.height - 100) height: Math.min(500, parent.height - 100)
anchors.centerIn: parent anchors.centerIn: parent
@@ -209,7 +241,7 @@ PanelWindow {
width: 40 width: 40
height: 32 height: 32
radius: activeTheme.cornerRadius radius: activeTheme.cornerRadius
color: clearArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent" color: clearArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
visible: clipboardHistory.totalCount > 0 visible: clipboardHistory.totalCount > 0
Text { Text {
@@ -217,7 +249,7 @@ PanelWindow {
text: "delete_sweep" text: "delete_sweep"
font.family: activeTheme.iconFont font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize font.pixelSize: activeTheme.iconSize
color: clearArea.containsMouse ? activeTheme.error : activeTheme.surfaceText color: clearArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
} }
MouseArea { MouseArea {
@@ -225,7 +257,7 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: clearAll() onClicked: showClearConfirmation = true
} }
Behavior on color { Behavior on color {
@@ -238,14 +270,14 @@ PanelWindow {
width: 40 width: 40
height: 32 height: 32
radius: activeTheme.cornerRadius radius: activeTheme.cornerRadius
color: closeArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent" color: closeArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: "close" text: "close"
font.family: activeTheme.iconFont font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize font.pixelSize: activeTheme.iconSize
color: closeArea.containsMouse ? activeTheme.error : activeTheme.surfaceText color: closeArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
} }
MouseArea { MouseArea {
@@ -334,13 +366,46 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
clip: true clip: true
// Improve scrolling responsiveness
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.vertical.width: 12
ScrollBar.vertical.minimumSize: 0.1 // Minimum scrollbar handle size
// Enable faster scrolling
wheelEnabled: true
ListView { ListView {
id: clipboardList id: clipboardList
model: filteredClipboardModel model: filteredClipboardModel
spacing: activeTheme.spacingS spacing: activeTheme.spacingS
// Improve scrolling performance
cacheBuffer: 100
boundsBehavior: Flickable.StopAtBounds
// Make mouse wheel scrolling more responsive
property real wheelStepSize: 60
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
onWheel: (wheel) => {
var delta = wheel.angleDelta.y
var steps = delta / 120 // Standard wheel step
clipboardList.contentY -= steps * clipboardList.wheelStepSize
// Ensure we stay within bounds
if (clipboardList.contentY < 0) {
clipboardList.contentY = 0
} else if (clipboardList.contentY > clipboardList.contentHeight - clipboardList.height) {
clipboardList.contentY = Math.max(0, clipboardList.contentHeight - clipboardList.height)
}
}
}
delegate: Rectangle { delegate: Rectangle {
width: clipboardList.width width: clipboardList.width - 16 // Account for scrollbar space
height: Math.max(60, contentColumn.implicitHeight + activeTheme.spacingM * 2) height: Math.max(60, contentColumn.implicitHeight + activeTheme.spacingM * 2)
radius: activeTheme.cornerRadius radius: activeTheme.cornerRadius
color: entryArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) : color: entryArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) :
@@ -374,122 +439,144 @@ PanelWindow {
} }
} }
// Entry type icon // Entry content
Rectangle { Row {
width: 36
height: 36
radius: activeTheme.cornerRadius
color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - 80 // Adjusted for index number and delete button
spacing: activeTheme.spacingM
// Image preview - actual image display for images
Rectangle {
width: entryType === "image" ? 48 : 0
height: entryType === "image" ? 36 : 0
radius: activeTheme.cornerRadiusSmall
color: Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.1)
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.2)
border.width: 1
visible: entryType === "image"
clip: true
property string entryId: model.entry ? model.entry.split('\t')[0] : ""
property string tempImagePath: "/tmp/clipboard_preview_" + entryId + ".png"
// Actual image preview using cliphist decode
Image {
id: imagePreview
anchors.fill: parent
anchors.margins: 1
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: false
source: parent.entryType === "image" && parent.entryId ? "file://" + parent.tempImagePath : ""
Component.onCompleted: {
console.log("Image preview initializing for entry:", parent.entryId, "path:", parent.tempImagePath)
if (parent.entryType === "image" && parent.entryId) {
// Simple approach: use shell redirection to write to file
imageDecodeProcess.entryId = parent.entryId
imageDecodeProcess.tempPath = parent.tempImagePath
imageDecodeProcess.imagePreview = imagePreview
imageDecodeProcess.command = ["sh", "-c", `cliphist decode ${parent.entryId} > "${parent.tempImagePath}" 2>/dev/null`]
imageDecodeProcess.running = true
}
}
onStatusChanged: {
console.log("Image preview status changed:", status, "for path:", source)
if (status === Image.Error) {
console.warn("Failed to load image from:", source)
} else if (status === Image.Ready) {
console.log("Successfully loaded image:", source)
}
}
// Fallback icon when image fails to load or is loading
Text {
anchors.centerIn: parent
text: imagePreview.status === Image.Loading ? "hourglass_empty" :
imagePreview.status === Image.Error ? "broken_image" : "photo"
font.family: activeTheme.iconFont
font.pixelSize: imagePreview.status === Image.Loading ? 14 : 18
color: imagePreview.status === Image.Error ? activeTheme.error : activeTheme.primary
visible: imagePreview.status !== Image.Ready
SequentialAnimation on opacity {
running: imagePreview.status === Image.Loading
loops: Animation.Infinite
NumberAnimation { to: 0.3; duration: 500 }
NumberAnimation { to: 1.0; duration: 500 }
}
}
}
}
Column {
id: contentColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.width - (entryType === "image" ? 60 : 0)
spacing: activeTheme.spacingXS
Text {
text: {
switch (entryType) {
case "image": return "Image • " + entryPreview
case "long_text": return "Long Text"
default: return "Text"
}
}
font.pixelSize: activeTheme.fontSizeSmall
color: activeTheme.primary
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
}
Text {
text: entryPreview
font.pixelSize: activeTheme.fontSizeMedium
color: activeTheme.surfaceText
width: parent.width
wrapMode: Text.WordWrap
maximumLineCount: entryType === "long_text" ? 3 : 1
elide: Text.ElideRight
visible: true // Show preview for all entry types including images
}
}
}
// Actions - Single centered delete button
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: 32
height: 32
radius: activeTheme.cornerRadius
color: deleteArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
z: 100 // Ensure it's above other elements
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: { text: "delete"
switch (entryType) {
case "image": return "image"
case "long_text": return "subject"
default: return "content_paste"
}
}
font.family: activeTheme.iconFont font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 4 font.pixelSize: activeTheme.iconSize - 4
color: activeTheme.primary color: deleteArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
}
}
// Entry content
Column {
id: contentColumn
anchors.verticalCenter: parent.verticalCenter
width: parent.width - 140 // Adjusted for index number
spacing: activeTheme.spacingXS
Text {
text: {
switch (entryType) {
case "image": return "Image • " + entryPreview
case "long_text": return "Long Text"
default: return "Text"
}
}
font.pixelSize: activeTheme.fontSizeSmall
color: activeTheme.primary
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
} }
Text { MouseArea {
text: entryPreview id: deleteArea
font.pixelSize: activeTheme.fontSizeMedium anchors.fill: parent
color: activeTheme.surfaceText hoverEnabled: true
width: parent.width cursorShape: Qt.PointingHandCursor
wrapMode: Text.WordWrap z: 101 // Ensure click area is above everything
maximumLineCount: entryType === "long_text" ? 3 : 2 onClicked: (mouse) => {
elide: Text.ElideRight console.log("Delete clicked for entry:", model.entry)
visible: entryType !== "image" deleteEntry(model.entry)
} // Prevent the click from propagating to the entry area
} mouse.accepted = true
// Actions
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: activeTheme.spacingXS
// Copy button
Rectangle {
width: 28
height: 28
radius: activeTheme.cornerRadiusSmall
color: copyArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "content_copy"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 8
color: copyArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
}
MouseArea {
id: copyArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: copyEntry(model.entry)
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
} }
} }
// Delete button Behavior on color {
Rectangle { ColorAnimation { duration: activeTheme.shortDuration }
width: 28
height: 28
radius: activeTheme.cornerRadiusSmall
color: deleteArea.containsMouse ? Qt.rgba(activeTheme.error.r, activeTheme.error.g, activeTheme.error.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "delete"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSize - 8
color: deleteArea.containsMouse ? activeTheme.error : activeTheme.surfaceText
}
MouseArea {
id: deleteArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: deleteEntry(model.entry)
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
} }
} }
} }
@@ -497,6 +584,7 @@ PanelWindow {
MouseArea { MouseArea {
id: entryArea id: entryArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 40 // Leave space for delete button
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -539,9 +627,187 @@ PanelWindow {
} }
} }
} }
// Clear All Confirmation Dialog
Rectangle {
anchors.fill: parent
color: Qt.rgba(0, 0, 0, 0.4)
visible: showClearConfirmation
z: 999
MouseArea {
anchors.fill: parent
onClicked: clipboardHistory.showClearConfirmation = false
}
}
Rectangle {
anchors.centerIn: parent
width: 350
height: 200 // Increased height for better spacing
radius: activeTheme.cornerRadiusLarge
color: activeTheme.surfaceContainer
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.3)
border.width: 1
visible: showClearConfirmation
z: 1000
Column {
anchors.centerIn: parent
spacing: activeTheme.spacingL
width: parent.width - 40
// Add top padding
Item {
width: 1
height: activeTheme.spacingM
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "warning"
font.family: activeTheme.iconFont
font.pixelSize: activeTheme.iconSizeLarge
color: activeTheme.error
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "Clear All Clipboard History?"
font.pixelSize: activeTheme.fontSizeLarge
font.weight: Font.Bold
color: activeTheme.surfaceText
horizontalAlignment: Text.AlignHCenter
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: "This action cannot be undone. All clipboard entries will be permanently deleted."
font.pixelSize: activeTheme.fontSizeMedium
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.7)
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
width: parent.width
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: activeTheme.spacingM
// Cancel button
Rectangle {
width: 100
height: 40
radius: activeTheme.cornerRadius
color: cancelArea.containsMouse ?
Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) :
"transparent"
border.color: activeTheme.primary
border.width: 1
Text {
anchors.centerIn: parent
text: "Cancel"
font.pixelSize: activeTheme.fontSizeMedium
font.weight: Font.Medium
color: activeTheme.primary
}
MouseArea {
id: cancelArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: clipboardHistory.showClearConfirmation = false
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
// Clear button
Rectangle {
width: 100
height: 40
radius: activeTheme.cornerRadius
color: confirmArea.containsMouse ?
Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.8) :
activeTheme.primary
Text {
anchors.centerIn: parent
text: "Clear All"
font.pixelSize: activeTheme.fontSizeMedium
font.weight: Font.Medium
color: activeTheme.surface
}
MouseArea {
id: confirmArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
clipboardHistory.showClearConfirmation = false
clearAll()
}
}
Behavior on color {
ColorAnimation { duration: activeTheme.shortDuration }
}
}
}
// Add some bottom padding
Item {
width: 1
height: activeTheme.spacingM
}
}
}
} }
// Clipboard processes // Clipboard processes
Process {
id: cleanupProcess
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
console.log("Temporary image files cleaned up")
}
}
}
Process {
id: imageDecodeProcess
running: false
property string entryId: ""
property string tempPath: ""
property var imagePreview: null
onExited: (exitCode) => {
console.log("Image decode process exited with code:", exitCode, "for entry:", entryId)
if (exitCode === 0 && imagePreview && tempPath) {
console.log("Image decoded successfully to:", tempPath)
// Force the Image component to reload
Qt.callLater(function() {
imagePreview.source = ""
imagePreview.source = "file://" + tempPath
})
} else {
console.warn("Failed to decode clipboard image for entry:", entryId)
}
}
onStarted: {
console.log("Starting image decode for entry:", entryId, "to path:", tempPath)
}
}
Process { Process {
id: clipboardProcess id: clipboardProcess
command: ["cliphist", "list"] command: ["cliphist", "list"]