1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

fix clipboard history issues

This commit is contained in:
bbedward
2025-08-26 14:59:55 -04:00
parent b887940dce
commit 9e16368222
5 changed files with 240 additions and 166 deletions

View File

@@ -12,13 +12,14 @@ DankModal {
property int totalCount: 0
property var activeTheme: Theme
property bool showClearConfirmation: false
property var clipboardEntries: []
property string searchText: ""
property int selectedIndex: 0
property bool keyboardNavigationActive: false
property bool showKeyboardHints: false
property Component clipboardContent
property int activeImageLoads: 0
readonly property int maxConcurrentLoads: 3
function updateFilteredModel() {
filteredClipboardModel.clear()
@@ -56,6 +57,7 @@ DankModal {
function show() {
open()
clipboardHistoryModal.searchText = ""
clipboardHistoryModal.activeImageLoads = 0
initializeThumbnailSystem()
refreshClipboard()
@@ -72,6 +74,7 @@ DankModal {
function hide() {
close()
clipboardHistoryModal.searchText = ""
clipboardHistoryModal.activeImageLoads = 0
updateFilteredModel()
keyboardController.reset()
@@ -273,99 +276,20 @@ DankModal {
}
}
DankModal {
ConfirmModal {
id: clearConfirmDialog
visible: showClearConfirmation
width: 350
height: 150
onBackgroundClicked: {
showClearConfirmation = false
}
content: Component {
Item {
anchors.fill: parent
Column {
anchors.centerIn: parent
width: parent.width - Theme.spacingM * 2
spacing: Theme.spacingM
StyledText {
text: "Clear All History?"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: "This will permanently delete all clipboard history."
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
wrapMode: Text.WordWrap
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingM
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: cancelClearButton.containsMouse ? Theme.surfaceTextPressed : Theme.surfaceVariantAlpha
StyledText {
text: "Cancel"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: cancelClearButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: showClearConfirmation = false
}
}
Rectangle {
width: 100
height: 40
radius: Theme.cornerRadius
color: confirmClearButton.containsMouse ? Theme.errorPressed : Theme.error
StyledText {
text: "Clear All"
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium
anchors.centerIn: parent
}
MouseArea {
id: confirmClearButton
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
clearAll()
showClearConfirmation = false
hide()
}
}
}
}
confirmButtonText: "Clear All"
confirmButtonColor: Theme.error
onVisibleChanged: {
if (visible) {
clipboardHistoryModal.shouldHaveFocus = false
} else if (clipboardHistoryModal.shouldBeVisible) {
clipboardHistoryModal.shouldHaveFocus = true
clipboardHistoryModal.modalFocusScope.forceActiveFocus()
if (clipboardHistoryModal.contentLoader.item && clipboardHistoryModal.contentLoader.item.searchField) {
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus()
}
}
}
@@ -531,7 +455,15 @@ DankModal {
iconColor: Theme.error
hoverColor: Theme.errorHover
onClicked: {
showClearConfirmation = true
clearConfirmDialog.show(
"Clear All History?",
"This will permanently delete all clipboard history.",
function() {
clearAll()
hide()
},
function() {} // No action on cancel
)
}
}
@@ -702,32 +634,115 @@ DankModal {
height: entryType === "image" ? 48 : Theme.iconSize
anchors.verticalCenter: parent.verticalCenter
CachingImage {
Image {
id: thumbnailImageSource
property string entryId: model.entry.split(
'\t')[0]
property bool isVisible: false
property string cachedImageData: ""
property bool loadQueued: false
anchors.fill: parent
source: entryType === "image"
&& imageLoader.imageData ? `data:image/png;base64,${imageLoader.imageData}` : ""
source: ""
fillMode: Image.PreserveAspectCrop
smooth: true
cache: true
cache: false // Disable Qt's cache to control it ourselves
visible: false
asynchronous: true
sourceSize.width: 128
sourceSize.height: 128
onCachedImageDataChanged: {
if (cachedImageData) {
source = ""
source = `data:image/png;base64,${cachedImageData}`
}
}
function tryLoadImage() {
if (!loadQueued && entryType === "image" && !cachedImageData) {
loadQueued = true
if (clipboardHistoryModal.activeImageLoads < clipboardHistoryModal.maxConcurrentLoads) {
clipboardHistoryModal.activeImageLoads++
imageLoader.running = true
} else {
// Retry after delay
retryTimer.restart()
}
}
}
Timer {
id: retryTimer
interval: 50
onTriggered: {
if (thumbnailImageSource.loadQueued && !imageLoader.running) {
if (clipboardHistoryModal.activeImageLoads < clipboardHistoryModal.maxConcurrentLoads) {
clipboardHistoryModal.activeImageLoads++
imageLoader.running = true
} else {
retryTimer.restart()
}
}
}
}
Component.onCompleted: {
if (entryType !== "image") return
// Check if item is visible on screen initially
let itemY = index * (72 + clipboardListView.spacing)
let viewTop = clipboardListView.contentY
let viewBottom = viewTop + clipboardListView.height
isVisible = (itemY + 72 >= viewTop && itemY <= viewBottom)
if (isVisible) {
tryLoadImage()
}
}
Connections {
target: clipboardListView
function onContentYChanged() {
if (entryType !== "image") return
let itemY = index * (72 + clipboardListView.spacing)
let viewTop = clipboardListView.contentY - 100 // Preload slightly before visible
let viewBottom = viewTop + clipboardListView.height + 200
let nowVisible = (itemY + 72 >= viewTop && itemY <= viewBottom)
if (nowVisible && !thumbnailImageSource.isVisible) {
thumbnailImageSource.isVisible = true
thumbnailImageSource.tryLoadImage()
}
}
}
Process {
id: imageLoader
property string imageData: ""
running: false
command: ["sh", "-c", `cliphist decode ${thumbnailImageSource.entryId} | base64 -w 0`]
running: entryType === "image"
stdout: StdioCollector {
onStreamFinished: {
imageLoader.imageData = text.trim()
let imageData = text.trim()
if (imageData && imageData.length > 0) {
thumbnailImageSource.cachedImageData = imageData
}
}
}
onExited: exitCode => {
thumbnailImageSource.loadQueued = false
if (clipboardHistoryModal.activeImageLoads > 0) {
clipboardHistoryModal.activeImageLoads--
}
if (exitCode !== 0) {
console.warn("Failed to load clipboard image:", thumbnailImageSource.entryId)
}
}
}
@@ -741,6 +756,7 @@ DankModal {
maskSource: clipboardCircularMask
visible: entryType === "image"
&& thumbnailImageSource.status === Image.Ready
&& thumbnailImageSource.source != ""
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
@@ -764,8 +780,8 @@ DankModal {
DankIcon {
visible: !(entryType === "image"
&& thumbnailImageSource.status
=== Image.Ready)
&& thumbnailImageSource.status === Image.Ready
&& thumbnailImageSource.source != "")
name: {
if (entryType === "image")
return "image"

View File

@@ -9,17 +9,39 @@ import qs.Widgets
DankModal {
id: root
property string powerConfirmAction: ""
property string powerConfirmTitle: ""
property string powerConfirmMessage: ""
property string confirmTitle: ""
property string confirmMessage: ""
property string confirmButtonText: "Confirm"
property string cancelButtonText: "Cancel"
property color confirmButtonColor: Theme.error
property var onConfirm: function() {}
property var onCancel: function() {}
property int selectedButton: -1 // -1 = none, 0 = Cancel, 1 = Confirm
property bool keyboardNavigation: false
function show(action, title, message) {
powerConfirmAction = action
powerConfirmTitle = title
powerConfirmMessage = message
selectedButton = -1 // No button selected initially
function show(title, message, onConfirmCallback, onCancelCallback) {
confirmTitle = title || ""
confirmMessage = message || ""
confirmButtonText = "Confirm"
cancelButtonText = "Cancel"
confirmButtonColor = Theme.error
onConfirm = onConfirmCallback || function() {}
onCancel = onCancelCallback || function() {}
selectedButton = -1
keyboardNavigation = false
open()
}
function showWithOptions(options) {
confirmTitle = options.title || ""
confirmMessage = options.message || ""
confirmButtonText = options.confirmText || "Confirm"
cancelButtonText = options.cancelText || "Cancel"
confirmButtonColor = options.confirmColor || Theme.error
onConfirm = options.onConfirm || function() {}
onCancel = options.onCancel || function() {}
selectedButton = -1
keyboardNavigation = false
open()
}
@@ -27,41 +49,35 @@ DankModal {
function selectButton() {
if (selectedButton === 0) {
close()
if (onCancel) onCancel()
} else {
close()
executePowerAction(powerConfirmAction)
}
}
function executePowerAction(action) {
switch (action) {
case "logout":
CompositorService.logout()
break
case "suspend":
SessionService.suspend()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
if (onConfirm) onConfirm()
}
}
shouldBeVisible: false
allowStacking: true
width: 350
height: 160
enableShadow: false
enableShadow: true
shouldHaveFocus: true
onBackgroundClicked: {
close()
if (onCancel) onCancel()
}
onOpened: {
modalFocusScope.forceActiveFocus()
modalFocusScope.focus = true
shouldHaveFocus = true
}
modalFocusScope.Keys.onPressed: function(event) {
switch (event.key) {
case Qt.Key_Escape:
close()
if (onCancel) onCancel()
event.accepted = true
break
case Qt.Key_Left:
case Qt.Key_Up:
keyboardNavigation = true
@@ -81,7 +97,12 @@ DankModal {
break
case Qt.Key_Return:
case Qt.Key_Enter:
selectButton()
if (selectedButton !== -1) {
selectButton()
} else {
selectedButton = 1
selectButton()
}
event.accepted = true
break
}
@@ -97,25 +118,16 @@ DankModal {
spacing: Theme.spacingM
StyledText {
text: powerConfirmTitle
text: confirmTitle
font.pixelSize: Theme.fontSizeLarge
color: {
switch (powerConfirmAction) {
case "poweroff":
return Theme.error
case "reboot":
return Theme.warning
default:
return Theme.surfaceText
}
}
color: Theme.surfaceText
font.weight: Font.Medium
width: parent.width
horizontalAlignment: Text.AlignHCenter
}
StyledText {
text: powerConfirmMessage
text: confirmMessage
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
width: parent.width
@@ -147,7 +159,7 @@ DankModal {
border.width: (keyboardNavigation && selectedButton === 0) ? 1 : 0
StyledText {
text: "Cancel"
text: cancelButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
@@ -173,18 +185,7 @@ DankModal {
height: 40
radius: Theme.cornerRadius
color: {
let baseColor
switch (powerConfirmAction) {
case "poweroff":
baseColor = Theme.error
break
case "reboot":
baseColor = Theme.warning
break
default:
baseColor = Theme.primary
break
}
let baseColor = confirmButtonColor
if (keyboardNavigation && selectedButton === 1)
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1)
else if (confirmButton.containsMouse)
@@ -196,7 +197,7 @@ DankModal {
border.width: (keyboardNavigation && selectedButton === 1) ? 1 : 0
StyledText {
text: "Confirm"
text: confirmButtonText
font.pixelSize: Theme.fontSizeMedium
color: Theme.primaryText
font.weight: Font.Medium

View File

@@ -34,7 +34,7 @@ PanelWindow {
demoActive = false
}
PowerConfirmModal {
ConfirmModal {
id: powerModal
}

View File

@@ -20,7 +20,7 @@ WlSessionLockSurface {
color: "transparent"
PowerConfirmModal {
ConfirmModal {
id: powerConfirmModal
}

View File

@@ -113,8 +113,27 @@ ShellRoot {
onPowerActionRequested: (action, title, message) => {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item)
powerConfirmModalLoader.item.show(action, title, message)
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor =
action === "poweroff" ? Theme.error :
action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, function() {
switch (action) {
case "logout":
CompositorService.logout()
break
case "suspend":
SessionService.suspend()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}, function() {})
}
}
onLockRequested: {
lock.activate()
@@ -166,8 +185,27 @@ ShellRoot {
onPowerActionRequested: (action, title, message) => {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item)
powerConfirmModalLoader.item.show(action, title, message)
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor =
action === "poweroff" ? Theme.error :
action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, function() {
switch (action) {
case "logout":
CompositorService.logout()
break
case "suspend":
SessionService.suspend()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}, function() {})
}
}
}
@@ -178,7 +216,7 @@ ShellRoot {
active: false
PowerConfirmModal {
ConfirmModal {
id: powerConfirmModal
}
@@ -243,8 +281,27 @@ ShellRoot {
onPowerActionRequested: (action, title, message) => {
powerConfirmModalLoader.active = true
if (powerConfirmModalLoader.item)
powerConfirmModalLoader.item.show(action, title, message)
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor =
action === "poweroff" ? Theme.error :
action === "reboot" ? Theme.warning : Theme.primary
powerConfirmModalLoader.item.show(title, message, function() {
switch (action) {
case "logout":
CompositorService.logout()
break
case "suspend":
SessionService.suspend()
break
case "reboot":
SessionService.reboot()
break
case "poweroff":
SessionService.poweroff()
break
}
}, function() {})
}
}
}