Warning, /plasma/print-manager/src/kcm/ui/FindPrinter.qml is written in an unsupported language. File is not indexed.
0001 /**
0002 SPDX-FileCopyrightText: 2023 Mike Noe <noeerover@gmail.com>
0003 SPDX-License-Identifier: GPL-2.0-or-later
0004 */
0005
0006 import QtQuick
0007 import QtQuick.Layouts
0008 import QtQuick.Controls as QQC2
0009 import org.kde.plasma.components as PComp
0010 import org.kde.kirigami as Kirigami
0011 import org.kde.plasma.printmanager as PM
0012 import org.kde.kitemmodels as KSFM
0013 import org.kde.plasma.extras as PlasmaExtras
0014
0015 Kirigami.Dialog {
0016 id: root
0017
0018 property bool loading: false
0019 property bool showingManual: false
0020 property bool hasDetectedDevices: false
0021
0022 // MFG:HP;MDL:ENVY 4520 series;CLS:PRINTER;DES:ENVY 4520 series;SN:TH6BN4M1390660;
0023 function parseDeviceId(devId: string, key: string) {
0024 if (devId === undefined) {
0025 return ""
0026 }
0027
0028 // if no key, return the array
0029 const arr = devId.split(";")
0030 if (key === undefined) {
0031 return arr
0032 }
0033
0034 // otherwise, return key[value]
0035 for (let i=0, len=arr.length; i<len; ++i) {
0036 const a = arr[i].split(":")
0037 if (a[0] === key)
0038 return a[1]
0039 }
0040
0041 return ""
0042 }
0043
0044 // Remove the ppd settings to force manual make/model driver selection
0045 function manualDriverSelect() {
0046 settings.remove("ppd-name")
0047 settings.add("ppd-type", PM.PPDType.Custom)
0048 root.setValues(settings.pending)
0049 close()
0050 }
0051
0052 // Find the first direct device and network device
0053 // note, we're looping the decscendent filter model
0054 function setDeviceSelection() {
0055 let directNdx = -1
0056 let netNdx = -1
0057 for (let i=0, len=deviceItems.rowCount(); i<len; ++i) {
0058 const ndx = deviceItems.mapToSource(deviceItems.index(i,0))
0059 const cls = deviceItems.sourceModel.data(ndx, PM.DevicesModel.DeviceClass)
0060 const devId = deviceItems.sourceModel.data(ndx, PM.DevicesModel.DeviceId)
0061 if (cls.toString() === "direct") {
0062 directNdx = i
0063 } else if (cls.toString() === "network" && devId.length > 0) {
0064 netNdx = i
0065 }
0066 if (netNdx >= 0 && directNdx >= 0)
0067 break
0068 }
0069
0070 // Did we actually find device, either direct or network?
0071 if (directNdx === -1 && netNdx === -1) {
0072 compLoader.sourceComponent = noDevicesComp
0073 hasDetectedDevices = false
0074 showingManual = true
0075 deviceItems.invalidateFilter()
0076 } else {
0077 hasDetectedDevices = true
0078 // by default, select direct connect printer
0079 deviceList.currentIndex = directNdx !== -1 ? directNdx : netNdx
0080 deviceList.itemAtIndex(deviceList.currentIndex).clicked()
0081 }
0082
0083 }
0084
0085 signal setValues(var values)
0086
0087 title: i18nc("@title:window", "Set up a Printer Connection")
0088
0089 standardButtons: Kirigami.Dialog.NoButton
0090
0091 customFooterActions: [
0092 Kirigami.Action {
0093 text: showingManual
0094 ? i18nc("@action:button", "Show Detected Devices")
0095 : i18nc("@action:button", "Show Manual Options")
0096 icon.name: showingManual
0097 ? "standard-connector-symbolic"
0098 : "internet-services"
0099 visible: hasDetectedDevices
0100 onTriggered: {
0101 showingManual = !showingManual
0102 deviceItems.invalidateFilter()
0103 deviceList.currentIndex = -1
0104 compLoader.sourceComponent = undefined
0105
0106 if (!showingManual) {
0107 setDeviceSelection()
0108 } else {
0109 compLoader.sourceComponent = chooseManualComp
0110 }
0111 }
0112 },
0113 Kirigami.Action {
0114 text: i18n("Refresh")
0115 enabled: !loading
0116 icon.name: "view-refresh-symbolic"
0117 onTriggered: {
0118 showingManual = false
0119 devices.load()
0120 }
0121 }
0122 ]
0123
0124 footerLeadingComponent: Kirigami.UrlButton {
0125 text: i18n("CUPS Network Printers Help")
0126 url: "http://localhost:631/help/network.html"
0127 padding: Kirigami.Units.largeSpacing
0128 }
0129
0130 onClosed: destroy(10)
0131
0132 ConfigValues {
0133 id: settings
0134 }
0135
0136 // Filter the descendants to exclude "null" deviceClass
0137 KSFM.KSortFilterProxyModel {
0138 id: deviceItems
0139 sortRole: PM.DevicesModel.DeviceCategory
0140
0141 // Descendants are the actual printer devices
0142 sourceModel: KSFM.KDescendantsProxyModel {
0143 sourceModel: devices
0144 }
0145
0146 filterRowCallback: (source_row, source_parent) => {
0147 const ndx = sourceModel.index(source_row, 0, source_parent)
0148 if (sourceModel.data(ndx, PM.DevicesModel.DeviceClass) === undefined) {
0149 return false
0150 }
0151 const cat = sourceModel.data(ndx, PM.DevicesModel.DeviceCategory)
0152 if (showingManual) {
0153 return cat === "Manual"
0154 } else {
0155 return cat !== "Manual"
0156 }
0157
0158 }
0159 }
0160
0161 // Two-level QSIM, top level is "device category" (Qt.UserRole)
0162 PM.DevicesModel {
0163 id: devices
0164
0165 function load() {
0166 loading = true
0167 kcm.clearRemotePrinters()
0168 kcm.clearRecommendedDrivers()
0169 update()
0170 }
0171
0172 Component.onCompleted: load()
0173
0174 onLoaded: {
0175 loading = false
0176 setDeviceSelection()
0177 }
0178 }
0179
0180 Component {
0181 id: uriComp
0182
0183 BaseDevice {
0184 id: uriItem
0185
0186 title: compLoader.info
0187 subtitle: i18nc("@title:group", "Find remote printing devices")
0188 helpText: i18nc("@info:usagetip", "Enter the address of the remote host/device")
0189 icon.source: "internet-services"
0190
0191 actions: [
0192 Kirigami.Action {
0193 text: i18nc("@action:button", "Select Printer")
0194 enabled: list.currentIndex !== -1
0195 icon.name: "dialog-ok-symbolic"
0196 onTriggered: {
0197 settings.set(kcm.remotePrinters[list.currentIndex])
0198 manualDriverSelect()
0199 }
0200 }
0201 ]
0202
0203 Component.onCompleted: {
0204 connSearch.text = compLoader.selector !== "other"
0205 ? compLoader.selector + "://"
0206 : "ipp://"
0207 connSearch.forceActiveFocus()
0208 }
0209
0210 Connections {
0211 target: kcm
0212
0213 function onRemotePrintersLoaded() {
0214 if (list.count > 0) {
0215 list.itemAtIndex(0).clicked()
0216 }
0217 }
0218 }
0219
0220 RowLayout {
0221 Layout.alignment: Qt.AlignHCenter
0222 QQC2.Label {
0223 text: i18nc("@label:textbox", "Uri:")
0224 }
0225
0226 Kirigami.SearchField {
0227 id: connSearch
0228 focus: true
0229 delaySearch: true
0230 autoAccept: false
0231 placeholderText: i18n("Enter host URI")
0232 Layout.fillWidth: true
0233
0234 KeyNavigation.left: uriItem.parent
0235 KeyNavigation.right: list
0236
0237 KeyNavigation.down: list
0238
0239 KeyNavigation.backtab: KeyNavigation.left
0240 KeyNavigation.tab: KeyNavigation.right
0241
0242 onAccepted: {
0243 if (text.length > 0)
0244 kcm.getRemotePrinters(text, compLoader.selector)
0245 else
0246 kcm.clearRemotePrinters()
0247 }
0248 }
0249 }
0250
0251 // remote printer list
0252 QQC2.ScrollView {
0253 Layout.alignment: Qt.AlignHCenter
0254 Layout.fillWidth: true
0255 Layout.fillHeight: true
0256
0257 contentItem: ListView {
0258 id: list
0259 currentIndex: -1
0260 highlight: PlasmaExtras.Highlight {}
0261 highlightMoveDuration: 0
0262 highlightResizeDuration: 0
0263
0264 activeFocusOnTab: true
0265 keyNavigationWraps: true
0266
0267 KeyNavigation.up: connSearch
0268 KeyNavigation.backtab: uriItem.parent
0269 Keys.onUpPressed: event => {
0270 if (currentIndex === 0) {
0271 currentIndex = -1
0272 }
0273 event.accepted = false
0274 }
0275
0276 model: kcm.remotePrinters
0277
0278 delegate: Kirigami.SubtitleDelegate {
0279 width: ListView.view.width
0280 text: modelData["printer-info"]
0281 subtitle: modelData["printer-name"]
0282 icon.name: modelData.remote
0283 ? "folder-network-symbolic"
0284 : modelData["printer-is-class"] ? "folder" : modelData.iconName
0285
0286 onClicked: {
0287 ListView.view.currentIndex = index
0288 }
0289 }
0290 }
0291 }
0292
0293 AddressExamples {
0294 Layout.alignment: Qt.AlignHCenter
0295 Layout.fillWidth: true
0296 Layout.fillHeight: true
0297
0298 onSelected: address => connSearch.text = address
0299 }
0300
0301 }
0302 }
0303
0304 Component {
0305 id: networkComp
0306
0307 BaseDevice {
0308 title: settings.value("printer-make-and-model")
0309 subtitle: settings.value("device-desc")
0310
0311 Component.onCompleted: {
0312 drivers.load(settings.value("device-id")
0313 , settings.value("printer-make-and-model")
0314 , settings.value("device-uri"))
0315 }
0316
0317 // Recommended Driver list
0318 Drivers {
0319 id: drivers
0320
0321 onSelected: driverMap => {
0322 settings.set(driverMap)
0323 root.setValues(settings.pending)
0324 close()
0325 }
0326 }
0327 }
0328 }
0329
0330 Component {
0331 id: directComp
0332
0333 BaseDevice {
0334 title: settings.value("printer-make-and-model")
0335 subtitle: settings.value("device-desc")
0336 helpText: i18nc("@info:usagetip", "Choose a device connection")
0337
0338 // Connection list
0339 QQC2.ScrollView {
0340 Layout.alignment: Qt.AlignHCenter
0341 Layout.fillWidth: true
0342 Layout.fillHeight: true
0343
0344 contentItem: ListView {
0345 id: directlist
0346 highlight: PlasmaExtras.Highlight {}
0347 highlightMoveDuration: 0
0348 highlightResizeDuration: 0
0349
0350 activeFocusOnTab: true
0351 keyNavigationWraps: true
0352
0353 KeyNavigation.backtab: root.parent
0354 Keys.onUpPressed: event => {
0355 if (currentIndex === 0) {
0356 currentIndex = -1;
0357 }
0358 event.accepted = false;
0359 }
0360
0361 model: settings.value("device-uris")
0362
0363 delegate: PComp.ItemDelegate {
0364 width: ListView.view.width
0365 text: devices.uriDevice(modelData)
0366 icon.name: "standard-connector-symbolic"
0367
0368 Component.onCompleted: {
0369 if (index === 0)
0370 onClicked()
0371 }
0372
0373 onClicked: {
0374 ListView.view.currentIndex = index
0375 settings.add("device-uri", modelData)
0376 drivers.load(settings.value("device-id")
0377 , settings.value("printer-make-and-model")
0378 , modelData)
0379 }
0380 }
0381 }
0382 }
0383
0384 // Recommended Driver list
0385 Drivers {
0386 id: drivers
0387
0388 onSelected: driverMap => {
0389 settings.set(driverMap)
0390 root.setValues(settings.pending)
0391 close()
0392 }
0393 }
0394 }
0395 }
0396
0397 Component {
0398 id: lpdComp
0399 NotAvailable {}
0400 }
0401
0402 Component {
0403 id: socketComp
0404 NotAvailable {}
0405 }
0406
0407 Component {
0408 id: serialComp
0409 NotAvailable {}
0410 }
0411
0412 Component {
0413 id: smbComp
0414 NotAvailable {}
0415 }
0416
0417 Component {
0418 id: naComp
0419 NotAvailable {}
0420 }
0421
0422 Component {
0423 id: noDevicesComp
0424
0425 Kirigami.PlaceholderMessage {
0426 text: i18nc("@info:status", "Unable to automatically discover any printing devices")
0427 explanation: i18nc("@info:usagetip", "Choose \"Refresh\" to try again or choose a manual configuration option from the list")
0428 Layout.maximumWidth: parent.width - Kirigami.Units.largeSpacing * 4
0429 }
0430 }
0431
0432 Component {
0433 id: chooseManualComp
0434
0435 Kirigami.PlaceholderMessage {
0436 text: i18nc("@info:usagetip", "Choose a manual configuration option from the list")
0437 Layout.maximumWidth: parent.width - Kirigami.Units.largeSpacing * 4
0438 }
0439 }
0440
0441 component NotAvailable: ColumnLayout {
0442 // This is inside a Loader that is fillW/fillH: true
0443 // pad with "spacers" top/bottom to force centering
0444 Item { Layout.fillHeight: true }
0445
0446 Kirigami.PlaceholderMessage {
0447 icon.name: "package-available-locked"
0448 text: compLoader.info
0449 explanation: i18nc("@info:status", "This feature is not yet available (%1)", compLoader.selector)
0450 Layout.maximumWidth: parent.width - Kirigami.Units.largeSpacing * 4
0451 }
0452
0453 Item { Layout.fillHeight: true }
0454 }
0455
0456 contentItem: RowLayout {
0457 spacing: 0
0458
0459 QQC2.ScrollView {
0460 Layout.fillHeight: true
0461 Layout.preferredWidth: Kirigami.Units.gridUnit*13
0462 clip: true
0463
0464 contentItem: ListView {
0465 id: deviceList
0466
0467 PComp.BusyIndicator {
0468 id: busyInd
0469 running: loading
0470 anchors.centerIn: parent
0471 implicitWidth: Math.floor(parent.width/2)
0472 implicitHeight: implicitWidth
0473 }
0474
0475 clip: true
0476 currentIndex: -1
0477 highlight: PlasmaExtras.Highlight {}
0478 highlightMoveDuration: 0
0479 highlightResizeDuration: 0
0480
0481 activeFocusOnTab: true
0482 keyNavigationWraps: true
0483
0484 model: deviceItems
0485
0486 section {
0487 property: "deviceCategory"
0488 delegate: Kirigami.ListSectionHeader {
0489 width: ListView.view.width
0490 required property string section
0491 label: section
0492 }
0493 }
0494
0495 delegate: Kirigami.SubtitleDelegate {
0496 width: ListView.view.width
0497 visible: deviceClass !== undefined
0498
0499 text: deviceInfo.replace("Internet Printing Protocol", "IPP")
0500 subtitle: deviceMakeModel.replace("Unknown", "")
0501
0502 icon.name: deviceId.length === 0
0503 ? "internet-services"
0504 : "printer-symbolic"
0505
0506 onClicked: {
0507 ListView.view.currentIndex = index
0508 compLoader.selector = ""
0509 compLoader.info = ""
0510 settings.clear()
0511
0512 if (deviceUri && deviceUri.length > 0) {
0513 compLoader.info = deviceInfo
0514 if (deviceId && deviceId.length > 0) {
0515 if (deviceClass === "file") {
0516 compLoader.selector = deviceUri.replace(/:\//g,'')
0517 } else {
0518 // a printer device
0519 settings.set({"device-id": deviceId
0520 , "device-uri": deviceUri
0521 , "device-uris": deviceUris
0522 , "device-class": deviceClass
0523 , "device-desc": deviceDescription
0524 , "printer-info": deviceInfo
0525 , "printer-make": parseDeviceId(deviceId, "MFG")
0526 , "printer-model": parseDeviceId(deviceId, "MDL")
0527 , "printer-make-and-model": deviceMakeModel
0528 , "printer-location": deviceLocation
0529 , "ppd-type": PM.PPDType.Custom
0530 })
0531 compLoader.selector = deviceClass
0532 }
0533
0534 } else {
0535 // a category item
0536 compLoader.selector = deviceUri
0537 }
0538 }
0539 }
0540 }
0541 }
0542 }
0543
0544 Kirigami.Separator {
0545 Layout.fillHeight: true
0546 width: 1
0547 }
0548
0549 Loader {
0550 id: compLoader
0551 active: !loading
0552
0553 Layout.fillWidth: true
0554 Layout.fillHeight: true
0555 Layout.margins: Kirigami.Units.largeSpacing
0556
0557 property string selector: ""
0558 property string info: ""
0559
0560 onSelectorChanged: {
0561 switch (selector) {
0562 case "other":
0563 case "ipp":
0564 case "ipps":
0565 case "http":
0566 case "https":
0567 case "scsi":
0568 case "cups-brf":
0569 sourceComponent = uriComp
0570 break
0571 case "network":
0572 sourceComponent = networkComp
0573 break
0574 case "direct":
0575 sourceComponent = directComp
0576 break
0577 case "lpd":
0578 sourceComponent = lpdComp
0579 break
0580 case "socket":
0581 sourceComponent = socketComp
0582 break
0583 case "smb":
0584 sourceComponent = sambaComp
0585 break
0586 case "serial":
0587 sourceComponent = serialComp
0588 break
0589 default:
0590 sourceComponent = naComp
0591 }
0592 }
0593 }
0594
0595 }
0596
0597 }