Launcher: wip image preview
This commit is contained in:
parent
ded133d164
commit
132dbce3a3
3 changed files with 55 additions and 53 deletions
|
|
@ -231,9 +231,6 @@ NPanel {
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
cacheBuffer: resultsList.height * 2
|
cacheBuffer: resultsList.height * 2
|
||||||
//boundsBehavior: Flickable.StopAtBounds
|
|
||||||
// maximumFlickVelocity: 2500
|
|
||||||
// flickDeceleration: 2000
|
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
cancelFlick()
|
cancelFlick()
|
||||||
if (currentIndex >= 0) {
|
if (currentIndex >= 0) {
|
||||||
|
|
@ -245,15 +242,26 @@ NPanel {
|
||||||
policy: ScrollBar.AsNeeded
|
policy: ScrollBar.AsNeeded
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the delegate in Launcher.qml's ListView with this enhanced version:
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
id: entry
|
id: entry
|
||||||
|
|
||||||
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
|
property bool isSelected: mouseArea.containsMouse || (index === selectedIndex)
|
||||||
property int badgeSize: Style.baseWidgetSize * 1.75 * scaling
|
property int badgeSize: Style.baseWidgetSize * 1.75 * scaling
|
||||||
|
|
||||||
|
// Property to reliably track the current item's ID.
|
||||||
|
// This changes whenever the delegate is recycled for a new item.
|
||||||
|
property var currentClipboardId: modelData.isImage ? modelData.clipboardId : ""
|
||||||
|
|
||||||
|
// When this delegate is assigned a new image item, trigger the decode.
|
||||||
|
onCurrentClipboardIdChanged: {
|
||||||
|
// Check if it's a valid ID and if the data isn't already cached.
|
||||||
|
if (currentClipboardId && !CliphistService.getImageData(currentClipboardId)) {
|
||||||
|
CliphistService.decodeToDataUrl(currentClipboardId, modelData.mime, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
width: resultsList.width - Style.marginS * scaling
|
width: resultsList.width - Style.marginS * scaling
|
||||||
height: badgeSize + Style.marginM * 2 *scaling
|
height: badgeSize + Style.marginM * 2 * scaling
|
||||||
radius: Style.radiusM * scaling
|
radius: Style.radiusM * scaling
|
||||||
color: entry.isSelected ? Color.mTertiary : Color.mSurface
|
color: entry.isSelected ? Color.mTertiary : Color.mSurface
|
||||||
|
|
||||||
|
|
@ -282,8 +290,19 @@ NPanel {
|
||||||
id: imagePreview
|
id: imagePreview
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 2 * scaling
|
anchors.margins: 2 * scaling
|
||||||
visible: modelData.isImage && modelData.imageSource
|
visible: modelData.isImage
|
||||||
source: modelData.imageSource || ""
|
|
||||||
|
// This property creates a dependency on the service's revision counter
|
||||||
|
readonly property int _rev: CliphistService.revision
|
||||||
|
|
||||||
|
// Fetches from the service's cache.
|
||||||
|
// The dependency on `_rev` ensures this binding is re-evaluated
|
||||||
|
// when the cache is updated by the service.
|
||||||
|
source: {
|
||||||
|
_rev
|
||||||
|
return CliphistService.getImageData(modelData.clipboardId) || ""
|
||||||
|
}
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
smooth: true
|
smooth: true
|
||||||
mipmap: true
|
mipmap: true
|
||||||
|
|
@ -307,7 +326,6 @@ NPanel {
|
||||||
// Error fallback
|
// Error fallback
|
||||||
onStatusChanged: {
|
onStatusChanged: {
|
||||||
if (status === Image.Error) {
|
if (status === Image.Error) {
|
||||||
// Fall back to icon
|
|
||||||
iconLoader.visible = true
|
iconLoader.visible = true
|
||||||
imagePreview.visible = false
|
imagePreview.visible = false
|
||||||
}
|
}
|
||||||
|
|
@ -319,7 +337,8 @@ NPanel {
|
||||||
id: iconLoader
|
id: iconLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Style.marginXS * scaling
|
anchors.margins: Style.marginXS * scaling
|
||||||
visible: !modelData.isImage || !modelData.imageSource || imagePreview.status === Image.Error
|
|
||||||
|
visible: !modelData.isImage || imagePreview.status === Image.Error
|
||||||
active: visible
|
active: visible
|
||||||
|
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
|
|
@ -391,23 +410,6 @@ NPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: text !== ""
|
visible: text !== ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Show text preview for text items if space allows
|
|
||||||
// NText {
|
|
||||||
// visible: !modelData.isImage && modelData.fullText && modelData.fullText.length > 100
|
|
||||||
// text: {
|
|
||||||
// if (!modelData.fullText) return ""
|
|
||||||
// const preview = modelData.fullText.substring(0, 150).replace(/\n/g, " ")
|
|
||||||
// return preview + (modelData.fullText.length > 150 ? "..." : "")
|
|
||||||
// }
|
|
||||||
// font.pointSize: Style.fontSizeXS * scaling
|
|
||||||
// color: entry.isSelected ? Color.mOnTertiary : Color.mOnSurfaceVariant
|
|
||||||
// opacity: 0.7
|
|
||||||
// elide: Text.ElideRight
|
|
||||||
// maximumLineCount: 2
|
|
||||||
// wrapMode: Text.WordWrap
|
|
||||||
// Layout.fillWidth: true
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,17 @@ QtObject {
|
||||||
// Plugin capabilities
|
// Plugin capabilities
|
||||||
property bool handleSearch: false // Don't handle regular search
|
property bool handleSearch: false // Don't handle regular search
|
||||||
|
|
||||||
|
// Connections {
|
||||||
|
// target: CliphistService
|
||||||
|
// // Use the function syntax for on<SignalName>
|
||||||
|
// function onListCompleted() {
|
||||||
|
// // Only refresh if the clipboard plugin is active
|
||||||
|
// if (launcher && launcher.activePlugin === root) {
|
||||||
|
// launcher.updateResults()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Initialize plugin
|
// Initialize plugin
|
||||||
function init() {
|
function init() {
|
||||||
Logger.log("ClipboardPlugin", "Initialized")
|
Logger.log("ClipboardPlugin", "Initialized")
|
||||||
|
|
@ -121,40 +132,22 @@ QtObject {
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: Format image clipboard entry with actual image data
|
// Helper: Format image clipboard entry
|
||||||
function formatImageEntry(item) {
|
function formatImageEntry(item) {
|
||||||
const meta = parseImageMeta(item.preview)
|
const meta = parseImageMeta(item.preview)
|
||||||
|
|
||||||
// Get the actual image data/path from the clipboard service
|
// The launcher's delegate will now be responsible for fetching the image data.
|
||||||
// This assumes CliphistService provides either a path or base64 data
|
// This function's role is to provide the necessary metadata for that request.
|
||||||
let imageData = null
|
|
||||||
|
|
||||||
// Try to get image data from the service
|
|
||||||
// Method 1: If the service provides a file path
|
|
||||||
if (item.imagePath) {
|
|
||||||
imageData = "file://" + item.imagePath
|
|
||||||
} // Method 2: If the service provides base64 data
|
|
||||||
else if (item.imageData) {
|
|
||||||
imageData = ClipHistService.getImageData(item.id)
|
|
||||||
|
|
||||||
// "data:" + (item.mime || "image/png") + ";base64," + item.imageData
|
|
||||||
} // Method 3: If we need to fetch it from the service
|
|
||||||
|
|
||||||
// else if (item.id) {
|
|
||||||
// // Some clipboard services might require fetching the image separately
|
|
||||||
// // This would depend on your CliphistService implementation
|
|
||||||
// imageData = CliphistService.getImageData ? CliphistService.getImageData(item.id) : null
|
|
||||||
// }
|
|
||||||
return {
|
return {
|
||||||
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
|
||||||
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
"description": meta ? `${meta.fmt} • ${meta.size}` : item.mime || "Image data",
|
||||||
"icon": "image",
|
"icon": "image",
|
||||||
"isImage": true,
|
"isImage": true,
|
||||||
"imageSource": imageData,
|
|
||||||
"imageWidth": meta ? meta.w : 0,
|
"imageWidth": meta ? meta.w : 0,
|
||||||
"imageHeight": meta ? meta.h : 0,
|
"imageHeight": meta ? meta.h : 0,
|
||||||
"clipboardId"// Add clipboard item ID for potential async loading
|
"clipboardId"// Provide the ID and mime type for the delegate to make an async request
|
||||||
: item.id
|
: item.id,
|
||||||
|
"mime": item.mime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -185,7 +178,7 @@ QtObject {
|
||||||
return {
|
return {
|
||||||
"name": title,
|
"name": title,
|
||||||
"description": description,
|
"description": description,
|
||||||
"icon": "description",
|
"icon": "text-x-generic",
|
||||||
"isImage": false
|
"isImage": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,8 @@ Singleton {
|
||||||
property string _b64CurrentMime: ""
|
property string _b64CurrentMime: ""
|
||||||
property string _b64CurrentId: ""
|
property string _b64CurrentId: ""
|
||||||
|
|
||||||
|
signal listCompleted()
|
||||||
|
|
||||||
// Check if cliphist is available
|
// Check if cliphist is available
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
checkCliphistAvailability()
|
checkCliphistAvailability()
|
||||||
|
|
@ -147,6 +149,9 @@ Singleton {
|
||||||
})
|
})
|
||||||
items = parsed
|
items = parsed
|
||||||
loading = false
|
loading = false
|
||||||
|
|
||||||
|
// Emit the signal for subscribers
|
||||||
|
root.listCompleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,10 +290,12 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageData(id) {
|
function getImageData(id) {
|
||||||
|
if (id === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return root.imageDataById[id]
|
return root.imageDataById[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function _startNextB64() {
|
function _startNextB64() {
|
||||||
if (root._b64Queue.length === 0 || !root.cliphistAvailable)
|
if (root._b64Queue.length === 0 || !root.cliphistAvailable)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue