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 }