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:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -34,7 +34,7 @@ PanelWindow {
|
||||
demoActive = false
|
||||
}
|
||||
|
||||
PowerConfirmModal {
|
||||
ConfirmModal {
|
||||
id: powerModal
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ WlSessionLockSurface {
|
||||
|
||||
color: "transparent"
|
||||
|
||||
PowerConfirmModal {
|
||||
ConfirmModal {
|
||||
id: powerConfirmModal
|
||||
}
|
||||
|
||||
|
||||
71
shell.qml
71
shell.qml
@@ -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() {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user