mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-28 23:42:51 -05:00
initial structure refactor
This commit is contained in:
959
Modules/ClipboardHistory.qml
Normal file
959
Modules/ClipboardHistory.qml
Normal file
@@ -0,0 +1,959 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
id: clipboardHistory
|
||||
|
||||
property bool isVisible: false
|
||||
property int totalCount: 0
|
||||
// Use the global Theme singleton
|
||||
property var activeTheme: Theme
|
||||
// Confirmation dialog state
|
||||
property bool showClearConfirmation: false
|
||||
// Clipboard entries model
|
||||
property var clipboardEntries: []
|
||||
|
||||
function updateFilteredModel() {
|
||||
filteredClipboardModel.clear();
|
||||
for (let i = 0; i < clipboardModel.count; i++) {
|
||||
const entry = clipboardModel.get(i).entry;
|
||||
if (searchField.text.trim().length === 0) {
|
||||
filteredClipboardModel.append({
|
||||
"entry": entry
|
||||
});
|
||||
} else {
|
||||
const content = getEntryPreview(entry).toLowerCase();
|
||||
if (content.includes(searchField.text.toLowerCase()))
|
||||
filteredClipboardModel.append({
|
||||
"entry": entry
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
// Update total count
|
||||
clipboardHistory.totalCount = filteredClipboardModel.count;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (isVisible)
|
||||
hide();
|
||||
else
|
||||
show();
|
||||
}
|
||||
|
||||
function show() {
|
||||
clipboardHistory.isVisible = true;
|
||||
searchField.focus = true;
|
||||
refreshClipboard();
|
||||
console.log("ClipboardHistory: Opening and refreshing");
|
||||
}
|
||||
|
||||
function hide() {
|
||||
clipboardHistory.isVisible = false;
|
||||
searchField.focus = false;
|
||||
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() {
|
||||
clipboardProcess.running = true;
|
||||
}
|
||||
|
||||
function copyEntry(entry) {
|
||||
const entryId = entry.split('\t')[0];
|
||||
copyProcess.command = ["sh", "-c", `cliphist decode ${entryId} | wl-copy`];
|
||||
copyProcess.running = true;
|
||||
// Simply hide the clipboard interface
|
||||
console.log("ClipboardHistory: Entry copied, hiding interface");
|
||||
hide();
|
||||
}
|
||||
|
||||
function deleteEntry(entry) {
|
||||
// Use the full entry line for deletion
|
||||
console.log("Deleting entry:", entry);
|
||||
deleteProcess.command = ["sh", "-c", `echo '${entry.replace(/'/g, "'\\''")}' | cliphist delete`];
|
||||
deleteProcess.running = true;
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
clearProcess.running = true;
|
||||
}
|
||||
|
||||
function getEntryPreview(entry) {
|
||||
// Remove cliphist ID prefix and clean up content
|
||||
let content = entry.replace(/^\s*\d+\s+/, "");
|
||||
// Handle different content types
|
||||
if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
|
||||
// Extract dimensions if available
|
||||
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
|
||||
if (content.length > 100)
|
||||
return content.substring(0, 100) + "...";
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
function getEntryType(entry) {
|
||||
// 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";
|
||||
|
||||
return "text";
|
||||
}
|
||||
|
||||
// Window properties
|
||||
color: "transparent"
|
||||
visible: isVisible
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: isVisible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: clipboardModel
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: filteredClipboardModel
|
||||
}
|
||||
|
||||
// Background overlay
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5)
|
||||
opacity: clipboardHistory.isVisible ? 1 : 0
|
||||
visible: clipboardHistory.isVisible
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: clipboardHistory.isVisible
|
||||
onClicked: clipboardHistory.hide()
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: activeTheme.mediumDuration
|
||||
easing.type: activeTheme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main clipboard container
|
||||
Rectangle {
|
||||
id: clipboardContainer
|
||||
|
||||
width: Math.min(500, parent.width - 200)
|
||||
height: Math.min(500, parent.height - 100)
|
||||
anchors.centerIn: parent
|
||||
color: activeTheme.popupBackground()
|
||||
radius: activeTheme.cornerRadiusXLarge
|
||||
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
opacity: clipboardHistory.isVisible ? 1 : 0
|
||||
scale: clipboardHistory.isVisible ? 1 : 0.9
|
||||
|
||||
// Header section
|
||||
Column {
|
||||
id: headerSection
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: activeTheme.spacingXL
|
||||
spacing: activeTheme.spacingL
|
||||
|
||||
// Title and actions
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Text {
|
||||
id: titleText
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Clipboard History" + (clipboardHistory.totalCount > 0 ? ` (${clipboardHistory.totalCount})` : "")
|
||||
font.pixelSize: activeTheme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: activeTheme.surfaceText
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: activeTheme.spacingS
|
||||
|
||||
// Clear all button
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
width: 40
|
||||
height: 32
|
||||
radius: activeTheme.cornerRadius
|
||||
color: clearArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
|
||||
visible: clipboardHistory.totalCount > 0
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "delete_sweep"
|
||||
font.family: activeTheme.iconFont
|
||||
font.pixelSize: activeTheme.iconSize
|
||||
color: clearArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: showClearConfirmation = true
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: activeTheme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Close button
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 32
|
||||
radius: activeTheme.cornerRadius
|
||||
color: closeArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "close"
|
||||
font.family: activeTheme.iconFont
|
||||
font.pixelSize: activeTheme.iconSize
|
||||
color: closeArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: clipboardHistory.hide()
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: activeTheme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Search field
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: activeTheme.cornerRadiusLarge
|
||||
color: Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, activeTheme.getContentBackgroundAlpha() * 0.4)
|
||||
border.color: searchField.focus ? activeTheme.primary : Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.08)
|
||||
border.width: searchField.focus ? 2 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: activeTheme.spacingL
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: activeTheme.spacingM
|
||||
|
||||
Text {
|
||||
text: "search"
|
||||
font.family: activeTheme.iconFont
|
||||
font.pixelSize: activeTheme.iconSize
|
||||
color: searchField.focus ? activeTheme.primary : Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: searchField
|
||||
|
||||
width: parent.parent.width - 80
|
||||
height: parent.parent.height
|
||||
font.pixelSize: activeTheme.fontSizeLarge
|
||||
color: activeTheme.surfaceText
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
selectByMouse: true
|
||||
onTextChanged: updateFilteredModel()
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_Escape)
|
||||
clipboardHistory.hide();
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.IBeamCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
|
||||
// Placeholder text
|
||||
Text {
|
||||
text: "Search clipboard entries..."
|
||||
font: searchField.font
|
||||
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: searchField.text.length === 0 && !searchField.focus
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: activeTheme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clipboard entries
|
||||
Rectangle {
|
||||
anchors.top: headerSection.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: activeTheme.spacingXL
|
||||
anchors.topMargin: activeTheme.spacingL
|
||||
color: "transparent"
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
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 {
|
||||
id: clipboardList
|
||||
|
||||
// Make mouse wheel scrolling more responsive
|
||||
property real wheelStepSize: 60
|
||||
|
||||
model: filteredClipboardModel
|
||||
spacing: activeTheme.spacingS
|
||||
// Improve scrolling performance
|
||||
cacheBuffer: 100
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
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 {
|
||||
property string entryType: getEntryType(model.entry)
|
||||
property string entryPreview: getEntryPreview(model.entry)
|
||||
property int entryIndex: index + 1
|
||||
|
||||
width: clipboardList.width - 16 // Account for scrollbar space
|
||||
height: Math.max(60, contentColumn.implicitHeight + activeTheme.spacingM * 2)
|
||||
radius: activeTheme.cornerRadius
|
||||
color: entryArea.containsMouse ? Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.08) : Qt.rgba(activeTheme.surfaceVariant.r, activeTheme.surfaceVariant.g, activeTheme.surfaceVariant.b, 0.05)
|
||||
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.1)
|
||||
border.width: 1
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.margins: activeTheme.spacingM
|
||||
spacing: activeTheme.spacingL
|
||||
|
||||
// Index number
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 12
|
||||
color: Qt.rgba(activeTheme.primary.r, activeTheme.primary.g, activeTheme.primary.b, 0.2)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: entryIndex.toString()
|
||||
font.pixelSize: activeTheme.fontSizeSmall
|
||||
font.weight: Font.Bold
|
||||
color: activeTheme.primary
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Entry content
|
||||
Row {
|
||||
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 {
|
||||
property string entryId: model.entry ? model.entry.split('\t')[0] : ""
|
||||
property string tempImagePath: "/tmp/clipboard_preview_" + entryId + ".png"
|
||||
|
||||
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
|
||||
|
||||
// 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: {
|
||||
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: {
|
||||
if (status === Image.Error)
|
||||
console.warn("Failed to load clipboard image from:", 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
|
||||
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 {
|
||||
anchors.centerIn: parent
|
||||
text: "delete"
|
||||
font.family: activeTheme.iconFont
|
||||
font.pixelSize: activeTheme.iconSize - 4
|
||||
color: deleteArea.containsMouse ? activeTheme.primary : activeTheme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deleteArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 101 // Ensure click area is above everything
|
||||
onClicked: (mouse) => {
|
||||
console.log("Delete clicked for entry:", model.entry);
|
||||
deleteEntry(model.entry);
|
||||
// Prevent the click from propagating to the entry area
|
||||
mouse.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: activeTheme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: entryArea
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 40 // Leave space for delete button
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: copyEntry(model.entry)
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: activeTheme.shortDuration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Empty state
|
||||
Column {
|
||||
anchors.centerIn: parent
|
||||
spacing: activeTheme.spacingL
|
||||
visible: clipboardHistory.totalCount === 0
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "content_paste_off"
|
||||
font.family: activeTheme.iconFont
|
||||
font.pixelSize: activeTheme.iconSizeLarge + 16
|
||||
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.3)
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "No clipboard history"
|
||||
font.pixelSize: activeTheme.fontSizeLarge
|
||||
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.6)
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: "Copy something to see it here"
|
||||
font.pixelSize: activeTheme.fontSizeMedium
|
||||
color: Qt.rgba(activeTheme.surfaceText.r, activeTheme.surfaceText.g, activeTheme.surfaceText.b, 0.4)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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.popupBackground()
|
||||
border.color: Qt.rgba(activeTheme.outline.r, activeTheme.outline.g, activeTheme.outline.b, 0.08)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: activeTheme.mediumDuration
|
||||
easing.type: activeTheme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: activeTheme.mediumDuration
|
||||
easing.type: activeTheme.emphasizedEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Clipboard processes
|
||||
Process {
|
||||
id: cleanupProcess
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0)
|
||||
console.log("Temporary image files cleaned up");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
// Force the Image component to reload
|
||||
|
||||
id: imageDecodeProcess
|
||||
|
||||
property string entryId: ""
|
||||
property string tempPath: ""
|
||||
property var imagePreview: null
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0 && imagePreview && tempPath)
|
||||
Qt.callLater(function() {
|
||||
imagePreview.source = "";
|
||||
imagePreview.source = "file://" + tempPath;
|
||||
});
|
||||
|
||||
}
|
||||
onStarted: {
|
||||
console.log("Starting image decode for entry:", entryId, "to path:", tempPath);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: clipboardProcess
|
||||
|
||||
command: ["cliphist", "list"]
|
||||
running: false
|
||||
onStarted: {
|
||||
clipboardHistory.clipboardEntries = [];
|
||||
clipboardModel.clear();
|
||||
console.log("ClipboardHistory: Starting cliphist process...");
|
||||
}
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0)
|
||||
updateFilteredModel();
|
||||
else
|
||||
console.warn("ClipboardHistory: Failed to load clipboard history");
|
||||
}
|
||||
// Handle keyboard shortcuts
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.key === Qt.Key_Escape)
|
||||
clipboardHistory.hide();
|
||||
|
||||
}
|
||||
Component.onCompleted: {
|
||||
focus = true;
|
||||
}
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: (line) => {
|
||||
if (line.trim()) {
|
||||
clipboardHistory.clipboardEntries.push(line);
|
||||
clipboardModel.append({
|
||||
"entry": line
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Process {
|
||||
id: copyProcess
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0)
|
||||
console.warn("ClipboardHistory: Failed to copy entry");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: deleteProcess
|
||||
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0)
|
||||
refreshClipboard();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: clearProcess
|
||||
|
||||
command: ["cliphist", "wipe"]
|
||||
running: false
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
clipboardHistory.clipboardEntries = [];
|
||||
clipboardModel.clear();
|
||||
updateFilteredModel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
console.log("ClipboardHistory: IPC open() called");
|
||||
clipboardHistory.show();
|
||||
return "CLIPBOARD_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function close() {
|
||||
console.log("ClipboardHistory: IPC close() called");
|
||||
clipboardHistory.hide();
|
||||
return "CLIPBOARD_CLOSE_SUCCESS";
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
console.log("ClipboardHistory: IPC toggle() called");
|
||||
clipboardHistory.toggle();
|
||||
return "CLIPBOARD_TOGGLE_SUCCESS";
|
||||
}
|
||||
|
||||
target: "clipboard"
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user