1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00
Files
DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml
bbedward e6c3ae9397 cups: add comprehensive CUPs setting page
- Add printers
- Delete printers
- Use polkit APIs as fallback on auth errors
- Fix ref system to conditionally subscribe to cups when wanted
2025-11-29 17:35:21 -05:00

1361 lines
74 KiB
QML

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Modals.Common
import qs.Services
import qs.Widgets
Item {
id: printerTab
property bool showAddPrinter: false
property string newPrinterName: ""
property string selectedDeviceUri: ""
property var selectedDevice: null
property string selectedPpd: ""
property string newPrinterLocation: ""
property string newPrinterInfo: ""
property var suggestedPPDs: []
function resetAddPrinterForm() {
newPrinterName = "";
selectedDeviceUri = "";
selectedDevice = null;
selectedPpd = "";
newPrinterLocation = "";
newPrinterInfo = "";
suggestedPPDs = [];
}
function selectDevice(device) {
if (!device)
return;
selectedDevice = device;
selectedDeviceUri = device.uri;
if (!newPrinterName) {
newPrinterName = CupsService.suggestPrinterName(device);
}
if (device.location && !newPrinterLocation) {
newPrinterLocation = CupsService.decodeUri(device.location);
}
suggestedPPDs = CupsService.getMatchingPPDs(device);
if (suggestedPPDs.length > 0 && !selectedPpd) {
selectedPpd = suggestedPPDs[0].name;
}
}
Component.onCompleted: {
CupsService.getClasses();
}
ConfirmModal {
id: deletePrinterConfirm
}
ConfirmModal {
id: purgeJobsConfirm
}
ConfirmModal {
id: deleteClassConfirm
}
DankFlickable {
anchors.fill: parent
clip: true
contentHeight: mainColumn.height + Theme.spacingXL
contentWidth: width
Column {
id: mainColumn
width: Math.min(600, parent.width - Theme.spacingL * 2)
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingL
StyledRect {
width: parent.width
height: overviewSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
Column {
id: overviewSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "print"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("CUPS Print Server")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Grid {
columns: 2
columnSpacing: Theme.spacingL
rowSpacing: Theme.spacingS
width: parent.width
StyledText {
text: I18n.tr("Status")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
}
Row {
spacing: Theme.spacingS
Rectangle {
width: 8
height: 8
radius: 4
anchors.verticalCenter: parent.verticalCenter
color: CupsService.cupsAvailable ? Theme.success : Theme.error
}
StyledText {
text: CupsService.cupsAvailable ? I18n.tr("Available") : I18n.tr("Unavailable")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
}
StyledText {
text: I18n.tr("Printers")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
}
StyledText {
text: CupsService.printerNames.length.toString()
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
StyledText {
text: I18n.tr("Total Jobs")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
}
StyledText {
text: CupsService.getTotalJobsNum().toString()
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
}
}
}
StyledRect {
width: parent.width
height: addPrinterSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
visible: CupsService.cupsAvailable
Column {
id: addPrinterSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "add_circle"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - addPrinterToggleBtn.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Add Printer")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Configure a new printer")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
Rectangle {
id: addPrinterToggleBtn
width: 28
height: 28
radius: 14
color: addPrinterToggleArea.containsMouse ? Theme.surfacePressed : "transparent"
anchors.verticalCenter: parent.verticalCenter
DankIcon {
anchors.centerIn: parent
name: printerTab.showAddPrinter ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceText
}
MouseArea {
id: addPrinterToggleArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
printerTab.showAddPrinter = !printerTab.showAddPrinter;
if (printerTab.showAddPrinter) {
if (CupsService.devices.length === 0) {
CupsService.getDevices();
CupsService.getPPDs();
}
} else {
printerTab.resetAddPrinterForm();
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingM
visible: printerTab.showAddPrinter
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Column {
width: parent.width
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Device")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: 80
anchors.verticalCenter: parent.verticalCenter
}
DankDropdown {
id: deviceDropdown
dropdownWidth: parent.width - 80 - scanDevicesBtn.width - Theme.spacingS * 2
popupWidth: parent.width - 80 - scanDevicesBtn.width - Theme.spacingS * 2
currentValue: {
if (CupsService.loadingDevices)
return I18n.tr("Scanning...");
if (printerTab.selectedDevice)
return CupsService.getDeviceDisplayName(printerTab.selectedDevice);
return I18n.tr("Select device...");
}
options: {
const filtered = CupsService.filteredDevices;
if (filtered.length === 0)
return [I18n.tr("No devices found")];
return filtered.map(d => CupsService.getDeviceDisplayName(d));
}
onValueChanged: value => {
if (value === I18n.tr("No devices found") || value === I18n.tr("Scanning..."))
return;
const filtered = CupsService.filteredDevices;
const device = filtered.find(d => CupsService.getDeviceDisplayName(d) === value);
if (device) {
printerTab.selectDevice(device);
}
}
}
DankActionButton {
id: scanDevicesBtn
iconName: "refresh"
buttonSize: 32
anchors.verticalCenter: parent.verticalCenter
enabled: !CupsService.loadingDevices
onClicked: CupsService.getDevices()
RotationAnimation on rotation {
running: CupsService.loadingDevices
loops: Animation.Infinite
from: 0
to: 360
duration: 1000
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
visible: printerTab.selectedDevice !== null
Item {
width: 80
height: 1
}
StyledText {
text: CupsService.getDeviceSubtitle(printerTab.selectedDevice)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: parent.width - 80 - Theme.spacingS
elide: Text.ElideRight
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Driver")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: 80
anchors.verticalCenter: parent.verticalCenter
}
DankDropdown {
id: ppdDropdown
dropdownWidth: parent.width - 80 - refreshPpdsBtn.width - Theme.spacingS * 2
popupWidth: parent.width - 80 - refreshPpdsBtn.width - Theme.spacingS * 2
currentValue: {
if (CupsService.loadingPPDs)
return I18n.tr("Loading...");
if (printerTab.selectedPpd) {
const ppd = CupsService.ppds.find(p => p.name === printerTab.selectedPpd);
if (ppd) {
const isSuggested = printerTab.suggestedPPDs.some(s => s.name === ppd.name);
return (isSuggested ? "★ " : "") + (ppd.makeModel || ppd.name);
}
return printerTab.selectedPpd;
}
return printerTab.suggestedPPDs.length > 0 ? I18n.tr("Recommended available") : I18n.tr("Select driver...");
}
options: {
if (CupsService.ppds.length === 0)
return [I18n.tr("No drivers found")];
const suggested = printerTab.suggestedPPDs.map(p => "★ " + (p.makeModel || p.name));
const others = CupsService.ppds.filter(p => !printerTab.suggestedPPDs.some(s => s.name === p.name)).map(p => p.makeModel || p.name);
return suggested.concat(others);
}
onValueChanged: value => {
if (value === I18n.tr("No drivers found") || value === I18n.tr("Loading..."))
return;
const cleanValue = value.replace(/^★ /, "");
const ppd = CupsService.ppds.find(p => (p.makeModel || p.name) === cleanValue);
if (ppd) {
printerTab.selectedPpd = ppd.name;
}
}
}
DankActionButton {
id: refreshPpdsBtn
iconName: "refresh"
buttonSize: 32
anchors.verticalCenter: parent.verticalCenter
enabled: !CupsService.loadingPPDs
onClicked: CupsService.getPPDs()
RotationAnimation on rotation {
running: CupsService.loadingPPDs
loops: Animation.Infinite
from: 0
to: 360
duration: 1000
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Name")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: 80
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
width: parent.width - 80 - Theme.spacingS
height: 40
placeholderText: I18n.tr("Printer name (no spaces)")
text: printerTab.newPrinterName
onTextEdited: printerTab.newPrinterName = text.replace(/\s/g, "-")
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Location")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: 80
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
width: parent.width - 80 - Theme.spacingS
height: 40
placeholderText: I18n.tr("Optional location")
text: printerTab.newPrinterLocation
onTextEdited: printerTab.newPrinterLocation = text
}
}
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Description")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
width: 80
anchors.verticalCenter: parent.verticalCenter
}
DankTextField {
width: parent.width - 80 - Theme.spacingS
height: 40
placeholderText: I18n.tr("Optional description")
text: printerTab.newPrinterInfo
onTextEdited: printerTab.newPrinterInfo = text
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
layoutDirection: Qt.RightToLeft
DankButton {
text: CupsService.creatingPrinter ? I18n.tr("Creating...") : I18n.tr("Create Printer")
iconName: CupsService.creatingPrinter ? "sync" : "add"
buttonHeight: 36
enabled: printerTab.newPrinterName.length > 0 && printerTab.selectedDeviceUri.length > 0 && printerTab.selectedPpd.length > 0 && !CupsService.creatingPrinter
onClicked: {
CupsService.createPrinter(printerTab.newPrinterName, printerTab.selectedDeviceUri, printerTab.selectedPpd, {
location: printerTab.newPrinterLocation,
information: printerTab.newPrinterInfo
});
printerTab.resetAddPrinterForm();
printerTab.showAddPrinter = false;
}
}
}
}
}
}
StyledRect {
width: parent.width
height: printersSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
visible: CupsService.cupsAvailable
Column {
id: printersSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "print"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - refreshBtn.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Printers")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: {
const count = CupsService.printerNames.length;
if (count === 0)
return I18n.tr("No printers configured");
return I18n.tr("%1 printer(s)").arg(count);
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
DankActionButton {
id: refreshBtn
iconName: "refresh"
buttonSize: 32
anchors.verticalCenter: parent.verticalCenter
onClicked: CupsService.getState()
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Item {
width: parent.width
height: 80
visible: CupsService.printerNames.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: "print_disabled"
size: 32
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No printers found")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
Column {
width: parent.width
spacing: 4
visible: CupsService.printerNames.length > 0
Repeater {
model: CupsService.printerNames
delegate: Rectangle {
id: printerDelegate
required property string modelData
required property int index
readonly property var printerData: CupsService.getPrinterData(modelData)
readonly property bool isExpanded: CupsService.expandedPrinter === modelData || hasJobs
readonly property bool hasJobs: (printerData?.jobs?.length ?? 0) > 0
readonly property bool isIdle: printerData?.state === "idle"
readonly property bool isStopped: printerData?.state === "stopped"
width: parent.width
height: isExpanded ? 56 + expandedContent.height : 56
radius: Theme.cornerRadius
color: printerMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
border.width: CupsService.selectedPrinter === modelData ? 2 : 0
border.color: Theme.primary
clip: true
Behavior on height {
NumberAnimation {
duration: 150
easing.type: Easing.OutQuad
}
}
Column {
anchors.fill: parent
spacing: 0
Item {
width: parent.width
height: 56
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
anchors.right: printerActions.left
anchors.rightMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: isStopped ? "print_disabled" : "print"
size: 20
color: isStopped ? Theme.error : (isIdle ? Theme.primary : Theme.warning)
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
width: parent.width - 20 - Theme.spacingS
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: CupsService.selectedPrinter === modelData ? Font.Medium : Font.Normal
elide: Text.ElideRight
width: parent.width
}
Row {
spacing: Theme.spacingXS
StyledText {
text: CupsService.getPrinterStateTranslation(printerData?.state || "")
font.pixelSize: Theme.fontSizeSmall
color: {
switch (printerData?.state) {
case "idle":
return Theme.primary;
case "stopped":
return Theme.error;
case "processing":
return Theme.warning;
default:
return Theme.surfaceVariantText;
}
}
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: (printerData?.jobs?.length ?? 0) > 0
}
StyledText {
text: I18n.tr("%1 job(s)").arg(printerData?.jobs?.length ?? 0)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: (printerData?.jobs?.length ?? 0) > 0
}
}
}
}
Row {
id: printerActions
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Rectangle {
width: 28
height: 28
radius: 14
color: expandBtn.containsMouse ? Theme.surfacePressed : "transparent"
DankIcon {
anchors.centerIn: parent
name: isExpanded ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceText
}
MouseArea {
id: expandBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
CupsService.expandedPrinter = isExpanded ? "" : modelData;
}
}
}
Rectangle {
width: 28
height: 28
radius: 14
color: deleteBtn.containsMouse ? Theme.errorHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
deletePrinterConfirm.showWithOptions({
title: I18n.tr("Delete Printer"),
message: I18n.tr("Delete \"%1\"?").arg(modelData),
confirmText: I18n.tr("Delete"),
confirmColor: Theme.error,
onConfirm: () => CupsService.deletePrinter(modelData)
});
}
}
}
}
MouseArea {
id: printerMouseArea
anchors.fill: parent
anchors.rightMargin: printerActions.width + Theme.spacingM
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
CupsService.setSelectedPrinter(modelData);
}
}
}
Column {
id: expandedContent
width: parent.width
visible: isExpanded
Rectangle {
width: parent.width - Theme.spacingM * 2
height: 1
x: Theme.spacingM
color: Theme.outlineLight
}
Item {
width: parent.width
height: detailsColumn.implicitHeight + Theme.spacingM * 2
Column {
id: detailsColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Flow {
width: parent.width
spacing: Theme.spacingXS
Repeater {
model: {
const fields = [];
const p = printerData;
if (!p)
return fields;
fields.push({
label: I18n.tr("State"),
value: CupsService.getPrinterStateTranslation(p.state)
});
if (p.stateReason && p.stateReason !== "none")
fields.push({
label: I18n.tr("Reason"),
value: CupsService.getPrinterStateReasonTranslation(p.stateReason)
});
if (p.makeModel)
fields.push({
label: I18n.tr("Model"),
value: p.makeModel
});
if (p.location)
fields.push({
label: I18n.tr("Location"),
value: p.location
});
fields.push({
label: I18n.tr("Accepting"),
value: p.accepting ? I18n.tr("Yes") : I18n.tr("No")
});
return fields;
}
delegate: Rectangle {
required property var modelData
required property int index
width: fieldContent.width + Theme.spacingM * 2
height: 32
radius: Theme.cornerRadius - 2
color: Theme.surfaceContainerHigh
border.width: 1
border.color: Theme.outlineLight
Row {
id: fieldContent
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: modelData.label + ":"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.value
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
Row {
width: parent.width
spacing: Theme.spacingS
Rectangle {
height: 28
width: pauseResumeRow.width + Theme.spacingM * 2
radius: 14
color: pauseResumeArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
Row {
id: pauseResumeRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: isStopped ? "play_arrow" : "pause"
size: 16
color: Theme.surfaceText
}
StyledText {
text: isStopped ? I18n.tr("Resume") : I18n.tr("Pause")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: pauseResumeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (isStopped) {
CupsService.resumePrinter(modelData);
} else {
CupsService.pausePrinter(modelData);
}
}
}
}
Rectangle {
height: 28
width: testPageRow.width + Theme.spacingM * 2
radius: 14
color: testPageArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
Row {
id: testPageRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "description"
size: 16
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("Test Page")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: testPageArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: CupsService.printTestPage(modelData)
}
}
Rectangle {
height: 28
width: acceptRejectRow.width + Theme.spacingM * 2
radius: 14
color: acceptRejectArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
Row {
id: acceptRejectRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: printerData?.accepting ? "block" : "check_circle"
size: 16
color: Theme.surfaceText
}
StyledText {
text: printerData?.accepting ? I18n.tr("Reject Jobs") : I18n.tr("Accept Jobs")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: acceptRejectArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (printerData?.accepting) {
CupsService.rejectJobs(modelData);
} else {
CupsService.acceptJobs(modelData);
}
}
}
}
}
Column {
width: parent.width
spacing: Theme.spacingXS
visible: (printerData?.jobs?.length ?? 0) > 0
Row {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Jobs")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: 1
height: 1
Layout.fillWidth: true
}
Rectangle {
height: 24
width: purgeRow.width + Theme.spacingM * 2
radius: 12
color: purgeArea.containsMouse ? Theme.errorHover : Theme.surfaceLight
Row {
id: purgeRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "delete_sweep"
size: 14
color: purgeArea.containsMouse ? Theme.error : Theme.surfaceText
}
StyledText {
text: I18n.tr("Clear All")
font.pixelSize: Theme.fontSizeSmall - 1
color: purgeArea.containsMouse ? Theme.error : Theme.surfaceText
font.weight: Font.Medium
}
}
MouseArea {
id: purgeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
purgeJobsConfirm.showWithOptions({
title: I18n.tr("Clear All Jobs"),
message: I18n.tr("Cancel all jobs for \"%1\"?").arg(modelData),
confirmText: I18n.tr("Clear"),
confirmColor: Theme.error,
onConfirm: () => CupsService.purgeJobs(modelData)
});
}
}
}
}
Repeater {
model: printerData?.jobs ?? []
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 44
radius: Theme.cornerRadius - 2
color: Theme.surfaceContainerHighest
border.width: 1
border.color: Theme.outlineLight
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.right: jobActions.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "description"
size: 18
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 1
width: parent.width - 18 - Theme.spacingS
StyledText {
text: "[" + modelData.id + "] " + CupsService.getJobStateTranslation(modelData.state)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
elide: Text.ElideRight
width: parent.width
}
StyledText {
text: {
const size = Math.round((modelData.size || 0) / 1024);
const date = new Date(modelData.timeCreated);
return size + " KB • " + date.toLocaleString(Qt.locale(), Locale.ShortFormat);
}
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
}
}
Row {
id: jobActions
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: 4
Rectangle {
width: 24
height: 24
radius: 12
color: holdJobBtn.containsMouse ? Theme.surfacePressed : "transparent"
visible: modelData.state === "pending"
DankIcon {
anchors.centerIn: parent
name: "pause"
size: 14
color: Theme.surfaceVariantText
}
MouseArea {
id: holdJobBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: CupsService.holdJob(modelData.id)
}
}
Rectangle {
width: 24
height: 24
radius: 12
color: restartJobBtn.containsMouse ? Theme.surfacePressed : "transparent"
visible: modelData.state === "pending-held" || modelData.state === "completed" || modelData.state === "aborted"
DankIcon {
anchors.centerIn: parent
name: "replay"
size: 14
color: Theme.surfaceVariantText
}
MouseArea {
id: restartJobBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: CupsService.restartJob(modelData.id)
}
}
Rectangle {
width: 24
height: 24
radius: 12
color: cancelJobBtn.containsMouse ? Theme.errorHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "close"
size: 14
color: cancelJobBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: cancelJobBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: CupsService.cancelJob(printerDelegate.modelData, modelData.id)
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
StyledRect {
width: parent.width
height: classesSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
visible: CupsService.cupsAvailable && CupsService.printerClasses.length > 0
Column {
id: classesSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "workspaces"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Theme.iconSize - Theme.spacingM - refreshClassesBtn.width - Theme.spacingM
spacing: Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Printer Classes")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("%1 class(es)").arg(CupsService.printerClasses.length)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
DankActionButton {
id: refreshClassesBtn
iconName: "refresh"
buttonSize: 32
anchors.verticalCenter: parent.verticalCenter
onClicked: CupsService.getClasses()
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
}
Column {
width: parent.width
spacing: 4
Repeater {
model: CupsService.printerClasses
delegate: Rectangle {
required property var modelData
required property int index
width: parent.width
height: 48
radius: Theme.cornerRadius
color: classMouseArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.right: classActions.left
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "workspaces"
size: 20
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: modelData.name || I18n.tr("Unknown")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
}
StyledText {
text: I18n.tr("%1 printer(s)").arg(modelData.members?.length ?? 0)
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
Row {
id: classActions
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS
Rectangle {
width: 28
height: 28
radius: 14
color: deleteClassBtn.containsMouse ? Theme.errorHover : "transparent"
DankIcon {
anchors.centerIn: parent
name: "delete"
size: 18
color: deleteClassBtn.containsMouse ? Theme.error : Theme.surfaceVariantText
}
MouseArea {
id: deleteClassBtn
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
deleteClassConfirm.showWithOptions({
title: I18n.tr("Delete Class"),
message: I18n.tr("Delete class \"%1\"?").arg(modelData.name),
confirmText: I18n.tr("Delete"),
confirmColor: Theme.error,
onConfirm: () => CupsService.deleteClass(modelData.name)
});
}
}
}
}
MouseArea {
id: classMouseArea
anchors.fill: parent
anchors.rightMargin: classActions.width + Theme.spacingM
hoverEnabled: true
}
}
}
}
}
}
}
}
}