summaryrefslogtreecommitdiff
path: root/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml')
-rw-r--r--raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml1702
1 files changed, 1702 insertions, 0 deletions
diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml
new file mode 100644
index 0000000..437dc47
--- /dev/null
+++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Settings/PrinterTab.qml
@@ -0,0 +1,1702 @@
+pragma ComponentBehavior: Bound
+
+import QtQuick
+import QtQuick.Layouts
+import qs.Common
+import qs.Modals.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: printerTab
+
+ LayoutMirroring.enabled: I18n.isRtl
+ LayoutMirroring.childrenInherit: true
+
+ property bool showAddPrinter: false
+ property bool manualEntryMode: false
+ property string manualHost: ""
+ property string manualPort: "631"
+ property string manualProtocol: "ipp"
+ property bool testingConnection: false
+ property var testConnectionResult: null
+ property string newPrinterName: ""
+ property string selectedDeviceUri: ""
+ property var selectedDevice: null
+ property string selectedPpd: ""
+ property string newPrinterLocation: ""
+ property string newPrinterInfo: ""
+ property var suggestedPPDs: []
+
+ function resetAddPrinterForm() {
+ manualEntryMode = false;
+ manualHost = "";
+ manualPort = "631";
+ manualProtocol = "ipp";
+ testingConnection = false;
+ testConnectionResult = null;
+ newPrinterName = "";
+ selectedDeviceUri = "";
+ selectedDevice = null;
+ selectedPpd = "";
+ newPrinterLocation = "";
+ newPrinterInfo = "";
+ suggestedPPDs = [];
+ }
+
+ Connections {
+ target: CupsService
+ function onPpdsChanged() {
+ if (printerTab.manualEntryMode && printerTab.testConnectionResult?.success)
+ printerTab.selectDriverlessPPD();
+ }
+ }
+
+ function selectDriverlessPPD() {
+ if (printerTab.selectedPpd || CupsService.ppds.length === 0)
+ return;
+
+ const probeModel = printerTab.testConnectionResult?.data?.makeModel || "";
+ let suggested = [];
+
+ // Try to find a model-specific PPD match
+ if (probeModel) {
+ const normalizedModel = probeModel.toLowerCase().replace(/[^a-z0-9]/g, "");
+ const modelMatches = CupsService.ppds.filter(p => {
+ const normalizedPPD = (p.makeModel || "").toLowerCase().replace(/[^a-z0-9]/g, "");
+ return normalizedPPD.includes(normalizedModel) || normalizedModel.includes(normalizedPPD);
+ });
+ if (modelMatches.length > 0)
+ suggested = suggested.concat(modelMatches);
+ }
+
+ // Always include driverless as an option
+ const driverless = CupsService.ppds.filter(p => p.name === "driverless" || p.name === "everywhere");
+ for (const d of driverless) {
+ if (!suggested.find(s => s.name === d.name))
+ suggested.push(d);
+ }
+
+ if (suggested.length > 0) {
+ printerTab.selectedPpd = suggested[0].name;
+ printerTab.suggestedPPDs = suggested;
+ }
+ }
+
+ 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
+ topPadding: 4
+
+ 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.surfaceContainerHigh
+
+ 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
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+ }
+
+ 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.surfaceContainerHigh
+ 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
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ StyledText {
+ text: I18n.tr("Configure a new printer")
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+
+ 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)
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Rectangle {
+ width: discoverRow.width + Theme.spacingM * 2
+ height: 32
+ radius: Theme.cornerRadius
+ color: !printerTab.manualEntryMode ? Theme.primary : (discoverArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight)
+
+ Row {
+ id: discoverRow
+ anchors.centerIn: parent
+ spacing: Theme.spacingXS
+
+ DankIcon {
+ name: "search"
+ size: 16
+ color: !printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
+ }
+
+ StyledText {
+ text: I18n.tr("Discover Devices", "Toggle button to scan for printers via mDNS/Avahi")
+ font.pixelSize: Theme.fontSizeSmall
+ color: !printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
+ font.weight: Font.Medium
+ }
+ }
+
+ MouseArea {
+ id: discoverArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ printerTab.manualEntryMode = false;
+ printerTab.testConnectionResult = null;
+ printerTab.testingConnection = false;
+ }
+ }
+ }
+
+ Rectangle {
+ width: manualRow.width + Theme.spacingM * 2
+ height: 32
+ radius: Theme.cornerRadius
+ color: printerTab.manualEntryMode ? Theme.primary : (manualArea.containsMouse ? Theme.primaryHoverLight : Theme.surfaceLight)
+
+ Row {
+ id: manualRow
+ anchors.centerIn: parent
+ spacing: Theme.spacingXS
+
+ DankIcon {
+ name: "edit"
+ size: 16
+ color: printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
+ }
+
+ StyledText {
+ text: I18n.tr("Add by Address", "Toggle button to manually add a printer by IP or hostname")
+ font.pixelSize: Theme.fontSizeSmall
+ color: printerTab.manualEntryMode ? Theme.onPrimary : Theme.surfaceText
+ font.weight: Font.Medium
+ }
+ }
+
+ MouseArea {
+ id: manualArea
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ printerTab.manualEntryMode = true;
+ printerTab.selectedDevice = null;
+ printerTab.selectedDeviceUri = "";
+ if (CupsService.ppds.length === 0)
+ CupsService.getPPDs();
+ }
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+ visible: !printerTab.manualEntryMode
+
+ 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
+ enableFuzzySearch: true
+ emptyText: I18n.tr("No devices found")
+ currentValue: {
+ if (CupsService.loadingDevices)
+ return I18n.tr("Scanning...");
+ if (printerTab.selectedDevice)
+ return CupsService.getDeviceDisplayName(printerTab.selectedDevice);
+ return I18n.tr("Select device...");
+ }
+ options: CupsService.filteredDevices.map(d => CupsService.getDeviceDisplayName(d))
+ onValueChanged: value => {
+ 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
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+ visible: printerTab.manualEntryMode
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: I18n.tr("Host", "Label for printer IP address or hostname input field")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ width: 80
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankTextField {
+ width: parent.width - 80 - Theme.spacingS
+ placeholderText: I18n.tr("IP address or hostname", "Placeholder text for manual printer address input")
+ text: printerTab.manualHost
+ onTextEdited: {
+ printerTab.manualHost = text;
+ printerTab.testConnectionResult = null;
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: I18n.tr("Port", "Label for printer port number input field")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ width: 80
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankTextField {
+ width: 80
+ placeholderText: "631"
+ text: printerTab.manualPort
+ onTextEdited: {
+ printerTab.manualPort = text;
+ printerTab.testConnectionResult = null;
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ StyledText {
+ text: I18n.tr("Protocol", "Label for printer protocol selector, e.g. ipp, ipps, lpd, socket")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: Theme.surfaceText
+ width: 80
+ anchors.verticalCenter: parent.verticalCenter
+ }
+
+ DankDropdown {
+ id: protocolDropdown
+ dropdownWidth: 120
+ popupWidth: 120
+ currentValue: printerTab.manualProtocol
+ options: ["ipp", "ipps", "lpd", "socket"]
+ onValueChanged: value => {
+ printerTab.manualProtocol = value;
+ printerTab.testConnectionResult = null;
+ }
+ }
+ }
+
+ Row {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ Item {
+ width: 80
+ height: 1
+ }
+
+ DankButton {
+ text: printerTab.testingConnection ? I18n.tr("Testing...", "Button state while testing printer connection") : I18n.tr("Test Connection", "Button to test connection to a printer by IP address")
+ iconName: printerTab.testingConnection ? "sync" : "lan"
+ buttonHeight: 36
+ enabled: printerTab.manualHost.length > 0 && !printerTab.testingConnection
+ onClicked: {
+ printerTab.testingConnection = true;
+ printerTab.testConnectionResult = null;
+ const port = parseInt(printerTab.manualPort) || 631;
+ CupsService.testConnection(printerTab.manualHost, port, printerTab.manualProtocol, response => {
+ printerTab.testingConnection = false;
+ if (response.error) {
+ printerTab.testConnectionResult = {
+ "success": false,
+ "error": response.error
+ };
+ } else if (response.result) {
+ printerTab.testConnectionResult = {
+ "success": response.result.reachable === true,
+ "data": response.result
+ };
+ if (response.result.reachable) {
+ if (response.result.uri)
+ printerTab.selectedDeviceUri = response.result.uri;
+ if (response.result.name && !printerTab.newPrinterName)
+ printerTab.newPrinterName = response.result.name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").substring(0, 32) || "Printer";
+ // Load PPDs if not loaded yet, then select driverless
+ if (CupsService.ppds.length === 0) {
+ CupsService.getPPDs();
+ }
+ selectDriverlessPPD();
+ }
+ }
+ });
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingXS
+ visible: printerTab.testConnectionResult !== null
+
+ Row {
+ spacing: Theme.spacingS
+
+ Item {
+ width: 80
+ height: 1
+ }
+
+ Rectangle {
+ width: 8
+ height: 8
+ radius: 4
+ anchors.verticalCenter: parent.verticalCenter
+ color: printerTab.testConnectionResult?.success ? Theme.success : Theme.error
+ }
+
+ StyledText {
+ text: printerTab.testConnectionResult?.success ? I18n.tr("Printer reachable", "Status message when test connection to printer succeeds") : I18n.tr("Connection failed", "Status message when test connection to printer fails")
+ font.pixelSize: Theme.fontSizeMedium
+ font.weight: Font.Medium
+ color: printerTab.testConnectionResult?.success ? Theme.success : Theme.error
+ }
+ }
+
+ Row {
+ spacing: Theme.spacingS
+ visible: printerTab.testConnectionResult?.success && (printerTab.testConnectionResult?.data?.makeModel || printerTab.testConnectionResult?.data?.info)
+
+ Item {
+ width: 80
+ height: 1
+ }
+
+ StyledText {
+ text: printerTab.testConnectionResult?.data?.makeModel || printerTab.testConnectionResult?.data?.info || ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ }
+ }
+
+ Row {
+ spacing: Theme.spacingS
+ visible: !printerTab.testConnectionResult?.success && printerTab.testConnectionResult?.data?.error
+
+ Item {
+ width: 80
+ height: 1
+ }
+
+ StyledText {
+ text: printerTab.testConnectionResult?.data?.error || printerTab.testConnectionResult?.error || ""
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ width: parent.parent.width - 80 - Theme.spacingS
+ wrapMode: Text.WordWrap
+ }
+ }
+ }
+ }
+
+ Column {
+ width: parent.width
+ spacing: Theme.spacingS
+
+ 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
+ enableFuzzySearch: true
+ emptyText: I18n.tr("No drivers found")
+ 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: {
+ 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 => {
+ 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
+ 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
+ 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
+ placeholderText: I18n.tr("Optional description")
+ text: printerTab.newPrinterInfo
+ onTextEdited: printerTab.newPrinterInfo = text
+ }
+ }
+ }
+
+ Row {
+ LayoutMirroring.enabled: false
+ 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.surfaceContainerHigh
+ 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
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ StyledText {
+ text: {
+ const count = CupsService.printerNames.length;
+ if (count === 0)
+ return I18n.tr("No printers configured");
+ return I18n.ntr("%1 printer", "%1 printers", count).arg(count);
+ }
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+
+ 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
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ Row {
+ anchors.left: parent.left
+ 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.ntr("%1 job", "%1 jobs", printerData?.jobs?.length ?? 0).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
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ 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
+ anchors.left: parent.left
+ }
+ }
+ }
+
+ 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.surfaceContainerHigh
+ 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
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+
+ StyledText {
+ text: I18n.ntr("%1 class", "%1 classes", CupsService.printerClasses.length).arg(CupsService.printerClasses.length)
+ font.pixelSize: Theme.fontSizeSmall
+ color: Theme.surfaceVariantText
+ width: parent.width
+ horizontalAlignment: Text.AlignLeft
+ }
+ }
+
+ 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.ntr("%1 printer", "%1 printers", modelData.members?.length ?? 0).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
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}