2 # -*- coding: utf-8 -*-
4 #############################################################################
6 ## Copyright (C) 2007 Canonical Ltd
7 ## Author: Jonathan Riddell <jriddell@ubuntu.com>
9 ## Includes code from System Config Printer
10 ## Copyright (C) 2007 Tim Waugh <twaugh@redhat.com>
11 ## Copyright (C) 2007 Red Hat, Inc.
13 ## This program is free software; you can redistribute it and/or
14 ## modify it under the terms of the GNU General Public License as
15 ## published by the Free Software Foundation; either version 2 of
16 ## the License, or (at your option) any later version.
18 ## This program is distributed in the hope that it will be useful,
19 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ## GNU General Public License for more details.
23 ## You should have received a copy of the GNU General Public License
24 ## along with this program. If not, see <http://www.gnu.org/licenses/>.
26 #############################################################################
28 MIN_REFRESH_INTERVAL
= 1 # seconds
31 import sys
, os
, time
, traceback
, re
, tempfile
, httplib
35 #load modules from system-config-printer-common (debug, smburi), change path here if you have it installed elsewhere
36 SYSTEM_CONFIG_PRINTER_DIR
= "/usr/share/system-config-printer"
37 if os
.path
.exists(SYSTEM_CONFIG_PRINTER_DIR
+ "/debug.py"):
38 sys
.path
.append(SYSTEM_CONFIG_PRINTER_DIR
)
40 from PyQt4
.QtCore
import *
41 from PyQt4
.QtGui
import *
44 from PyKDE4
.kdecore
import *
45 from PyKDE4
.kdeui
import *
47 #use _() to keep code the same as gnome system-config-printer
49 return unicode(i18n(string
), "utf-8")
51 def translate(self
, prop
):
52 """reimplement method from uic to change it to use gettext"""
53 if prop
.get("notr", None) == "true":
54 return self
._cstring
(prop
)
58 text
= prop
.text
.encode("UTF-8")
61 uic
.properties
.Properties
._string
= translate
64 cups
.require ("1.9.27")
66 # These come from system-config-printer
68 import cupshelpers
#, options
69 from smburi
import SMBURI
73 import dbus
.mainloop
.qt
76 ellipsis
= unichr(0x2026)
79 try_CUPS_SERVER_REMOTE_ANY
= cups
.CUPS_SERVER_REMOTE_ANY
80 except AttributeError:
81 # cups module was compiled with CUPS < 1.3
82 try_CUPS_SERVER_REMOTE_ANY
= "_remote_any"
84 def validDeviceURI (uri
):
85 """Returns True is the provided URI is valid."""
86 if uri
.find (":/") > 0:
91 """our main class is the main window"""
93 printer_states
= { cups
.IPP_PRINTER_IDLE
: i18nc("Printer state", "Idle"),
94 cups
.IPP_PRINTER_PROCESSING
: i18nc("Printer state", "Processing"),
95 cups
.IPP_PRINTER_BUSY
: i18nc("Printer state", "Busy"),
96 cups
.IPP_PRINTER_STOPPED
: i18nc("Printer state", "Stopped") }
98 def __init__(self
, start_printer
= None, change_ppd
= False):
99 QWidget
.__init
__(self
)
102 self
.language
= locale
.getlocale(locale
.LC_MESSAGES
)
103 self
.encoding
= locale
.getlocale(locale
.LC_CTYPE
)
106 os
.environ
['LC_ALL'] = 'C'
107 locale
.setlocale (locale
.LC_ALL
, "")
108 self
.language
= locale
.getlocale(locale
.LC_MESSAGES
)
109 self
.encoding
= locale
.getlocale(locale
.LC_CTYPE
)
112 self
.conflicts
= set() # of options
113 self
.connect_server
= (self
.printer
and self
.printer
.getServer()) \
115 self
.connect_user
= cups
.getUser()
116 self
.password
= '' #FIXME not in Gnome version
117 self
.passwd_retry
= False #FIXME not in Gnome version
118 self
.widget_data_setting
= {} #FIXME not in Gnome version
119 #FIXMEcups.setPasswordCB(self.cupsPasswdCallback)
120 ##self.server_is_publishing = False #FIXME new in Gnome version
122 self
.changed
= set() # of options
124 self
.servers
= set((self
.connect_server
,))
127 self
.cups
= cups
.Connection()
129 #warn the user that cups is not running
130 message
= i18n("CUPS is not currently running. CUPS is required for complete printing functionality. Please start CUPS then restart this application.")
131 answer
= QMessageBox
.warning(self
, i18n("Print Server Not Running"), message
, QMessageBox
.Ok
)
132 #still allow the app to start for those that really want that
133 if answer
== QMessageBox
.Ok
:
138 if os
.path
.exists("system-config-printer.ui"):
139 APPDIR
= QDir
.currentPath()
141 file = KStandardDirs
.locate("appdata", "system-config-printer.ui")
142 APPDIR
= file.left(file.lastIndexOf("/"))
144 uic
.loadUi(APPDIR
+ "/" + "system-config-printer.ui", self
)
148 self
.newPrinterGUI
= np
= NewPrinterGUI(self
)
149 #np.NewPrinterWindow.set_transient_for(self.MainWindow)
153 self
.connect(self
.mainlist
, SIGNAL("itemSelectionChanged()"), self
.on_tvMainList_cursor_changed
)
154 self
.connect(self
.mainlist
, SIGNAL("currentItemChanged (QTreeWidgetItem*, QTreeWidgetItem*)"), self
.on_tvMainList_changed
)
155 self
.connect(self
.chkServerBrowse
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
156 self
.connect(self
.chkServerShare
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
157 self
.connect(self
.chkServerShareAny
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
158 self
.connect(self
.chkServerRemoteAdmin
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
159 self
.connect(self
.chkServerAllowCancelAll
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
160 self
.connect(self
.chkServerLogDebug
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
162 self
.connect(self
.btnNewClass
, SIGNAL("clicked()"), self
.on_new_class_activate
)
163 self
.connect(self
.btnNewPrinter
, SIGNAL("clicked()"), self
.on_new_printer_activate
)
165 self
.connect(self
.entPDescription
, SIGNAL("textEdited(const QString&)"), self
.on_printer_changed
)
166 self
.connect(self
.entPLocation
, SIGNAL("textEdited(const QString&)"), self
.on_printer_changed
)
167 self
.connect(self
.entPDevice
, SIGNAL("textEdited(const QString&)"), self
.on_printer_changed
)
168 self
.connect(self
.chkPEnabled
, SIGNAL("stateChanged(int)"), self
.on_printer_changed
)
169 self
.connect(self
.chkPAccepting
, SIGNAL("stateChanged(int)"), self
.on_printer_changed
)
170 self
.connect(self
.chkPShared
, SIGNAL("stateChanged(int)"), self
.on_printer_changed
)
171 self
.connect(self
.cmbPErrorPolicy
, SIGNAL("currentIndexChanged(int)"), self
.on_printer_changed
)
172 self
.connect(self
.cmbPOperationPolicy
, SIGNAL("currentIndexChanged(int)"), self
.on_printer_changed
)
173 self
.connect(self
.cmbPStartBanner
, SIGNAL("currentIndexChanged(int)"), self
.on_printer_changed
)
174 self
.connect(self
.cmbPEndBanner
, SIGNAL("currentIndexChanged(int)"), self
.on_printer_changed
)
175 #self.connect(self.rbtnPAllow, SIGNAL("toggled(bool)"), self.on_printer_changed)
178 self
.populateList(start_printer
, change_ppd
)
179 except cups
.HTTPError
, (s
,):
183 self
.show_HTTP_Error(s
)
185 self
.mainlist
.header().hide()
187 #hide some bits until implemented
188 self
.btnNewPrinterNetwork
.hide()
189 self
.newPrinterNetworkLabel
.hide()
190 self
.btnNewPrinterSpecial
.hide()
191 self
.newPrinterSpecialLabel
.hide()
192 self
.btnNewPrinter
.setText(i18n("New Printer"))
193 self
.btnPrinterPropertiesApply
.setIcon(KIcon("dialog-ok-apply"))
194 self
.btnRevert
.setIcon(KIcon("document-revert"))
195 self
.newPrinterLabel
.hide()
196 #(obsolete) only show settings until ready for the rest
197 #self.mainlist.hide()
198 self
.mainlist
.setCurrentItem(self
.settingsItem
)
199 #FIXME hide labels until implemented
200 self
.lblPOptions
.hide()
201 self
.lblPInstallOptions
.hide()
203 self
.setWindowIcon(KIcon("printer"))
205 # now called dests_iconview_item_activated() in the Gnome version
206 def on_tvMainList_cursor_changed(self
):
208 # The unapplied changes for this item have not been saved,
209 # and the user just pressed "Cancel".
210 #FIXME, should offer dialog prompting to save or cancel here
212 items
= self
.mainlist
.selectedItems()
216 #FIXME only show settings until ready for the rest
217 #item = self.settingsItem
218 type = str(item
.text(1))
219 name
= str(item
.text(0))
220 #name, type = self.getSelectedItem()
221 #model, self.mainListSelected = self.tvMainList.get_selection().get_selected()
222 #Save the values incase it gets deselected
223 self
.mainListSelectedType
= type
224 self
.mainListSelectedName
= name
227 #self.ntbkMain.set_current_page(0)
228 self
.ntbkMain
.setCurrentIndex(0)
229 elif type == "Settings":
230 #self.ntbkMain.set_current_page(0)
231 self
.ntbkMain
.setCurrentIndex(1)
235 # No connection to CUPS. Make sure the Apply/Revert buttons
237 self
.setDataButtonState()
238 item_selected
= False
239 elif type in ['Printer', 'Class']:
241 self
.fillPrinterTab(name
)
242 self
.fillPrinterOptions()
243 self
.setDataButtonState()
245 # Perhaps cupsGetPPD2 failed for a browsed printer.
246 self
.ntbkMain
.setCurrentIndex(3)
247 #self.ntbkMain.set_current_page(2)
250 #self.ntbkMain.set_current_page(1)
251 self
.ntbkMain
.setCurrentIndex(2)
253 #self.ntbkMain.set_current_page(2)
254 self
.ntbkMain
.setCurrentIndex(3)
255 self
.setDataButtonState()
256 item_selected
= False
258 """FIXME, copy button
259 is_local = item_selected and not self.printers[name].discovered
260 for widget in [self.copy, self.btnCopy]:
261 widget.set_sensitive(item_selected)
262 for widget in [self.delete, self.btnDelete]:
263 widget.set_sensitive(is_local)
266 def printer_properties_response(self
):
267 name
, type = self
.getSelectedItem()
268 if type in ("Printer", "Class"):
269 return self
.save_printer(self
.printer
)
270 elif type == "Settings":
271 return self
.save_serversettings()
273 def on_tvMainList_changed(self
, new
, old
):
274 """about to change, offer to save"""
276 answer
= QMessageBox
.question(self
, "Save Changes", "Do you want to save changes?", QMessageBox
.Save | QMessageBox
.Discard | QMessageBox
.Cancel
, QMessageBox
.Save
)
277 if answer
== QMessageBox
.Save
:
278 self
.printer_properties_response()
279 elif answer
== QMessageBox
.Discard
:
280 self
.changed
= set() # avoid asking the user
282 def busy (self
, win
= None):
286 win
.setCursor(Qt
.WaitCursor
)
287 QApplication
.processEvents()
291 def ready (self
, win
= None):
295 win
.setCursor(Qt
.ArrowCursor
)
296 QApplication
.processEvents()
300 def setConnected(self
):
301 connected
= bool(self
.cups
)
303 host
= cups
.getServer()
307 self
.setWindowTitle(i18n("Printer configuration - %1", host
))
310 status_msg
= i18n("Connected to %1", host
)
312 status_msg
= i18n("Not connected")
313 #FIXME do we want a statusbar?
314 #self.statusbarMain.push(self.status_context_id, status_msg)
316 for widget
in (#FIXMEself.btnNewPrinter, self.btnNewClass,
317 #self.new_printer, self.new_class,
318 self
.chkServerBrowse
, self
.chkServerShare
,
319 self
.chkServerRemoteAdmin
,
320 self
.chkServerAllowCancelAll
,
321 self
.chkServerLogDebug
):
322 widget
.setEnabled(connected
)
324 sharing
= self
.chkServerShare
.isChecked ()
325 self
.chkServerShareAny
.setEnabled (sharing
)
328 del self
.server_settings
332 def populateList(self
, start_printer
= None, change_ppd
= False):
333 #FIXMEold_name, old_type = self.getSelectedItem()
342 self
.printers
= cupshelpers
.getPrinters(self
.cups
)
344 # Get default printer.
346 self
.default_printer
= self
.cups
.getDefault ()
347 except AttributeError: # getDefault appeared in pycups-1.9.31
348 # This fetches the list of printers and classes *again*,
349 # just to find out the default printer.
350 dests
= self
.cups
.getDests ()
351 if dests
.has_key ((None,None)):
352 self
.default_printer
= dests
[(None,None)].name
354 self
.default_printer
= None
355 except cups
.IPPError
, (e
, m
):
356 self
.show_IPP_Error(e
, m
)
358 self
.default_printer
= None
361 self
.default_printer
= None
368 for name
, printer
in self
.printers
.iteritems():
370 self
.default_printer
= name
371 self
.servers
.add(printer
.getServer())
374 if printer
.is_class
: remote_classes
.append(name
)
375 else: remote_printers
.append(name
)
377 if printer
.is_class
: local_classes
.append(name
)
378 else: local_printers
.append(name
)
380 local_printers
.sort()
382 remote_printers
.sort()
383 remote_classes
.sort()
385 if (old_name
!= "" and
386 (not old_name
in local_printers
) and
387 (not old_name
in local_classes
) and
388 (not old_name
in remote_printers
) and
389 (not old_name
in remote_classes
)):
390 # The previously selected printer no longer exists.
393 if (self
.default_printer
!= None and
394 start_printer
== None and
396 start_printer
= self
.default_printer
398 if not start_printer
:
399 start_printer
= old_name
404 "_remote_printers" : True,
405 "_remote_classes" : True,
409 # remove old printers/classes
410 iter = self.mainlist.get_iter_first()
411 iter = self.mainlist.iter_next(iter) # step over server settings
413 entry = self.mainlist.get_value(iter, 1)
414 path = self.mainlist.get_path(iter)
415 expanded[entry] = self.tvMainList.row_expanded(path)
416 more_entries = self.mainlist.remove(iter)
417 if not more_entries: break
419 self
.mainlist
.clear()
420 QTreeWidgetItem(self
.mainlist
, ["New Printer", 'New'])
421 self
.settingsItem
= QTreeWidgetItem(self
.mainlist
, ["Server Settings", 'Settings'])
424 for printers
, text
, name
in (
425 (local_printers
, i18n("Local Printers"), "_printers"),
426 (local_classes
, i18n("Local Classes"), "_classes"),
427 (remote_printers
, i18n("Remote Printers"), "_remote_printers"),
428 (remote_classes
, i18n("Remote Classes"), "_remote_classes")):
429 if not printers
: continue
431 #self.mainlist.addTopLevelItem(QTreeWidgetItem(self.mainlist, text))
432 rootTreeItem
= QTreeWidgetItem(self
.mainlist
, [text
, name
])
433 #iter = self.mainlist.append(None, (text, name))
434 #path = self.mainlist.get_path(iter)
436 for printer_name
in printers
:
437 if start_printer
== None:
438 start_printer
= printer_name
439 treeItem
= QTreeWidgetItem(rootTreeItem
, [printer_name
, "Printer"])
440 #p_iter = self.mainlist.append(iter, (printer_name, "Printer"))
441 if printer_name
==start_printer
:
442 treeItem
.setSelected(True)
443 expanded
[name
] = True
445 rootTreeItem
.setExpanded(True)
446 #self.tvMainList.expand_row(path, False)
447 self
.on_tvMainList_cursor_changed()
448 self
.setDataButtonState()
452 self.on_btnChangePPD_clicked (self.btnChangePPD)
458 def on_printer_changed(self
, text
):
459 widget
= self
.sender()
460 if not widget
: #method called as a method not a slot
462 if isinstance(widget
, QCheckBox
):
463 value
= widget
.isChecked()
464 elif isinstance(widget
, QLineEdit
):
465 value
= unicode(widget
.text())
466 elif isinstance(widget
, QRadioButton
):
467 value
= widget
.isChecked()
468 elif isinstance(widget
, QComboBox
):
469 value
= unicode(widget
.currentText())
471 raise ValueError, "Widget type not supported (yet)"
475 self
.entPDescription
: p
.info
,
476 self
.entPLocation
: p
.location
,
477 self
.entPDevice
: p
.device_uri
,
478 self
.chkPEnabled
: p
.enabled
,
479 self
.chkPAccepting
: not p
.rejecting
,
480 self
.chkPShared
: p
.is_shared
,
481 self
.cmbPStartBanner
: p
.job_sheet_start
,
482 self
.cmbPEndBanner
: p
.job_sheet_end
,
483 self
.cmbPErrorPolicy
: p
.error_policy
,
484 self
.cmbPOperationPolicy
: p
.op_policy
,
485 #self.rbtnPAllow: p.default_allow, #FIXME access control tab
488 old_value
= old_values
[widget
]
490 if old_value
== value
:
491 self
.changed
.discard(widget
)
493 self
.changed
.add(widget
)
494 self
.setDataButtonState()
500 # Server side options
502 # set buttons sensitivity
503 def setDataButtonState(self
):
504 try: # Might not be a printer selected
505 possible
= (self
.ppd
and
506 not bool (self
.changed
) and
507 self
.printer
.enabled
and
508 not self
.printer
.rejecting
)
510 self
.btnPrintTestPage
.setEnabled(possible
)
512 commands
= (self
.printer
.type & cups
.CUPS_PRINTER_COMMANDS
) != 0
513 self
.btnSelfTest
.setEnabled(commands
and possible
)
514 self
.btnCleanHeads
.setEnabled(commands
and possible
)
516 debugprint ("exception in setDataButtonState")
519 installablebold
= False
522 debugprint ("Conflicts detected")
523 self
.btnConflict
.show()
524 for option
in self
.conflicts
:
525 if option
.tab_label
== self
.lblPInstallOptions
:
526 installablebold
= True
530 self
.btnConflict
.hide()
531 installabletext
= i18n("Installable Options")
532 optionstext
= i18n("Printer Options")
534 installabletext
= i18nc("Conflicted entry", "<b>%1</b>", installabletext
)
536 optionstext
= i18nc("Conflicted entry", "<b>%1</b>", optionstext
)
537 self
.lblPInstallOptions
.setText(installabletext
)
538 self
.lblPOptions
.setText(optionstext
)
541 store = self.tvPrinterProperties.get_model ()
543 for n in range (self.ntbkPrinter.get_n_pages ()):
544 page = self.ntbkPrinter.get_nth_page (n)
545 label = self.ntbkPrinter.get_tab_label (page)
546 if label == self.lblPInstallOptions:
547 iter = store.get_iter ((n,))
548 store.set_value (iter, 0, installabletext)
549 elif label == self.lblPOptions:
550 iter = store.get_iter ((n,))
551 store.set_value (iter, 0, optionstext)
554 self
.btnPrinterPropertiesApply
.setEnabled(len (self
.changed
) > 0)
555 self
.btnRevert
.setEnabled(len (self
.changed
) > 0)
557 def save_printer(self
, printer
, saveall
=False):
558 class_deleted
= False
562 if not printer
.is_class
and self
.ppd
:
563 self
.getPrinterSettings()
564 if self
.ppd
.nondefaultsMarked() or saveall
:
565 self
.passwd_retry
= False # use cached Passwd
566 self
.cups
.addPrinter(name
, ppd
=self
.ppd
)
573 new_members = self.getCurrentClassMembers(self.tvClassMembers)
575 dialog = gtk.MessageDialog(
576 flags=0, type=gtk.MESSAGE_WARNING,
577 buttons=gtk.BUTTONS_YES_NO,
578 message_format=_("This will delete this class!"))
579 dialog.format_secondary_text(_("Proceed anyway?"))
580 result = dialog.run()
582 if result==gtk.RESPONSE_NO:
587 old_members = printer.class_members[:]
589 for member in new_members:
590 if member in old_members:
591 old_members.remove(member)
593 self.cups.addPrinterToClass(member, name)
594 for member in old_members:
595 self.cups.deletePrinterFromClass(member, name)
598 location
= unicode(self
.entPLocation
.text())
599 info
= unicode(self
.entPDescription
.text())
600 device_uri
= unicode(self
.entPDevice
.text())
601 if device_uri
.find (ellipsis
) != -1:
602 # The URI is sanitized and not editable.
603 device_uri
= printer
.device_uri
605 enabled
= self
.chkPEnabled
.isChecked()
606 accepting
= self
.chkPAccepting
.isChecked()
607 shared
= self
.chkPShared
.isChecked()
609 if info
!=printer
.info
or saveall
:
610 self
.passwd_retry
= False # use cached Passwd
611 self
.cups
.setPrinterInfo(name
, info
)
612 if location
!=printer
.location
or saveall
:
613 self
.passwd_retry
= False # use cached Passwd
614 self
.cups
.setPrinterLocation(name
, location
)
615 if (not printer
.is_class
and
616 (device_uri
!=printer
.device_uri
or saveall
)):
617 self
.passwd_retry
= False # use cached Passwd
618 self
.cups
.setPrinterDevice(name
, device_uri
)
620 if enabled
!= printer
.enabled
or saveall
:
621 self
.passwd_retry
= False # use cached Passwd
622 self
.printer
.setEnabled(enabled
)
623 if accepting
== printer
.rejecting
or saveall
:
624 self
.passwd_retry
= False # use cached Passwd
625 self
.printer
.setAccepting(accepting
)
626 if shared
!= printer
.is_shared
or saveall
:
627 self
.passwd_retry
= False # use cached Passwd
628 self
.printer
.setShared(shared
)
630 job_sheet_start
= unicode(self
.cmbPStartBanner
.currentText())
631 job_sheet_end
= unicode(self
.cmbPEndBanner
.currentText())
632 error_policy
= unicode(self
.cmbPErrorPolicy
.currentText())
633 op_policy
= unicode(self
.cmbPOperationPolicy
.currentText())
635 if (job_sheet_start
!= printer
.job_sheet_start
or
636 job_sheet_end
!= printer
.job_sheet_end
) or saveall
:
637 self
.passwd_retry
= False # use cached Passwd
638 printer
.setJobSheets(job_sheet_start
, job_sheet_end
)
639 if error_policy
!= printer
.error_policy
or saveall
:
640 self
.passwd_retry
= False # use cached Passwd
641 printer
.setErrorPolicy(error_policy
)
642 if op_policy
!= printer
.op_policy
or saveall
:
643 self
.passwd_retry
= False # use cached Passwd
644 printer
.setOperationPolicy(op_policy
)
647 default_allow = self.rbtnPAllow.get_active()
648 except_users = self.getPUsers()
650 if (default_allow != printer.default_allow or
651 except_users != printer.except_users) or saveall:
652 self.passwd_retry = False # use cached Passwd
653 printer.setAccess(default_allow, except_users)
655 for option in printer.attributes:
656 if option not in self.server_side_options:
657 printer.unsetOption(option)
658 for option in self.server_side_options.itervalues():
659 if option.is_changed() or saveall:
660 printer.setOption(option.name, option.get_current_value())
662 except cups
.IPPError
, (e
, s
):
663 self
.show_IPP_Error(e
, s
)
665 self
.changed
= set() # of options
666 if not self
.__dict
__.has_key ("server_settings"):
667 # We can authenticate with the server correctly at this point,
668 # but we have never fetched the server settings to see whether
669 # the server is publishing shared printers. Fetch the settings
670 # now so that we can update the "not published" label if necessary.
672 self
.server_settings
= self
.cups
.adminGetServerSettings()
679 # Update our copy of the printer's settings.
680 printers
= cupshelpers
.getPrinters (self
.cups
)
681 this_printer
= { name
: printers
[name
] }
682 self
.printers
.update (this_printer
)
686 def getPrinterSettings(self
):
687 #self.ppd.markDefaults()
688 for option
in self
.options
.itervalues():
692 def on_btnPrintTestPage_clicked(self
):
693 if self
.test_button_cancels
:
694 jobs
= self
.printer
.testsQueued ()
696 debugprint ("Canceling job %s" % job
)
698 self
.cups
.cancelJob (job
)
699 except cups
.IPPError
, (e
, msg
):
700 self
.show_IPP_Error(e
, msg
)
701 self
.setTestButton (self
.printer
)
704 # if we have a page size specific custom test page, use it;
705 # otherwise use cups' default one
706 custom_testpage
= None
707 opt
= self
.ppd
.findOption ("PageSize")
709 custom_testpage
= os
.path
.join(SYSTEM_CONFIG_PRINTER_DIR
, 'testpage-%s.ps' % opt
.defchoice
.lower())
711 if custom_testpage
and os
.path
.exists(custom_testpage
):
712 debugprint ('Printing custom test page ' + custom_testpage
)
713 job_id
= self
.cups
.printTestPage(self
.printer
.name
,
714 file=custom_testpage
)
716 debugprint ('Printing default test page')
717 job_id
= self
.cups
.printTestPage(self
.printer
.name
)
719 self
.setTestButton (self
.printer
)
720 QMessageBox
.information(self
, i18nc("Test page submitted", "Submitted"), i18n("Test page submitted as "
722 except cups
.IPPError
, (e
, msg
):
723 if (e
== cups
.IPP_NOT_AUTHORIZED
and
724 self
.connect_server
!= 'localhost' and
725 self
.connect_server
[0] != '/'):
726 self
.lblError
.set_markup ('<span size="larger">'+
727 i18n("<b>Not possible</b>") + '</span>\n\n' +
728 i18n("The remote server did not accept "
729 "the print job, most likely "
730 "because the printer is not "
732 self
.ErrorDialog
.set_transient_for (self
.MainWindow
)
733 self
.ErrorDialog
.run ()
734 self
.ErrorDialog
.hide ()
736 self
.show_IPP_Error(e
, msg
)
738 def maintenance_command (self
, command
):
739 (tmpfd
, tmpfname
) = tempfile
.mkstemp ()
740 os
.write (tmpfd
, "#CUPS-COMMAND\n%s\n" % command
)
743 format
= "application/vnd.cups-command"
744 job_id
= self
.cups
.printTestPage (self
.printer
.name
,
747 user
=self
.connect_user
)
748 self
.lblInfo
.set_markup ('<span size="larger">' +
749 i18nc("Maintenance command submitted", "<b>Submitted</b>") + '</span>\n\n' +
750 i18n("Maintenance command submitted as "
752 self
.InfoDialog
.set_transient_for (self
.MainWindow
)
753 self
.InfoDialog
.run ()
754 self
.InfoDialog
.hide ()
755 except cups
.IPPError
, (e
, msg
):
756 if (e
== cups
.IPP_NOT_AUTHORIZED
and
757 self
.printer
.name
!= 'localhost'):
758 self
.lblError
.set_markup ('<span size="larger">'+
759 i18n("<b>Not possible</b>") + '</span>\n\n' +
760 i18n("The remote server did not accept "
761 "the print job, most likely "
762 "because the printer is not "
764 self
.ErrorDialog
.set_transient_for (self
.MainWindow
)
765 self
.ErrorDialog
.run ()
766 self
.ErrorDialog
.hide ()
768 self
.show_IPP_Error(e
, msg
)
771 def on_btnSelfTest_clicked(self
):
772 self
.maintenance_command ("PrintSelfTestPage")
775 def on_btnCleanHeads_clicked(self
):
776 self
.maintenance_command ("Clean all")
778 def fillComboBox(self
, combobox
, values
, value
):
780 for nr
, val
in enumerate(values
):
781 combobox
.addItem(val
)
783 combobox
.setCurrentIndex(nr
)
785 def fillPrinterTab(self
, name
):
786 self
.changed
= set() # of options
787 self
.options
= {} # keyword -> Option object
788 self
.conflicts
= set() # of options
790 printer
= self
.printers
[name
]
791 self
.printer
= printer
792 printer
.getAttributes ()
794 editable
= not self
.printer
.discovered
795 editablePPD
= not self
.printer
.remote
798 self
.ppd
= printer
.getPPD()
799 except cups
.IPPError
, (e
, m
):
800 # Some IPP error other than IPP_NOT_FOUND.
801 self
.show_IPP_Error(e
, m
)
802 # Treat it as a raw queue.
805 # The underlying cupsGetPPD2() function returned NULL without
806 # setting an IPP error, so it'll be something like a failed
808 #FIXME show a dialogue
811 self.lblError.set_markup('<span size="larger">' +
812 _("<b>Error</b>") + '</span>\n\n' +
813 _("There was a problem connecting to "
815 self.ErrorDialog.set_transient_for(self.MainWindow)
816 self.ErrorDialog.run()
817 self.ErrorDialog.hide()
821 for widget
in (self
.entPDescription
, self
.entPLocation
,
823 widget
.setReadOnly(not editable
)
825 for widget
in (self
.btnSelectDevice
, self
.btnChangePPD
):
827 self.chkPEnabled, self.chkPAccepting, self.chkPShared,
828 self.cmbPStartBanner, self.cmbPEndBanner,
829 self.cmbPErrorPolicy, self.cmbPOperationPolicy,
830 self.rbtnPAllow, self.rbtnPDeny, self.tvPUsers,
831 self.entPUser, self.btnPAddUser, self.btnPDelUser):
833 widget
.setEnabled(editable
)
836 self
.entPDescription
.setText(printer
.info
)
837 self
.entPLocation
.setText(printer
.location
)
838 #obsolete self.lblPMakeModel.setText(printer.make_and_model)
839 #obsolete self.lblPState.setText(printer.state_description)
841 uri
= printer
.device_uri
842 if uri
.startswith("smb://"):
844 user
, password
) = SMBURI (uri
=uri
[6:]).separate ()
847 if len (user
) or len (password
):
849 uri
+= SMBURI (group
=group
, host
=host
, share
=share
).get_uri ()
850 self
.entPDevice
.setEnabled(False)
852 self
.entPDevice
.setEnabled(True)
853 self
.entPDevice
.setText(uri
)
854 self
.changed
.discard(self
.entPDevice
)
856 # Hide make/model and Device URI for classes
857 for widget
in (self
.lblPMakeModel2
, self
.lblPMakeModel
,
858 self
.btnChangePPD
, self
.lblPDevice2
,
859 self
.entPDevice
, self
.btnSelectDevice
):
867 self
.btnPMakeDefault
.setEnabled(not printer
.default
)
869 self
.lblPDefault
.setText(i18n("This is the default printer"))
870 elif self
.default_printer
:
871 self
.lblPDefault
.setText(self
.default_printer
)
873 self
.lblPDefault
.setText(i18n("No default printer set."))
875 self
.setTestButton (printer
)
881 self
.chkPEnabled
.setChecked(printer
.enabled
)
882 self
.chkPAccepting
.setChecked(not printer
.rejecting
)
883 self
.chkPShared
.setChecked(printer
.is_shared
)
885 if printer
.is_shared
:
886 flag
= cups
.CUPS_SERVER_SHARE_PRINTERS
887 publishing
= int (self
.server_settings
[flag
])
889 self
.lblNotPublished
.hide()
891 self
.lblNotPublished
.show()
893 self
.lblNotPublished
.hide()
895 self
.lblNotPublished
.hide()
898 self
.cmbPStartBanner
.setEnabled(editable
)
899 self
.cmbPEndBanner
.setEnabled(editable
)
902 self
.cmbPErrorPolicy
.setEnabled(editable
)
903 self
.cmbPOperationPolicy
.setEnabled(editable
)
907 self.rbtnPAllow.set_active(printer.default_allow)
908 self.rbtnPDeny.set_active(not printer.default_allow)
909 self.setPUsers(printer.except_users)
911 self.entPUser.set_text("")
913 # Server side options (Job options)
914 self.server_side_options = {}
915 for option in self.job_options_widgets.values ():
916 if option.name == "media" and self.ppd:
917 # Slightly special case because the 'system default'
918 # (i.e. what you get when you press Reset) depends
919 # on the printer's PageSize.
920 opt = self.ppd.findOption ("PageSize")
922 option.set_default (opt.defchoice)
924 option_editable = editable
926 value = self.printer.attributes[option.name]
931 if self.printer.possible_attributes.has_key (option.name):
932 supported = self.printer.\
933 possible_attributes[option.name][1]
934 # Set the option widget.
935 # In CUPS 1.3.x the orientation-requested-default
936 # attribute may have the value None; this means there
937 # is no value set. This suits our needs here, as None
938 # resets the option to the system default and makes the
939 # Reset button insensitive.
940 option.reinit (value, supported=supported)
942 option.reinit (value)
944 self.server_side_options[option.name] = option
946 option_editable = False
947 self.lblError.set_markup ('<span ' +
949 _("<b>Error</b>") + '</span>\n\n' +
950 _("Option '%s' has value '%s' "
951 "and cannot be edited.") %
952 (option.name, value))
953 self.ErrorDialog.set_transient_for (self.MainWindow)
954 self.ErrorDialog.run()
955 self.ErrorDialog.hide()
956 option.widget.set_sensitive (option_editable)
958 option.button.set_sensitive (False)
959 self.other_job_options = []
960 self.draw_other_job_options (editable=editable)
961 for option in self.printer.attributes.keys ():
962 if self.server_side_options.has_key (option):
965 if self.printer.possible_attributes.has_key (option):
966 supported = self.printer.possible_attributes[option][1]
967 self.add_job_option (option, value=self.printer.attributes[option],
968 supported=supported, is_new=False,
970 self.entNewJobOption.set_text ('')
971 self.entNewJobOption.set_sensitive (editable)
972 self.btnNewJobOption.set_sensitive (False)
975 # remove InstallOptions tab
976 tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
978 self.ntbkPrinter.remove_page(tab_nr)
979 self.fillClassMembers(name, editable)
982 self.fillPrinterOptions(name, editablePPD)
985 self
.changed
= set() # of options
986 self
.updatePrinterProperties ()
987 self
.setDataButtonState()
989 def fillPrinterOptions(self
):
990 return #FIXME TODO options tabs
992 #In Gnome is now on_delete_activate(self, UNUSED):
994 def on_btnDelete_clicked(self
):
995 name
, type = self
.getSelectedItem()
999 if type == "Printer":
1000 message_format
= i18n("Really delete printer %s?")
1002 message_format
= i18n("Really delete class %s?")
1004 cancel
= QMessageBox
.question(self
,"",
1005 unicode(message_format
) % name
,
1006 i18n("&Yes"), i18n("&No"),
1012 self
.cups
.deletePrinter(name
)
1013 except cups
.IPPError
, (e
, msg
):
1014 self
.show_IPP_Error(e
, msg
)
1016 self
.changed
= set()
1018 self
.mainlist
.setCurrentItem(self
.mainlist
.itemAt(0,0))
1020 #in Gnome side is now set_default_printer (self, name):
1022 def on_btnPMakeDefault_clicked(self
):
1024 self
.cups
.setDefault(self
.printer
.name
)
1025 except cups
.IPPError
, (e
, msg
):
1026 self
.show_IPP_Error(e
, msg
)
1029 # Also need to check system-wide lpoptions because that's how
1030 # previous Fedora versions set the default (bug #217395).
1031 (tmpfd
, tmpfname
) = tempfile
.mkstemp ()
1034 resource
= "/admin/conf/lpoptions"
1035 self
.cups
.getFile(resource
, tmpfname
)
1037 except cups
.HTTPError
, (s
,):
1039 os
.remove (tmpfname
)
1043 if s
!= cups
.HTTP_NOT_FOUND
:
1044 self
.show_HTTP_Error(s
)
1048 lines
= file (tmpfname
).readlines ()
1052 if line
.startswith ("Default "):
1053 # This is the system-wide default.
1054 name
= line
.split (' ')[1]
1055 if name
!= self
.printer
.name
:
1056 # Stop it from over-riding the server default.
1057 lines
[i
] = "Dest " + line
[8:]
1062 file (tmpfname
, 'w').writelines (lines
)
1064 self
.cups
.putFile (resource
, tmpfname
)
1065 except cups
.HTTPError
, (s
,):
1066 os
.remove (tmpfname
)
1068 self
.show_HTTP_Error(s
)
1071 # Now reconnect because the server needs to reload.
1075 os
.remove (tmpfname
)
1081 except cups
.HTTPError
, (s
,):
1085 self
.show_HTTP_Error(s
)
1087 ##########################################################################
1089 ##########################################################################
1091 def fillServerTab(self
):
1092 self
.changed
= set()
1094 self
.server_settings
= self
.cups
.adminGetServerSettings()
1095 except cups
.IPPError
, (e
, m
):
1097 self
.show_IPP_Error(e
, m
)
1098 self
.tvMainList
.get_selection().unselect_all()
1099 self
.on_tvMainList_cursor_changed(self
.tvMainList
)
1102 for widget
, setting
in [
1103 (self
.chkServerBrowse
, cups
.CUPS_SERVER_REMOTE_PRINTERS
),
1104 (self
.chkServerShare
, cups
.CUPS_SERVER_SHARE_PRINTERS
),
1105 (self
.chkServerShareAny
, try_CUPS_SERVER_REMOTE_ANY
),
1106 (self
.chkServerRemoteAdmin
, cups
.CUPS_SERVER_REMOTE_ADMIN
),
1107 (self
.chkServerAllowCancelAll
, cups
.CUPS_SERVER_USER_CANCEL_ANY
),
1108 (self
.chkServerLogDebug
, cups
.CUPS_SERVER_DEBUG_LOGGING
),]:
1109 # widget.set_data("setting", setting)
1110 self
.widget_data_setting
[widget
] = setting
1111 self
.disconnect(widget
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
1112 if self
.server_settings
.has_key(setting
):
1113 widget
.setChecked(int(self
.server_settings
[setting
]))
1114 widget
.setEnabled(True)
1116 widget
.setChecked(False)
1117 widget
.setEnabled(False)
1118 self
.connect(widget
, SIGNAL("stateChanged(int)"), self
.on_server_widget_changed
)
1119 self
.setDataButtonState()
1120 # Set sensitivity of 'Allow printing from the Internet'.
1121 self
.on_server_changed (self
.chkServerShare
) # (any will do here)
1123 def on_server_widget_changed(self
, value
):
1124 self
.on_server_changed(self
.sender())
1126 def on_server_changed(self
, widget
):
1127 #setting = widget.get_data("setting")
1128 #print "widget_data_setting " + str(self.widget_data_setting)
1129 #print "widget " + str(widget)
1130 setting
= self
.widget_data_setting
[widget
]
1131 if self
.server_settings
.has_key (setting
):
1132 if str(int(widget
.isChecked())) == self
.server_settings
[setting
]:
1133 self
.changed
.discard(widget
)
1135 self
.changed
.add(widget
)
1137 sharing
= self
.chkServerShare
.isChecked ()
1138 self
.chkServerShareAny
.setEnabled (
1139 sharing
and self
.server_settings
.has_key(try_CUPS_SERVER_REMOTE_ANY
))
1141 self
.setDataButtonState()
1143 def save_serversettings(self
):
1144 setting_dict
= self
.server_settings
.copy()
1145 for widget
, setting
in [
1146 (self
.chkServerBrowse
, cups
.CUPS_SERVER_REMOTE_PRINTERS
),
1147 (self
.chkServerShare
, cups
.CUPS_SERVER_SHARE_PRINTERS
),
1148 (self
.chkServerShareAny
, try_CUPS_SERVER_REMOTE_ANY
),
1149 (self
.chkServerRemoteAdmin
, cups
.CUPS_SERVER_REMOTE_ADMIN
),
1150 (self
.chkServerAllowCancelAll
, cups
.CUPS_SERVER_USER_CANCEL_ANY
),
1151 (self
.chkServerLogDebug
, cups
.CUPS_SERVER_DEBUG_LOGGING
),]:
1152 if not self
.server_settings
.has_key(setting
): continue
1153 setting_dict
[setting
] = str(int(widget
.isChecked()))
1155 self
.cups
.adminSetServerSettings(setting_dict
)
1156 except cups
.IPPError
, (e
, m
):
1157 self
.show_IPP_Error(e
, m
)
1159 except RuntimeError, s
:
1160 self
.show_IPP_Error(None, s
)
1162 self
.changed
= set()
1163 self
.setDataButtonState()
1164 time
.sleep(1) # give the server a chance to process our request
1166 # Now reconnect, in case the server needed to reload.
1169 # Refresh the server settings in case they have changed in the
1172 self
.fillServerTab()
1176 # ====================================================================
1177 # == New Printer Dialog ==============================================
1178 # ====================================================================
1181 def on_new_printer_activate(self
):
1183 self
.newPrinterGUI
.init("printer")
1187 def on_new_class_activate(self
):
1188 self
.newPrinterGUI
.init("class")
1191 def on_btnSelectDevice_clicked(self
):
1192 self
.newPrinterGUI
.init("device")
1195 def on_btnChangePPD_clicked(self
):
1197 self
.newPrinterGUI
.init("ppd")
1200 def checkNPName(self
, name
):
1201 if not name
: return False
1203 for printer
in self
.printers
.values():
1204 if not printer
.discovered
and printer
.name
.lower()==name
:
1208 def makeNameUnique(self
, name
):
1209 """Make a suggested queue name valid and unique."""
1210 name
= name
.replace (" ", "_")
1211 name
= name
.replace ("/", "_")
1212 name
= name
.replace ("#", "_")
1213 if not self
.checkNPName (name
):
1215 while not self
.checkNPName (name
+ str (suffix
)):
1219 name
+= str (suffix
)
1223 ## Watcher interface helpers
1226 def on_btnRevert_clicked(self
):
1227 self
.changed
= set() # avoid asking the user
1228 self
.on_tvMainList_cursor_changed()
1231 def on_btnPrinterPropertiesApply_clicked(self
):
1232 err
= self
.printer_properties_response()
1238 def show_IPP_Error(self
, exception
, message
):
1239 if exception
== cups
.IPP_NOT_AUTHORIZED
:
1240 QMessageBox
.critical(self
, i18n('Not authorized'), i18n('The password may be incorrect.'))
1242 QMessageBox
.critical(self
, i18n('CUPS server error'), i18n("There was an error during the CUPS "\
1243 "operation: '%1'.", message
))
1244 def show_HTTP_Error(self
, status
):
1245 if (status
== cups
.HTTP_UNAUTHORIZED
or
1246 status
== cups
.HTTP_FORBIDDEN
):
1247 QMessageBox
.critical(self
, i18n('Not authorized'),
1248 i18n('The password may be incorrect, or the '
1249 'server may be configured to deny '
1250 'remote administration.'))
1252 if status
== cups
.HTTP_BAD_REQUEST
:
1253 msg
= i18nc("HTTP error", "Bad request")
1254 elif status
== cups
.HTTP_NOT_FOUND
:
1255 msg
= i18nc("HTTP error", "Not found")
1256 elif status
== cups
.HTTP_REQUEST_TIMEOUT
:
1257 msg
= i18nc("HTTP error", "Request timeout")
1258 elif status
== cups
.HTTP_UPGRADE_REQUIRED
:
1259 msg
= i18nc("HTTP error", "Upgrade required")
1260 elif status
== cups
.HTTP_SERVER_ERROR
:
1261 msg
= i18nc("HTTP error", "Server error")
1263 msg
= i18nc("HTTP error", "Not connected")
1265 msg
= i18nc("HTTP error", "status %1", status
)
1267 QMessageBox
.critical(self
, i18n('CUPS server error'), i18n("There was an HTTP error: %1.", msg
))
1269 def getSelectedItem(self
):
1270 return str(self
.mainListSelectedName
).decode ('utf-8'), str(self
.mainListSelectedType
)
1272 items = self.mainlist.selectedItems()
1278 name = str(name).decode ('utf-8')
1279 return name.strip(), type
1282 def reconnect (self
):
1283 """Reconnect to CUPS after the server has reloaded."""
1284 # libcups would handle the reconnection if we just told it to
1285 # do something, for example fetching a list of classes.
1286 # However, our local authentication certificate would be
1287 # invalidated by a server restart, so it is better for us to
1288 # handle the reconnection ourselves.
1294 cups
.setServer(self
.connect_server
)
1295 cups
.setUser(self
.connect_user
)
1299 self
.cups
= cups
.Connection ()
1301 except RuntimeError:
1302 # Connection failed.
1307 self
.passwd_retry
= False
1309 def updatePrinterProperties(self
):
1310 debugprint ("update printer properties")
1311 printer
= self
.printer
1312 self
.lblPMakeModel
.setText(printer
.make_and_model
)
1313 state
= self
.printer_states
.get (printer
.state
, i18nc("Printer state", "Unknown"))
1314 reason
= printer
.other_attributes
.get ('printer-state-message', '')
1315 if len (reason
) > 0:
1316 state
+= ' - ' + reason
1317 self
.lblPState
.setText(state
)
1318 if len (self
.changed
) == 0:
1319 debugprint ("no changes yet: full printer properties update")
1321 self
.chkPEnabled
.setEnabled(printer
.enabled
)
1322 self
.chkPAccepting
.setEnabled(not printer
.rejecting
)
1323 self
.chkPShared
.setEnabled(printer
.is_shared
)
1326 self
.fillComboBox(self
.cmbPStartBanner
,
1327 printer
.job_sheets_supported
,
1328 printer
.job_sheet_start
),
1329 self
.fillComboBox(self
.cmbPEndBanner
, printer
.job_sheets_supported
,
1330 printer
.job_sheet_end
)
1333 self
.fillComboBox(self
.cmbPErrorPolicy
,
1334 printer
.error_policy_supported
,
1335 printer
.error_policy
)
1336 self
.fillComboBox(self
.cmbPOperationPolicy
,
1337 printer
.op_policy_supported
,
1342 self.rbtnPAllow.set_active(printer.default_allow)
1343 self.rbtnPDeny.set_active(not printer.default_allow)
1344 self.setPUsers(printer.except_users)
1347 def setTestButton (self
, printer
):
1348 if printer
.testsQueued ():
1349 self
.test_button_cancels
= True
1350 self
.btnPrintTestPage
.setText(i18n('Cancel Tests'))
1351 self
.btnPrintTestPage
.setEnabled(True)
1353 self
.test_button_cancels
= False
1354 self
.btnPrintTestPage
.setText(i18n('Print Test Page'))
1355 self
.setDataButtonState()
1357 def getCurrentClassMembers(self
, listwidget
):
1358 count
= listwidget
.count()
1360 for i
in range(count
):
1361 result
.append(listwidget
.item(i
).text())
1365 def moveClassMembers(self
, treeview_from
, treeview_to
):
1366 rows
= treeview_from
.selectedItems()
1368 treeview_from
.takeItem(treeview_from
.row(row
))
1369 treeview_to
.addItem(row
)
1374 def cupsPasswdCallback(self
, querystring
):
1376 if self
.passwd_retry
or len(self
.password
) == 0:
1377 waiting
= self
.WaitWindow
.get_property('visible')
1379 self
.WaitWindow
.hide ()
1380 self
.lblPasswordPrompt
.set_label (self
.prompt_primary
+
1382 self
.PasswordDialog
.set_transient_for (self
.MainWindow
)
1383 self
.entPasswd
.grab_focus ()
1385 result
= self
.PasswordDialog
.run()
1386 self
.PasswordDialog
.hide()
1388 self
.WaitWindow
.show ()
1389 while gtk
.events_pending ():
1390 gtk
.main_iteration ()
1391 if result
== gtk
.RESPONSE_OK
:
1392 self
.password
= self
.entPasswd
.get_text()
1395 self
.passwd_retry
= False
1397 self
.passwd_retry
= True
1398 return self
.password
1400 class NewPrinterGUI(QDialog
):
1402 new_printer_device_tabs
= {
1403 "parallel" : 0, # empty tab
1418 ntbkNewPrinterPages
= {
1423 "class-members" : 4,
1424 "downloadable" : -1,
1427 def __init__(self
, mainapp
):
1428 QDialog
.__init
__(self
, mainapp
)
1429 self
.mainapp
= mainapp
1430 self
.language
= mainapp
.language
1431 self
.dialog_mode
= ""
1433 self
.WaitWindow
= QMessageBox(self
.mainapp
)
1434 self
.WaitWindow
.setStandardButtons(QMessageBox
.NoButton
)
1436 if os
.path
.exists("system-config-printer.ui"):
1437 APPDIR
= QDir
.currentPath()
1439 file = KStandardDirs
.locate("appdata", "system-config-printer.ui")
1440 APPDIR
= file.left(file.lastIndexOf("/"))
1442 uic
.loadUi(APPDIR
+ "/" + "new-printer.ui", self
)
1444 self
.connect(self
.tvNPDevices
, SIGNAL("itemSelectionChanged()"), self
.on_tvNPDevices_cursor_changed
)
1445 self
.connect(self
.tvNPMakes
, SIGNAL("itemSelectionChanged()"), self
.on_tvNPMakes_cursor_changed
)
1446 self
.connect(self
.tvNPModels
, SIGNAL("itemSelectionChanged()"), self
.on_tvNPModels_cursor_changed
)
1447 self
.connect(self
.entNPTDevice
, SIGNAL("textEdited(const QString&)"), self
.on_entNPTDevice_changed
)
1448 # self.connect(self.entNPTIPPHostname, SIGNAL("textEdited(const QString&)"), self.on_entNPTIPPHostname_changed)
1449 # self.connect(self.entNPTIPPQueuename, SIGNAL("textEdited(const QString&)"), self.on_entNPTIPPQueuename_changed)
1450 self
.connect(self
.entSMBURI
, SIGNAL("textEdited(const QString&)"), self
.on_entSMBURI_changed
)
1451 self
.rbtnSMBAuthPrompt
.setChecked(True)
1452 self
.on_rbtnSMBAuthSet_toggled(False)
1453 self
.connect(self
.rbtnSMBAuthSet
, SIGNAL("toggled(bool)"), self
.on_rbtnSMBAuthSet_toggled
)
1454 self
.rbtnNPFoomatic
.setChecked(True)
1455 self
.connect(self
.rbtnNPFoomatic
, SIGNAL("toggled(bool)"), self
.on_rbtnNPFoomatic_toggled
)
1456 self
.connect(self
.filechooserPPDButton
, SIGNAL("clicked()"),self
.on_filechooserPPDButton
)
1457 self
.options
= {} # keyword -> Option object
1458 self
.changed
= set()
1459 self
.conflicts
= set()
1462 # Synchronisation objects.
1463 self
.ppds_lock
= thread
.allocate_lock()
1464 self
.devices_lock
= thread
.allocate_lock()
1465 self
.smb_lock
= thread
.allocate_lock()
1466 self
.ipp_lock
= thread
.allocate_lock()
1467 self
.drivers_lock
= thread
.allocate_lock()
1469 #self.connect(self.btnNCAddMember, SIGNAL("clicked()"), self.slot_btnNCAddMember_clicked)
1470 #self.connect(self.btnNCDelMember, SIGNAL("clicked()"), self.slot_btnNCDelMember_clicked)
1473 # share with mainapp
1474 self.WaitWindow = mainapp.WaitWindow
1475 self.lblWait = mainapp.lblWait
1476 self.busy = mainapp.busy
1477 self.ready = mainapp.ready
1478 self.show_IPP_Error = mainapp.show_IPP_Error
1479 self.show_HTTP_Error = mainapp.show_HTTP_Error
1482 # Optionally disable downloadable driver support.
1483 if not config
.DOWNLOADABLE_DRIVER_SUPPORT
:
1484 self
.rbtnNPDownloadableDriverSearch
.setEnabled(False)
1485 self
.downloadableDriverSearchFrame
.hide()
1488 # Set up OpenPrinting widgets.
1489 self.openprinting = openprinting.OpenPrinting ()
1490 self.openprinting_query_handle = None
1491 combobox = self.cmbNPDownloadableDriverFoundPrinters
1492 cell = gtk.CellRendererText()
1493 combobox.pack_start (cell, True)
1494 combobox.add_attribute(cell, 'text', 0)
1497 self.smb_store = gtk.TreeStore (str, # host or share
1499 gobject.TYPE_PYOBJECT, # domain dict
1500 gobject.TYPE_PYOBJECT) # host dict
1501 self.tvSMBBrowser.set_model (self.smb_store)
1502 self.smb_store.set_sort_column_id (0, gtk.SORT_ASCENDING)
1505 col = gtk.TreeViewColumn (_("Share"), gtk.CellRendererText (),
1507 col.set_resizable (True)
1508 col.set_sort_column_id (0)
1509 self.tvSMBBrowser.append_column (col)
1511 col = gtk.TreeViewColumn (_("Comment"), gtk.CellRendererText (),
1513 self.tvSMBBrowser.append_column (col)
1514 slct = self.tvSMBBrowser.get_selection ()
1515 slct.set_select_function (self.smb_select_function)
1517 self.SMBBrowseDialog.set_transient_for(self.NewPrinterWindow)
1520 self.ipp_store = gtk.TreeStore (str, # queue
1522 gobject.TYPE_PYOBJECT) # dict
1523 self.tvIPPBrowser.set_model (self.ipp_store)
1524 self.ipp_store.set_sort_column_id (0, gtk.SORT_ASCENDING)
1527 col = gtk.TreeViewColumn (_("Queue"), gtk.CellRendererText (),
1529 col.set_resizable (True)
1530 col.set_sort_column_id (0)
1531 self.tvIPPBrowser.append_column (col)
1533 col = gtk.TreeViewColumn (_("Location"), gtk.CellRendererText (),
1535 self.tvIPPBrowser.append_column (col)
1536 self.IPPBrowseDialog.set_transient_for(self.NewPrinterWindow)
1538 self.tvNPDriversTooltips = TreeViewTooltips(self.tvNPDrivers, self.NPDriversTooltips)
1540 ppd_filter = gtk.FileFilter()
1541 ppd_filter.set_name(_("PostScript Printer Description files (*.ppd, *.PPD, *.ppd.gz, *.PPD.gz, *.PPD.GZ)"))
1542 ppd_filter.add_pattern("*.ppd")
1543 ppd_filter.add_pattern("*.PPD")
1544 ppd_filter.add_pattern("*.ppd.gz")
1545 ppd_filter.add_pattern("*.PPD.gz")
1546 ppd_filter.add_pattern("*.PPD.GZ")
1547 self.filechooserPPD.add_filter(ppd_filter)
1549 ppd_filter = gtk.FileFilter()
1550 ppd_filter.set_name(_("All files (*)"))
1551 ppd_filter.add_pattern("*")
1552 self.filechooserPPD.add_filter(ppd_filter)
1554 self.xml.signal_autoconnect(self)
1557 #FIXME hide bits which are not yet implemented
1558 self
.btnSMBBrowse
.hide()
1559 self
.btnSMBVerify
.hide()
1560 self
.btnIPPFindQueue
.hide()
1561 self
.btnIPPVerify
.hide()
1562 self
.btnNPTLpdProbe
.hide()
1564 def option_changed(self
, option
):
1565 if option
.is_changed():
1566 self
.changed
.add(option
)
1568 self
.changed
.discard(option
)
1570 if option
.conflicts
:
1571 self
.conflicts
.add(option
)
1573 self
.conflicts
.discard(option
)
1574 self
.setDataButtonState()
1578 def setDataButtonState(self
):
1579 self
.btnNPForward
.setEnabled(not bool(self
.conflicts
))
1581 def init(self
, dialog_mode
):
1582 self
.dialog_mode
= dialog_mode
1583 self
.options
= {} # keyword -> Option object
1584 self
.changed
= set()
1585 self
.conflicts
= set()
1588 combobox = self.cmbNPDownloadableDriverFoundPrinters
1589 combobox.set_model (gtk.ListStore (str, str))
1590 self.entNPDownloadableDriverSearch.set_text ('')
1591 button = self.btnNPDownloadableDriverSearch
1592 label = button.get_children ()[0].get_children ()[0].get_children ()[1]
1593 self.btnNPDownloadableDriverSearch_label = label
1594 label.set_text (_("Search"))
1597 if self
.dialog_mode
== "printer":
1598 self
.setWindowTitle(i18n("New Printer"))
1599 # Start on devices page (1, not 0)
1600 self
.ntbkNewPrinter
.setCurrentIndex(self
.ntbkNewPrinterPages
["device"])
1601 self
.fillDeviceTab()
1602 self
.on_rbtnNPFoomatic_toggled()
1603 # Start fetching information from CUPS in the background
1604 self
.new_printer_PPDs_loaded
= False
1607 elif self
.dialog_mode
== "class":
1608 self
.setWindowTitle(i18n("New Class"))
1609 self
.fillNewClassMembers()
1610 # Start on name page
1611 self
.ntbkNewPrinter
.setCurrentIndex(self
.ntbkNewPrinterPages
["name"])
1612 elif self
.dialog_mode
== "device":
1613 self
.setWindowTitle(i18n("Change Device URI"))
1614 self
.ntbkNewPrinter
.setCurrentIndex(self
.ntbkNewPrinterPages
["device"])
1615 self
.queryDevices ()
1617 self
.fillDeviceTab(self
.mainapp
.printer
.device_uri
)
1618 # Start fetching information from CUPS in the background
1619 self
.new_printer_PPDs_loaded
= False
1621 elif self
.dialog_mode
== "ppd":
1622 self
.setWindowTitle(i18n("Change Driver"))
1623 self
.ntbkNewPrinter
.setCurrentIndex(2)
1624 self
.on_rbtnNPFoomatic_toggled()
1626 self
.auto_model
= ""
1627 ppd
= self
.mainapp
.ppd
1629 attr
= ppd
.findAttr("Manufacturer")
1631 self
.auto_make
= attr
.value
1634 attr
= ppd
.findAttr("ModelName")
1635 if not attr
: attr
= ppd
.findAttr("ShortNickName")
1636 if not attr
: attr
= ppd
.findAttr("NickName")
1638 if attr
.value
.startswith(self
.auto_make
):
1639 self
.auto_model
= attr
.value
[len(self
.auto_make
):].strip ()
1642 self
.auto_model
= attr
.value
.split(" ", 1)[1]
1644 self
.auto_model
= ""
1646 self
.auto_model
= ""
1648 # Special CUPS names for a raw queue.
1649 self
.auto_make
= 'Raw'
1650 self
.auto_model
= 'Queue'
1655 if self
.dialog_mode
in ("printer", "class"):
1656 self
.entNPName
.setText (self
.mainapp
.makeNameUnique(self
.dialog_mode
))
1657 #FIXMEself.entNPName.grab_focus()
1658 for widget
in [self
.entNPLocation
,
1659 self
.entNPDescription
]: #,
1660 #self.entSMBURI, self.entSMBUsername,
1661 #self.entSMBPassword, self.entNPTDirectJetHostname]:
1665 p
= os
.popen ('/bin/hostname', 'r')
1666 hostname
= p
.read ().strip ()
1668 self
.entNPLocation
.setText(hostname
)
1670 nonfatalException ()
1672 self
.entNPTDirectJetPort
.setText('9100')
1678 def queryPPDs(self
):
1679 debugprint ("queryPPDs")
1680 if not self
.ppds_lock
.acquire(0):
1681 debugprint ("queryPPDs: in progress")
1683 debugprint ("Lock acquired for PPDs thread")
1685 thread
.start_new_thread (self
.getPPDs_thread
, (self
.language
[0],))
1686 debugprint ("PPDs thread started")
1688 def getPPDs_thread(self
, language
):
1690 debugprint ("Connecting (PPDs)")
1691 cups
.setServer (self
.mainapp
.connect_server
)
1692 cups
.setUser (self
.mainapp
.connect_user
)
1693 cups
.setPasswordCB (self
.mainapp
.cupsPasswdCallback
)
1694 # cups.setEncryption (...)
1695 c
= cups
.Connection ()
1696 debugprint ("Fetching PPDs")
1697 ppds_dict
= c
.getPPDs()
1698 self
.ppds_result
= cupshelpers
.ppds
.PPDs(ppds_dict
,
1700 debugprint ("Closing connection (PPDs)")
1702 except cups
.IPPError
, (e
, msg
):
1703 self
.ppds_result
= cups
.IPPError (e
, msg
)
1706 self
.ppds_result
= { }
1708 debugprint ("Releasing PPDs lock")
1709 self
.ppds_lock
.release ()
1711 def fetchPPDs(self
, parent
=None):
1712 debugprint ("fetchPPDs")
1716 # Keep the UI refreshed while we wait for the devices to load.
1718 while (self
.ppds_lock
.locked()):
1721 self
.WaitWindow
.setText(i18n('<b>Searching</b>') + '<br /><br />' +
1722 i18n('Searching for drivers'))
1723 self
.WaitWindow
.show ()
1725 QApplication
.processEvents()
1730 self
.WaitWindow
.hide ()
1732 debugprint ("Got PPDs")
1733 result
= self
.ppds_result
# atomic operation
1734 if isinstance (result
, cups
.IPPError
):
1735 # Propagate exception.
1739 def loadPPDs(self
, parent
=None):
1743 self
.ppds
= self
.fetchPPDs (parent
=parent
)
1754 def fillNewClassMembers(self
):
1755 self
.tvNCMembers
.clear()
1756 self
.tvNCNotMembers
.clear()
1757 for printer
in self
.mainapp
.printers
.itervalues():
1758 self
.tvNCNotMembers
.addItem(printer
.name
)
1761 def on_btnNCAddMember_clicked(self
):
1762 self
.mainapp
.moveClassMembers(self
.tvNCNotMembers
, self
.tvNCMembers
)
1763 self
.btnNPApply
.setEnabled(
1764 bool(self
.mainapp
.getCurrentClassMembers(self
.tvNCMembers
)))
1767 def on_btnNCDelMember_clicked(self
):
1768 self
.mainapp
.moveClassMembers(self
.tvNCMembers
, self
.tvNCNotMembers
)
1769 self
.btnNPApply
.setEnabled(
1770 bool(self
.mainapp
.getCurrentClassMembers(self
.tvNCMembers
)))
1773 def on_btnNPBack_clicked(self
):
1777 def on_btnNPForward_clicked(self
):
1780 def nextNPTab(self
, step
=1):
1781 page_nr
= self
.ntbkNewPrinter
.currentIndex()
1783 if self
.dialog_mode
== "class":
1785 order
= [self
.ntbkNewPrinterPages
["name"], self
.ntbkNewPrinterPages
["class-members"]]
1786 elif self
.dialog_mode
== "printer":
1788 if page_nr
== 1: # Device (first page)
1789 # Choose an appropriate name.
1793 name
= self
.device
.id_dict
["MDL"]
1794 name
= self
.mainapp
.makeNameUnique (name
)
1795 self
.entNPName
.setText(name
)
1797 nonfatalException ()
1798 self
.auto_make
, self
.auto_model
= None, None
1799 self
.device
.uri
= self
.getDeviceURI()
1800 if self
.device
.type in ("socket", "lpd", "ipp", "bluetooth"):
1801 host
= self
.getNetworkPrinterMakeModel(self
.device
)
1804 faxuri
= self
.get_hplip_uri_for_network_printer(host
,
1808 dialog
= gtk
.Dialog(self
.device
.info
,
1809 self
.NewPrinterWindow
,
1811 gtk
.DIALOG_DESTROY_WITH_PARENT
,
1812 (i18n("Printer"), 1,
1814 label
= gtk
.Label(i18n("This printer supports both "
1815 "printing and sending faxes. "
1816 "Which functionality should be "
1817 "used for this queue?"))
1818 dialog
.vbox
.pack_start(label
, True, True, 0)
1820 queue_type
= dialog
.run()
1822 if (queue_type
== 2):
1823 self
.device
.id_dict
= \
1824 self
.get_hpfax_device_id(faxuri
)
1825 self
.device
.uri
= faxuri
1826 self
.auto_make
= self
.device
.id_dict
["MFG"]
1827 self
.auto_model
= self
.device
.id_dict
["MDL"]
1828 self
.device
.id = "MFG:" + self
.auto_make
+ \
1829 ";MDL:" + self
.auto_model
+ \
1831 self
.device
.id_dict
["DES"] + ";"
1832 uri
= self
.device
.uri
1833 if uri
and uri
.startswith ("smb://"):
1834 uri
= SMBURI (uri
=uri
[6:]).sanitize_uri ()
1836 # Try to access the PPD, in this case our detected IPP
1837 # printer is a queue on a remote CUPS server which is
1838 # not automatically set up on our local CUPS server
1839 # (for example DNS-SD broadcasted queue from Mac OS X)
1840 self
.remotecupsqueue
= None
1841 res
= re
.search ("ipp://(\S+(:\d+|))/printers/(\S+)", uri
)
1845 conn
= httplib
.HTTPConnection(resg
[0])
1846 conn
.request("GET", "/printers/%s.ppd" % resg
[2])
1847 resp
= conn
.getresponse()
1848 if resp
.status
== 200: self
.remotecupsqueue
= resg
[2]
1850 debugprint("exception in getting remotecupsqueue")
1853 # We also want to fetch the printer-info and
1854 # printer-location attributes, to pre-fill those
1855 # fields for this new queue.
1856 oldserver
= cups
.getServer()
1857 oldport
= cups
.getPort()
1859 cups
.setServer (resg
[0])
1860 if len (resg
[1]) > 0:
1861 cups
.setPort (int (resg
[1]))
1865 c
= cups
.Connection ()
1866 r
= ['printer-info', 'printer-location']
1867 attrs
= c
.getPrinterAttributes (uri
=uri
,
1868 requested_attributes
=r
)
1869 info
= attrs
.get ('printer-info', '')
1870 location
= attrs
.get ('printer-location', '')
1872 self
.entNPDescription
.setText(info
)
1873 if len (location
) > 0:
1874 self
.entNPLocation
.setText(location
)
1876 nonfatalException ()
1878 cups
.setServer (oldserver
)
1879 cups
.setPort (oldport
)
1881 if (not self
.remotecupsqueue
and
1882 not self
.new_printer_PPDs_loaded
):
1885 except cups
.IPPError
, (e
, msg
):
1887 self
.show_IPP_Error(e
, msg
)
1892 self
.new_printer_PPDs_loaded
= True
1896 if self
.remotecupsqueue
:
1897 # We have a remote CUPS queue, let the client queue
1898 # stay raw so that the driver on the server gets used
1901 name
= self
.remotecupsqueue
1902 name
= self
.mainapp
.makeNameUnique (name
)
1903 self
.entNPName
.setText(name
)
1904 elif self
.device
.id:
1905 id_dict
= self
.device
.id_dict
1906 (status
, ppdname
) = self
.ppds
.\
1907 getPPDNameFromDeviceID (id_dict
["MFG"],
1913 (status
, ppdname
) = self
.ppds
.\
1914 getPPDNameFromDeviceID ("Generic",
1920 if ppdname
and not self
.remotecupsqueue
:
1921 ppddict
= self
.ppds
.getInfoFromPPDName (ppdname
)
1922 make_model
= ppddict
['ppd-make-and-model']
1924 cupshelpers
.ppds
.ppdMakeModelSplit (make_model
)
1925 self
.auto_make
= make
1926 self
.auto_model
= model
1928 nonfatalException ()
1929 if not self
.remotecupsqueue
:
1931 elif page_nr
== 3: # Model has been selected
1932 if not self
.device
.id:
1933 # Choose an appropriate name when no Device ID
1934 # is available, based on the model the user has
1937 items
= self
.tvNPModels
.selectedItems()
1938 name
= unicode(items
[0].text())
1939 name
= self
.mainapp
.makeNameUnique (name
)
1940 self
.entNPName
.setText(name
)
1942 nonfatalException ()
1944 ##self.ready (self.NewPrinterWindow)
1945 if self
.remotecupsqueue
:
1947 elif self
.rbtnNPFoomatic
.isChecked():
1948 order
= [1, 2, 3, 6, 0]
1949 elif self
.rbtnNPPPD
.isChecked():
1950 order
= [1, 2, 6, 0]
1952 # Downloadable driver
1953 order
= [1, 2, 7, 6, 0]
1954 elif self
.dialog_mode
== "device":
1956 elif self
.dialog_mode
== "ppd":
1957 self
.rbtnChangePPDasIs
.setChecked(True)
1958 if self
.rbtnNPFoomatic
.isChecked():
1959 order
= [2, 3, 5, 6]
1960 elif self
.rbtnNPPPD
.isChecked():
1963 # Downloadable driver
1964 order
= [2, 7, 5, 6]
1966 next_page_nr
= order
[order
.index(page_nr
)+step
]
1968 # fill Installable Options tab
1969 if next_page_nr
== 6 and step
> 0:
1970 #TODO Prepare Installable Options screen.
1971 self
.ppd
= self
.getNPPPD()
1973 if next_page_nr == 6:
1974 # Prepare Installable Options screen.
1975 if isinstance(self.ppd, cups.PPD):
1976 self.fillNPInstallableOptions()
1978 self.installable_options = None
1979 # Put a label there explaining why the page is empty.
1982 self.fillNPInstallableOptions()
1985 # step over if empty and not in PPD mode
1986 if self.dialog_mode != "ppd" and not self.installable_options:
1987 next_page_nr = order[order.index(next_page_nr)+1]
1989 next_page_nr
= order
[order
.index(next_page_nr
)+1]
1990 self
.installable_options
= False
1991 # Step over empty Installable Options tab
1992 if next_page_nr
== 6 and not self
.installable_options
and step
<0:
1993 next_page_nr
= order
[order
.index(next_page_nr
)-1]
1995 if next_page_nr
== 7: # About to show downloadable drivers
1996 if self
.drivers_lock
.locked ():
1997 # Still searching for drivers.
1998 self
.lblWait
.set_markup ('<span size="larger">' +
1999 i18n('<b>Searching</b>') + '</span>\n\n' +
2000 i18n('Searching for drivers'))
2001 self
.WaitWindow
.set_transient_for (self
.NewPrinterWindow
)
2002 self
.WaitWindow
.show ()
2005 # Keep the UI refreshed while we wait for the drivers
2006 # query to complete.
2007 while self
.drivers_lock
.locked ():
2008 while gtk
.events_pending ():
2009 gtk
.main_iteration ()
2012 self
.ready (self
.NewPrinterWindow
)
2013 self
.WaitWindow
.hide ()
2015 self
.fillDownloadableDrivers()
2017 self
.ntbkNewPrinter
.setCurrentIndex(next_page_nr
)
2021 def setNPButtons(self
):
2022 nr
= self
.ntbkNewPrinter
.currentIndex()
2024 if self
.dialog_mode
== "device":
2025 self
.btnNPBack
.hide()
2026 self
.btnNPForward
.hide()
2027 self
.btnNPApply
.show()
2028 uri
= self
.getDeviceURI ()
2029 self
.btnNPApply
.setEnabled(validDeviceURI (uri
))
2032 if self
.dialog_mode
== "ppd":
2034 if not self
.installable_options
:
2035 # There are no installable options, so this is the
2037 debugprint ("No installable options")
2038 self
.btnNPForward
.hide ()
2039 self
.btnNPApply
.show ()
2041 self
.btnNPForward
.show ()
2042 self
.btnNPApply
.hide ()
2045 self
.btnNPForward
.hide()
2046 self
.btnNPApply
.show()
2049 self
.btnNPForward
.show()
2050 self
.btnNPApply
.hide()
2052 self
.btnNPBack
.hide()
2053 self
.btnNPForward
.show()
2054 downloadable_selected
= False
2055 if self
.rbtnNPDownloadableDriverSearch
.isChecked():
2056 combobox
= self
.cmbNPDownloadableDriverFoundPrinters
2057 iter = combobox
.get_active_iter () #FIXME
2058 if iter and combobox
.get_model ().get_value (iter, 1):
2059 downloadable_selected
= True
2061 self
.btnNPForward
.setEnabled(bool(
2062 self
.rbtnNPFoomatic
.isChecked() or
2063 not self
.filechooserPPD
.text().isEmpty() or
2064 downloadable_selected
))
2067 self
.btnNPBack
.show()
2071 if nr
== 1: # Device
2074 uri
= self
.getDeviceURI ()
2075 valid
= validDeviceURI (uri
)
2078 self
.btnNPForward
.setEnabled(valid
)
2079 self
.btnNPBack
.hide ()
2081 self
.btnNPBack
.show()
2083 self
.btnNPForward
.show()
2084 self
.btnNPApply
.hide()
2087 self
.btnNPBack
.show()
2088 if self
.dialog_mode
== "class":
2089 self
.btnNPForward
.setEnabled(True)
2090 if self
.dialog_mode
== "printer":
2091 self
.btnNPForward
.hide()
2092 self
.btnNPApply
.show()
2093 self
.btnNPApply
.setEnabled(
2094 self
.mainapp
.checkNPName(unicode(self
.entNPName
.text())))
2095 if nr
== 2: # Make/PPD file
2096 downloadable_selected
= False
2097 if self
.rbtnNPDownloadableDriverSearch
.isChecked():
2098 combobox
= self
.cmbNPDownloadableDriverFoundPrinters
2099 iter = combobox
.get_active_iter () #FIXME
2100 if iter and combobox
.get_model ().get_value (iter, 1):
2101 downloadable_selected
= True
2103 self
.btnNPForward
.setEnabled(bool(
2104 self
.rbtnNPFoomatic
.isChecked() or
2105 not self
.filechooserPPD
.text().isEmpty() or
2106 downloadable_selected
))
2108 if nr
== 3: # Model/Driver
2109 iter = self
.tvNPDrivers
.currentItem()
2110 self
.btnNPForward
.setEnabled(bool(iter))
2111 if nr
== 4: # Class Members
2112 self
.btnNPForward
.hide()
2113 self
.btnNPApply
.show()
2114 self
.btnNPApply
.setEnabled(self
.tvNCMembers
.count() > 0)
2115 if nr
== 7: # Downloadable drivers
2116 if self
.ntbkNPDownloadableDriverProperties
.get_current_page() == 1: #FIXME
2117 accepted
= self
.rbtnNPDownloadLicenseYes
.get_active ()
2119 treeview
= self
.tvNPDownloadableDrivers
2120 model
, iter = treeview
.get_selection ().get_selected ()
2121 accepted
= (iter != None)
2123 self
.btnNPForward
.set_sensitive(accepted
)
2125 def on_filechooserPPDButton(self
):
2126 home
= QDir
.homePath()
2128 fd
.setFileMode(fd
.ExistingFile
)
2129 filename
= fd
.getOpenFileName(self
, 'Open PPD file', home
)
2130 self
.filechooserPPD
.setText(filename
)
2131 self
.btnNPForward
.setEnabled(True)
2133 def getDevices_thread(self
):
2135 debugprint ("Connecting (devices)")
2136 cups
.setServer (self
.mainapp
.connect_server
)
2137 #cups.setUser (self.mainapp.connect_user)
2139 cups
.setPasswordCB (self
.mainapp
.cupsPasswdCallback
)
2140 # cups.setEncryption (...)
2141 c
= cups
.Connection ()
2142 debugprint ("Fetching devices")
2143 self
.devices_result
= cupshelpers
.getDevices(c
)
2144 except cups
.IPPError
, (e
, msg
):
2145 self
.devices_result
= cups
.IPPError (e
, msg
)
2147 debugprint ("Exception in getDevices_thread")
2148 self
.devices_result
= {}
2151 debugprint ("Closing connection (devices)")
2156 debugprint ("Releasing devices lock")
2157 self
.devices_lock
.release ()
2160 def queryDevices(self
):
2161 if not self
.devices_lock
.acquire(0):
2162 debugprint ("queryDevices: in progress")
2164 debugprint ("Lock acquired for devices thread")
2166 thread
.start_new_thread (self
.getDevices_thread
, ())
2167 #self.getDevices_thread()
2168 debugprint ("Devices thread started")
2170 def fetchDevices(self
, parent
=None):
2171 debugprint ("fetchDevices")
2172 self
.queryDevices ()
2175 # Keep the UI refreshed while we wait for the devices to load.
2177 while (self
.devices_lock
.locked()):
2180 self
.WaitWindow
.setText (i18n('<b>Searching</b>') + '<br/><br/>' +
2181 i18n('Searching for printers'))
2183 # parent = self.mainapp.MainWindow
2184 #self.WaitWindow.set_transient_for (parent)
2185 #self.WaitWindow.show ()
2186 self
.WaitWindow
.show()
2188 QApplication
.processEvents()
2193 #self.WaitWindow.hide ()
2194 self
.WaitWindow
.hide()
2196 debugprint ("Got devices")
2197 result
= self
.devices_result
# atomic operation
2198 if isinstance (result
, cups
.IPPError
):
2199 # Propagate exception.
2203 def get_hpfax_device_id(self
, faxuri
):
2204 os
.environ
["URI"] = faxuri
2205 cmd
= 'LC_ALL=C DISPLAY= hp-info -d "${URI}"'
2206 debugprint (faxuri
+ ": " + cmd
)
2208 p
= subprocess
.Popen (cmd
, shell
=True,
2209 stdin
=file("/dev/null"),
2210 stdout
=subprocess
.PIPE
,
2211 stderr
=subprocess
.PIPE
)
2212 (stdout
, stderr
) = p
.communicate ()
2214 # Problem executing command.
2217 for line
in stdout
.split ("\n"):
2218 if line
.find ("fax-type") == -1:
2221 res
= re
.search ("(\d+)", line
)
2225 if faxtype
>= 0: break
2229 return cupshelpers
.parseDeviceID ('MFG:HP;MDL:Fax 2;DES:HP Fax 2;')
2231 return cupshelpers
.parseDeviceID ('MFG:HP;MDL:Fax;DES:HP Fax;')
2233 def get_hplip_uri_for_network_printer(self
, host
, mode
):
2234 os
.environ
["HOST"] = host
2235 if mode
== "print": mod
= "-c"
2236 elif mode
== "fax": mod
= "-f"
2238 cmd
= 'hp-makeuri ' + mod
+ ' "${HOST}"'
2239 debugprint (host
+ ": " + cmd
)
2242 p
= subprocess
.Popen (cmd
, shell
=True,
2243 stdin
=file("/dev/null"),
2244 stdout
=subprocess
.PIPE
,
2245 stderr
=subprocess
.PIPE
)
2246 (stdout
, stderr
) = p
.communicate ()
2248 # Problem executing command.
2251 uri
= stdout
.strip ()
2254 def getNetworkPrinterMakeModel(self
, device
):
2255 # Determine host name/IP
2257 s
= device
.uri
.find ("://")
2260 e
= device
.uri
[s
:].find (":")
2261 if e
== -1: e
= device
.uri
[s
:].find ("/")
2262 if e
== -1: e
= device
.uri
[s
:].find ("?")
2263 if e
== -1: e
= len (device
.uri
)
2264 host
= device
.uri
[s
:s
+e
]
2265 # Try to get make and model via SNMP
2267 os
.environ
["HOST"] = host
2268 cmd
= '/usr/lib/cups/backend/snmp "${HOST}"'
2269 debugprint (host
+ ": " + cmd
)
2272 p
= subprocess
.Popen (cmd
, shell
=True,
2273 stdin
=file("/dev/null"),
2274 stdout
=subprocess
.PIPE
,
2275 stderr
=subprocess
.PIPE
)
2276 (stdout
, stderr
) = p
.communicate ()
2278 # Problem executing command.
2282 mm
= re
.sub("^\s*\S+\s+\S+\s+\"", "", stdout
)
2283 mm
= re
.sub("\"\s+.*$", "", mm
)
2284 if mm
and mm
!= "": device
.make_and_model
= mm
2285 # Extract make and model and create a pseudo device ID, so
2286 # that a PPD/driver can be assigned to the device
2287 make_and_model
= None
2288 if len (device
.make_and_model
) > 7:
2289 make_and_model
= device
.make_and_model
2290 elif len (device
.info
) > 7:
2291 make_and_model
= device
.info
2292 make_and_model
= re
.sub("\s*(\(|\d+\.\d+\.\d+\.\d+).*$", "", make_and_model
)
2293 if make_and_model
and not device
.id:
2296 (mk
, md
) = cupshelpers
.ppds
.ppdMakeModelSplit (make_and_model
)
2297 device
.id = "MFG:" + mk
+ ";MDL:" + md
+ ";DES:" + mk
+ " " + md
+ ";"
2298 device
.id_dict
= cupshelpers
.parseDeviceID (device
.id)
2299 # Check whether the device is supported by HPLIP and replace
2300 # its URI by an HPLIP URI
2302 hplipuri
= self
.get_hplip_uri_for_network_printer(host
, "print")
2304 device
.uri
= hplipuri
2305 s
= hplipuri
.find ("/usb/")
2306 if s
== -1: s
= hplipuri
.find ("/par/")
2307 if s
== -1: s
= hplipuri
.find ("/net/")
2310 e
= hplipuri
[s
:].find ("?")
2311 if e
== -1: e
= len (hplipuri
)
2312 mdl
= hplipuri
[s
:s
+e
].replace ("_", " ")
2313 if mdl
.startswith ("hp ") or mdl
.startswith ("HP "):
2315 device
.make_and_model
= "HP " + mdl
2316 device
.id = "MFG:HP;MDL:" + mdl
+ ";DES:HP " + mdl
+ ";"
2317 device
.id_dict
= cupshelpers
.parseDeviceID (device
.id)
2318 # Return the host name/IP for further actions
2321 def fillDeviceTab(self
, current_uri
=None, query
=True):
2324 devices
= self
.fetchDevices()
2325 except cups
.IPPError
, (e
, msg
):
2326 self
.show_IPP_Error(e
, msg
)
2333 if devices
.has_key (current_uri
):
2334 current
= devices
.pop(current_uri
)
2336 current
= cupshelpers
.Device (current_uri
)
2337 current
.info
= "Current device"
2339 self
.devices
= devices
.values()
2341 for device
in self
.devices
:
2342 if device
.type == "usb":
2343 # Find USB URIs with corresponding HPLIP URIs and mark them
2344 # for deleting, so that the user will only get the HPLIP
2345 # URIs for full device support in the list
2347 s
= device
.uri
.find ("?serial=")
2350 e
= device
.uri
[s
:].find ("?")
2351 if e
== -1: e
= len (device
.uri
)
2352 ser
= device
.uri
[s
:s
+e
]
2354 s
= device
.uri
[6:].find ("/")
2357 e
= device
.uri
[s
:].find ("?")
2358 if e
== -1: e
= len (device
.uri
)
2359 mod
= device
.uri
[s
:s
+e
].lower ().replace ("%20", "_")
2360 if mod
.startswith ("hp_"):
2363 for hpdevice
in self
.devices
:
2367 if not uri
.startswith ("hp:"): continue
2369 s
= uri
.find ("?serial=")
2372 e
= uri
[s
:].find ("?")
2373 if e
== -1: e
= len (uri
)
2375 if hpser
!= ser
: continue
2377 if mod
and not (ser
and hpser
):
2378 s
= uri
.find ("/usb/")
2381 e
= uri
[s
:].find ("?")
2382 if e
== -1: e
= len (uri
)
2383 hpmod
= uri
[s
:s
+e
].lower ()
2384 if hpmod
.startswith ("hp_"):
2386 if hpmod
!= mod
: continue
2388 if matchfound
== 1: break
2390 device
.uri
= "delete"
2391 if device
.type == "hal":
2392 # Remove HAL USB URIs, for these printers there are already
2394 if device
.uri
.startswith("hal:///org/freedesktop/Hal/devices/usb_device"):
2395 device
.uri
= "delete"
2396 if device
.type == "socket":
2397 # Remove default port to more easily find duplicate URIs
2398 device
.uri
= device
.uri
.replace (":9100", "")
2400 ## XXX This needs to be moved to *after* the device is
2401 # selected. Looping through all the network printers like
2402 # this is far too slow.
2403 if False and device
.type in ("socket", "lpd", "ipp", "bluetooth"):
2404 host
= self
.getNetworkPrinterMakeModel(device
)
2407 faxuri
= self
.get_hplip_uri_for_network_printer(host
,
2410 self
.devices
.append(cupshelpers
.Device(faxuri
,
2411 **{'device-class' : "direct",
2412 'device-info' : device
.info
+ " HP Fax HPLIP",
2413 'device-device-make-and-model' : "HP Fax",
2414 'device-id' : "MFG:HP;MDL:Fax;DES:HP Fax;"}))
2415 if device
.uri
.startswith ("hp:"):
2417 device
.info
+= (" HPLIP")
2419 nonfatalException ()
2420 # Mark duplicate URIs for deletion
2421 for i
in range (len (self
.devices
)):
2422 for j
in range (len (self
.devices
)):
2424 device1
= self
.devices
[i
]
2425 device2
= self
.devices
[j
]
2426 if device1
.uri
== "delete" or device2
.uri
== "delete":
2428 if device1
.uri
== device2
.uri
:
2429 # Keep the one with the longer (better) device ID
2430 if (not device1
.id):
2431 device1
.uri
= "delete"
2432 elif (not device2
.id):
2433 device2
.uri
= "delete"
2434 elif (len (device1
.id) < len (device2
.id)):
2435 device1
.uri
= "delete"
2437 device2
.uri
= "delete"
2438 self
.devices
= filter(lambda x
: x
.uri
not in ("hp:/no_device_found",
2439 "hpfax:/no_device_found",
2442 "scsi", "http", "delete"),
2446 self
.devices
.append(cupshelpers
.Device('',
2447 **{'device-info' :i18nc("Other device", "Other")}))
2449 current
.info
= i18nc("Current device", "%1 (Current)", current
.info
)
2450 self
.devices
.insert(0, current
)
2451 self
.device
= current
2452 self
.tvNPDevices
.clear()
2454 for device
in self
.devices
:
2455 self
.tvNPDevices
.addItem(device
.info
)
2457 #self.tvNPDevices.get_selection().select_path(0)
2458 self
.tvNPDevices
.setCurrentRow(0)
2459 self
.on_tvNPDevices_cursor_changed()
2461 def on_entNPTDevice_changed(self
, entry
):
2466 def on_entSMBURI_changed (self
, text
):
2468 (group
, host
, share
, user
, password
) = SMBURI (uri
=uri
).separate ()
2470 self
.entSMBUsername
.setText(user
)
2472 self
.entSMBPassword
.setText(password
)
2473 if user
or password
:
2474 uri
= SMBURI (group
=group
, host
=host
, share
=share
).get_uri ()
2475 self
.entSMBURI
.setText(uri
)
2476 self
.rbtnSMBAuthSet
.setChecked(True)
2477 elif unicode(self
.entSMBUsername
.text()) == '':
2478 self
.rbtnSMBAuthPrompt
.setChecked(True)
2480 self
.btnSMBVerify
.setEnabled(bool(uri
))
2482 def on_rbtnSMBAuthSet_toggled(self
, ticked
):
2483 self
.tblSMBAuth
.setEnabled(ticked
)
2485 def on_entNPTIPPHostname_textChanged(self
):
2486 valid
= len (self
.entNPTIPPHostname
.text ()) > 0
2487 self
.btnIPPFindQueue
.setEnabled(valid
)
2488 self
.update_IPP_URI_label ()
2491 def update_IPP_URI_label(self
):
2492 hostname
= unicode(self
.entNPTIPPHostname
.text())
2493 queue
= unicode(self
.entNPTIPPQueuename
.text())
2494 valid
= len (hostname
) > 0 and queue
!= '/printers/'
2497 uri
= "ipp://%s%s" % (hostname
, queue
)
2498 self
.lblIPPURI
.setText(uri
)
2499 self
.lblIPPURI
.show ()
2500 self
.entNPTIPPQueuename
.show ()
2502 self
.lblIPPURI
.hide ()
2504 self
.btnIPPVerify
.setEnabled(valid
)
2505 self
.setNPButtons ()
2507 #FIXME this seems totally different from the Gnome one
2509 def on_btnIPPFindQueue_clicked(self
):
2510 self
.IPPBrowseBox
.clear()
2511 host
= str(self
.entNPTIPPHostname
.text())
2512 cups
.setServer (host
)
2513 printers
= classes
= {}
2515 c
= cups
.Connection()
2516 printers
= c
.getPrinters ()
2517 classes
= c
.getClasses ()
2519 except RuntimeError:
2521 except cups
.IPP_Error
, (e
, msg
):
2524 for printer
, dict in printers
.iteritems ():
2526 self
.IPPBrowseBox
.addItem(printer
)
2527 # store.set_value (iter, 0, printer)
2528 # store.set_value (iter, 1, dict.get ('printer-location', ''))
2529 # store.set_value (iter, 2, dict)
2530 for pclass
, dict in classes
.iteritems ():
2532 # iter = store.append (None)
2533 # store.set_value (iter, 0, pclass)
2534 # store.set_value (iter, 1, dict.get ('printer-location', ''))
2535 # store.set_value (iter, 2, dict)
2537 if len (printers
) + len (classes
) == 0:
2538 # Display 'No queues' dialog
2539 QMessageBox
.information(self
, i18n("No queues"),i18n("There are no queues available."))
2541 def on_tvNPDevices_cursor_changed(self
):
2542 device
= self
.devices
[self
.tvNPDevices
.currentRow()]
2543 self
.device
= device
2544 self
.lblNPDeviceDescription
.setText('')
2545 page
= self
.new_printer_device_tabs
.get(device
.type, 1)
2546 self
.ntbkNPType
.setCurrentIndex(page
)
2549 url
= device
.uri
.split(":", 1)[-1]
2551 # This is the "no options" page, with just a label to describe
2552 # the selected device.
2553 if device
.type == "parallel":
2554 text
= i18n("A printer connected to the parallel port.")
2555 elif device
.type == "usb":
2556 text
= i18n("A printer connected to a USB port.")
2557 elif device
.type == "hp":
2558 text
= i18n("HPLIP software driving a printer, "
2559 "or the printer function of a multi-function device.")
2560 elif device
.type == "hpfax":
2561 text
= i18n("HPLIP software driving a fax machine, "
2562 "or the fax function of a multi-function device.")
2563 elif device
.type == "hal":
2564 text
= i18n("Local printer detected by the "
2565 "Hardware Abstraction Layer (HAL).")
2569 self
.lblNPDeviceDescription
.setText(text
)
2570 elif device
.type=="socket":
2571 if device
.uri
.startswith ("socket"):
2572 host
= device
.uri
[9:]
2575 port
= int (host
[i
+ 1:])
2580 self
.entNPTDirectJetHostname
.setText(host
)
2581 self
.entNPTDirectJetPort
.setText(str (port
))
2582 elif device
.type=="serial":
2583 if not device
.is_class
:
2584 options
= device
.uri
.split("?")[1]
2585 options
= options
.split("+")
2587 for option
in options
:
2588 name
, value
= option
.split("=")
2589 option_dict
[name
] = value
2591 for widget
, name
, optionvalues
in (
2592 (self
.cmbNPTSerialBaud
, "baud", None),
2593 (self
.cmbNPTSerialBits
, "bits", None),
2594 (self
.cmbNPTSerialParity
, "parity",
2595 ["none", "odd", "even"]),
2596 (self
.cmbNPTSerialFlow
, "flow",
2597 ["none", "soft", "hard", "hard"])):
2598 if option_dict
.has_key(name
): # option given in URI?
2599 if optionvalues
is None: # use text in widget
2600 model
= widget
.get_model()
2601 iter = model
.get_iter_first()
2604 value
= model
.get(iter,0)[0]
2605 if value
== option_dict
[name
]:
2606 widget
.set_active(nr
)
2608 iter = model
.iter_next(iter)
2610 else: # use optionvalues
2611 nr
= optionvalues
.index(
2613 widget
.set_active(nr
+1) # compensate "Default"
2615 widget
.set_active(0)
2617 # XXX FILL TABS FOR VALID DEVICE URIs
2618 elif device
.type in ("ipp", "http"):
2619 if (device
.uri
.startswith ("ipp:") or
2620 device
.uri
.startswith ("http:")):
2621 match
= re
.match ("(ipp|https?)://([^/]+)(.*)", device
.uri
)
2623 server
= match
.group (2)
2624 printer
= match
.group (3)
2629 self
.entNPTIPPHostname
.setText(server
)
2630 self
.entNPTIPPQueuename
.setText(printer
)
2631 self
.lblIPPURI
.setText(device
.uri
)
2632 self
.lblIPPURI
.show()
2633 self
.entNPTIPPQueuename
.show()
2635 self
.entNPTIPPHostname
.setText('')
2636 self
.entNPTIPPQueuename
.setText('/printers/')
2637 self
.entNPTIPPQueuename
.show()
2638 self
.lblIPPURI
.hide()
2639 elif device
.type=="lpd":
2640 if device
.uri
.startswith ("lpd"):
2641 host
= device
.uri
[6:]
2644 printer
= host
[i
+ 1:]
2648 self
.cmbentNPTLpdHost
.addItem(host
)
2649 self
.cmbentNPTLpdQueue
.addItem(printer
)
2650 elif device
.uri
== "lpd":
2652 elif device
.uri
== "smb":
2653 self
.entSMBURI
.setText('')
2654 self
.btnSMBVerify
.setEnabled(False)
2655 elif device
.type == "smb":
2656 self
.entSMBUsername
.setText('')
2657 self
.entSMBPassword
.setText('')
2658 self
.entSMBURI
.setText(device
.uri
[6:])
2659 self
.btnSMBVerify
.setEnabled(True)
2661 self
.entNPTDevice
.setText(device
.uri
)
2665 def getDeviceURI(self
):
2666 type = self
.device
.type
2667 if type == "socket": # DirectJet
2668 host
= unicode(self
.entNPTDirectJetHostname
.text())
2669 port
= unicode(self
.entNPTDirectJetPort
.text())
2670 device
= "socket://" + host
2672 device
= device
+ ':' + port
2673 elif type in ("http", "ipp"): # IPP
2674 if self
.lblIPPURI
.isVisible
:
2675 device
= unicode(self
.lblIPPURI
.text())
2678 elif type == "lpd": # LPD
2679 host
= unicode(self
.cmbentNPTLpdHost
.currentText())
2680 printer
= unicode(self
.cmbentNPTLpdQueue
.currentText())
2681 device
= "lpd://" + host
2683 device
= device
+ "/" + printer
2684 elif type == "parallel": # Parallel
2685 device
= self
.device
.uri
2686 elif type == "scsi": # SCSII
2688 elif type == "serial": # Serial
2690 for widget
, name
, optionvalues
in (
2691 (self
.cmbNPTSerialBaud
, "baud", None),
2692 (self
.cmbNPTSerialBits
, "bits", None),
2693 (self
.cmbNPTSerialParity
, "parity",
2694 ("none", "odd", "even")),
2695 (self
.cmbNPTSerialFlow
, "flow",
2696 ("none", "soft", "hard", "hard"))):
2697 nr
= widget
.get_active()
2699 if optionvalues
is not None:
2700 option
= optionvalues
[nr
-1]
2702 option
= widget
.get_active_text()
2703 options
.append(name
+ "=" + option
)
2704 options
= "+".join(options
)
2705 device
= self
.device
.uri
.split("?")[0] #"serial:/dev/ttyS%s"
2707 device
= device
+ "?" + options
2709 uri
= unicode(self
.entSMBURI
.text())
2710 (group
, host
, share
, u
, p
) = SMBURI (uri
=uri
).separate ()
2713 if self
.rbtnSMBAuthSet
.isChecked():
2714 user
= unicode(self
.entSMBUsername
.text())
2715 password
= unicode(self
.entSMBPassword
.text())
2716 uri
= SMBURI (group
=group
, host
=host
, share
=share
,
2717 user
=user
, password
=password
).get_uri ()
2718 device
= "smb://" + uri
2719 elif not self
.device
.is_class
:
2720 device
= self
.device
.uri
2722 device
= str(self
.entNPTDevice
.text())
2726 if nr
== self
.ntbkNewPrinterPages
["device"]: # Device
2729 uri
= self
.getDeviceURI ()
2730 valid
= validDeviceURI (uri
)
2732 debugprint("exception in getDeviceURI()")
2734 self
.btnNPForward
.setEnabled(valid
)
2735 self
.btnNPBack
.hide ()
2737 self
.btnNPBack
.show()
2739 self
.btnNPForward
.show()
2740 self
.btnNPApply
.hide()
2742 if nr
== self
.ntbkNewPrinterPages
["name"]: # Name
2743 self
.btnNPBack
.show()
2744 if self
.dialog_mode
== "printer":
2745 self
.btnNPForward
.hide()
2746 self
.btnNPApply
.show()
2747 self
.btnNPApply
.setEnabled(
2748 self
.mainapp
.checkNPName(self
.entNPName
.getText()))
2749 if nr
== self
.ntbkNewPrinterPages
["make"]: # Make/PPD file
2750 downloadable_selected
= False
2751 if self
.rbtnNPDownloadableDriverSearch
.get_active ():
2752 combobox
= self
.cmbNPDownloadableDriverFoundPrinters
2753 iter = combobox
.get_active_iter ()
2754 if iter and combobox
.get_model ().get_value (iter, 1):
2755 downloadable_selected
= True
2757 self
.btnNPForward
.setEnabled(bool(
2758 self
.rbtnNPFoomatic
.get_active() or
2759 not self
.filechooserPPD
.text().isEmpty() or
2760 downloadable_selected
))
2761 if nr
== self
.ntbkNewPrinterPages
["model"]: # Model/Driver
2762 model
, iter = self
.tvNPDrivers
.get_selection().get_selected()
2763 self
.btnNPForward
.set_sensitive(bool(iter))
2764 if nr
== self
.ntbkNewPrinterPages
["class-members"]: # Class Members
2765 self
.btnNPForward
.hide()
2766 self
.btnNPApply
.show()
2767 self
.btnNPApply
.setEnabled(
2768 bool(self
.mainapp
.getCurrentClassMembers(self
.tvNCMembers
)))
2769 if nr
== self
.ntbkNewPrinterPages
["downloadable"]: # Downloadable drivers
2770 if self
.ntbkNPDownloadableDriverProperties
.get_current_page() == 1:
2771 accepted
= self
.rbtnNPDownloadLicenseYes
.get_active ()
2775 self
.btnNPForward
.set_sensitive(accepted
)
2779 def on_rbtnNPFoomatic_toggled(self
):
2780 rbtn1
= self
.rbtnNPFoomatic
.isChecked()
2781 rbtn2
= self
.rbtnNPPPD
.isChecked()
2782 rbtn3
= self
.rbtnNPDownloadableDriverSearch
.isChecked()
2783 self
.tvNPMakes
.setEnabled(rbtn1
)
2784 self
.filechooserPPD
.setEnabled(rbtn2
)
2787 if not rbtn3 and self.openprinting_query_handle:
2788 # Need to cancel a search in progress.
2789 self.openprinting.cancelOperation (self.openprinting_query_handle)
2790 self.openprinting_query_handle = None
2791 self.btnNPDownloadableDriverSearch_label.setText(_("Search"))
2792 # Clear printer list.
2793 self.cmbNPDownloadableDriverFoundPrinters.clear()
2796 for widget
in [self
.entNPDownloadableDriverSearch
,
2797 self
.cmbNPDownloadableDriverFoundPrinters
]:
2798 widget
.setEnabled(rbtn3
)
2799 self
.btnNPDownloadableDriverSearch
.\
2800 setEnabled(rbtn3
and (self
.openprinting_query_handle
== None))
2806 def fillMakeList(self
):
2807 makes
= self
.ppds
.getMakes()
2808 self
.tvNPMakes
.clear()
2812 self
.tvNPMakes
.addItem(make
)
2814 if make
==self
.auto_make
:
2815 self
.tvNPMakes
.setCurrentRow(index
-1)
2818 self
.on_tvNPMakes_cursor_changed()
2820 def on_tvNPMakes_cursor_changed(self
):
2821 items
= self
.tvNPMakes
.selectedItems()
2823 self
.NPMake
= unicode(items
[0].text())
2824 self
.fillModelList()
2826 def fillModelList(self
):
2827 models
= self
.ppds
.getModels(self
.NPMake
)
2828 self
.tvNPModels
.clear()
2832 for pmodel
in models
:
2833 self
.tvNPModels
.addItem(pmodel
)
2834 if self
.NPMake
==self
.auto_make
and pmodel
==self
.auto_model
:
2835 self
.tvNPModels
.setCurrentRow(index
)
2839 self
.tvNPModels
.setCurrentRow(0)
2840 ##self.tvNPModels.columns_autosize()
2841 self
.on_tvNPModels_cursor_changed()
2843 def fillDriverList(self
, pmake
, pmodel
):
2844 self
.NPModel
= pmodel
2845 self
.tvNPDrivers
.clear()
2847 ppds
= self
.ppds
.getInfoFromModel(pmake
, pmodel
)
2849 self
.NPDrivers
= self
.ppds
.orderPPDNamesByPreference(ppds
.keys())
2850 for i
in range (len(self
.NPDrivers
)):
2851 ppd
= ppds
[self
.NPDrivers
[i
]]
2852 driver
= ppd
["ppd-make-and-model"]
2853 driver
= driver
.replace(" (recommended)", "")
2856 lpostfix
= " [%s]" % ppd
["ppd-natural-language"]
2862 self
.tvNPDrivers
.addItem(i18nc("Recommended driver", "%1 (recommended)", driver
))
2863 self
.tvNPDrivers
.setCurrentRow(0)
2865 self
.tvNPDrivers
.addItem(driver
)
2866 ##self.tvNPDrivers.columns_autosize()
2868 def on_tvNPModels_cursor_changed(self
):
2869 items
= self
.tvNPModels
.selectedItems()
2871 pmodel
= unicode(items
[0].text())
2872 self
.fillDriverList(self
.NPMake
, pmodel
)
2876 if self
.rbtnNPFoomatic
.isChecked():
2877 #items = self.tvNPDrivers.selectedItems()
2878 #nr = unicode(items[0])
2879 nr
= self
.tvNPDrivers
.currentRow()
2880 ppd
= self
.NPDrivers
[nr
]
2881 elif self
.rbtnNPPPD
.isChecked():
2882 ppd
= cups
.PPD(unicode(self
.filechooserPPD
.text()))
2885 # PPD of the driver downloaded from OpenPrinting XXX
2886 treeview = self.tvNPDownloadableDrivers
2887 model, iter = treeview.get_selection ().get_selected ()
2888 driver = model.get_value (iter, 1)
2889 if driver.has_key ('ppds'):
2890 # Only need to download a PPD.
2891 file_to_download = driver
2896 except RuntimeError, e
:
2897 if self
.rbtnNPFoomatic
.isChecked():
2898 # Foomatic database problem of some sort.
2899 err_title
= i18n('Database error')
2900 model
, iter = (self
.tvNPDrivers
.get_selection().
2902 nr
= model
.get_path(iter)[0]
2903 driver
= self
.NPDrivers
[nr
]
2904 if driver
.startswith ("gutenprint"):
2905 # This printer references some XML that is not
2906 # installed by default. Point the user at the
2907 # package they need to install.
2908 err
= i18n("You will need to install the '%1' package "
2909 "in order to use this driver.",
2910 "gutenprint-foomatic")
2912 err
= i18n("The '%1' driver cannot be "
2913 "used with printer '%2 %3'.", driver
, self
.NPMake
, self
.NPModel
)
2914 elif self
.rbtnNPPPD
.isChecked():
2915 # This error came from trying to open the PPD file.
2916 err_title
= i18n('PPD error')
2917 filename
= self
.filechooserPPD
.text()
2918 err
= i18n('Failed to read PPD file. Possible reason '
2920 os
.environ
["PPD"] = filename
2921 # We want this to be in the current natural language,
2922 # so we intentionally don't set LC_ALL=C here.
2923 p
= os
.popen ('/usr/bin/cupstestppd -rvv "$PPD"', 'r')
2924 output
= p
.readlines ()
2926 err
+= reduce (lambda x
, y
: x
+ y
, output
)
2928 # Failed to get PPD downloaded from OpenPrinting XXX
2929 err_title
= i18n('Downloadable drivers')
2930 err_text
= i18n("Support for downloadable "
2931 "drivers is not yet completed.")
2933 error_text
= ('<span size="larger">' +
2934 i18nc("Error title", "<b>%1</b>", err_title
) + '</span>\n\n' + err
)
2935 QMessageBox
.critical(self
, err_title
, error_text
)
2938 if isinstance(ppd
, str) or isinstance(ppd
, unicode):
2941 f
= self
.mainapp
.cups
.getServerPPD(ppd
)
2944 except AttributeError:
2946 debugprint ("pycups function getServerPPD not available: never mind")
2947 except RuntimeError:
2949 debugprint ("libcups from CUPS 1.3 not available: never mind")
2950 except cups
.IPPError
:
2952 debugprint ("CUPS 1.3 server not available: never mind")
2956 # Create new Printer
2958 def on_btnNPApply_clicked(self
):
2959 if self
.dialog_mode
in ("class", "printer"):
2960 name
= unicode(self
.entNPName
.text())
2961 location
= unicode(self
.entNPLocation
.text())
2962 info
= unicode(self
.entNPDescription
.text())
2964 name
= self
.mainapp
.printer
.name
2966 #replace any whitespace in printer name with underscore otherwise
2967 #CUPS throws an error
2968 name
= name
.replace(" ", "_")
2970 # Whether to check for missing drivers.
2975 if self
.dialog_mode
=="class":
2976 members
= self
.mainapp
.getCurrentClassMembers(self
.tvNCMembers
)
2978 for member
in members
:
2979 self
.passwd_retry
= False # use cached Passwd
2980 self
.mainapp
.cups
.addPrinterToClass(str(member
), name
)
2981 except cups
.IPPError
, (e
, msg
):
2982 self
.show_IPP_Error(e
, msg
)
2984 elif self
.dialog_mode
=="printer":
2985 self
.device
.uri
= unicode(self
.device
.uri
)
2988 uri
= self
.device
.uri
2990 uri
= self
.getDeviceURI()
2991 if not self
.ppd
: # XXX needed?
2992 # Go back to previous page to re-select driver.
2996 # write Installable Options to ppd
2997 for option
in self
.options
.itervalues():
3001 self
.WaitWindow
.setText(i18n('<b>Adding</b>') + '<br /><br />' +
3002 i18n('Adding printer'))
3003 #self.WaitWindow.set_transient_for (self.NewPrinterWindow)
3004 self
.WaitWindow
.show ()
3005 QApplication
.processEvents()
3007 self
.passwd_retry
= False # use cached Passwd
3008 if isinstance(ppd
, str) or isinstance(ppd
, unicode):
3009 self
.mainapp
.cups
.addPrinter(name
, ppdname
=ppd
,
3010 device
=uri
, info
=info
, location
=location
)
3012 elif ppd
is None: # raw queue
3013 self
.mainapp
.cups
.addPrinter(name
, device
=uri
,
3014 info
=info
, location
=location
)
3016 cupshelpers
.setPPDPageSize(ppd
, self
.language
[0])
3017 self
.mainapp
.cups
.addPrinter(name
, ppd
=ppd
,
3018 device
=uri
, info
=info
, location
=location
)
3021 cupshelpers
.activateNewPrinter (self
.mainapp
.cups
, name
)
3022 except cups
.IPPError
, (e
, msg
):
3024 self
.WaitWindow
.hide ()
3025 self
.show_IPP_Error(e
, msg
)
3028 ##self.ready (self.NewPrinterWindow)
3029 self
.WaitWindow
.hide ()
3031 self
.WaitWindow
.hide ()
3032 ##self.ready (self.NewPrinterWindow)
3034 if self
.dialog_mode
in ("class", "printer"):
3036 self
.passwd_retry
= False # use cached Passwd
3037 self
.mainapp
.cups
.setPrinterLocation(name
, location
)
3038 self
.passwd_retry
= False # use cached Passwd
3039 self
.mainapp
.cups
.setPrinterInfo(name
, info
)
3040 except cups
.IPPError
, (e
, msg
):
3041 self
.show_IPP_Error(e
, msg
)
3043 elif self
.dialog_mode
== "device":
3045 uri
= self
.getDeviceURI()
3046 self
.mainapp
.cups
.addPrinter(name
, device
=uri
)
3047 except cups
.IPPError
, (e
, msg
):
3048 self
.show_IPP_Error(e
, msg
)
3050 elif self
.dialog_mode
== "ppd":
3052 ppd
= self
.ppd
= self
.getNPPPD()
3054 # Go back to previous page to re-select driver.
3058 # set ppd on server and retrieve it
3059 # cups doesn't offer a way to just download a ppd ;(=
3061 if isinstance(ppd
, str) or isinstance(ppd
, unicode):
3062 if self
.rbtnChangePPDasIs
.isChecked():
3063 # To use the PPD as-is we need to prevent CUPS copying
3064 # the old options over. Do this by setting it to a
3065 # raw queue (no PPD) first.
3067 self
.mainapp
.cups
.addPrinter(name
, ppdname
='raw')
3068 except cups
.IPPError
, (e
, msg
):
3069 self
.show_IPP_Error(e
, msg
)
3071 self
.mainapp
.cups
.addPrinter(name
, ppdname
=ppd
)
3072 except cups
.IPPError
, (e
, msg
):
3073 self
.show_IPP_Error(e
, msg
)
3077 filename
= self
.mainapp
.cups
.getPPD(name
)
3078 ppd
= cups
.PPD(filename
)
3080 except cups
.IPPError
, (e
, msg
):
3081 if e
== cups
.IPP_NOT_FOUND
:
3084 self
.show_IPP_Error(e
, msg
)
3087 # We have an actual PPD to upload, not just a name.
3088 if not self
.rbtnChangePPDasIs
.isChecked():
3089 cupshelpers
.copyPPDOptions(self
.mainapp
.ppd
, ppd
) # XXX
3091 # write Installable Options to ppd
3092 for option
in self
.options
.itervalues():
3094 cupshelpers
.setPPDPageSize(ppd
, self
.language
[0])
3097 self
.mainapp
.cups
.addPrinter(name
, ppd
=ppd
)
3098 except cups
.IPPError
, (e
, msg
):
3099 self
.show_IPP_Error(e
, msg
)
3106 self
.mainapp
.populateList(start_printer
=name
)
3109 self
.checkDriverExists (name
, ppd
=checkppd
)
3113 # Also check to see whether the media option has become
3114 # invalid. This can happen if it had previously been
3115 # explicitly set to a page size that is not offered with
3116 # the new PPD (see bug #441836).
3119 option = self.mainapp.server_side_options['media']
3120 if option.get_current_value () == None:
3121 debugprint ("Invalid media option: resetting")
3123 self.mainapp.changed.add (option)
3124 self.mainapp.save_printer (self.mainapp.printer)
3128 print "exception in check to see whether the media option has become invalid"
3132 def show_IPP_Error(self
, exception
, message
):
3133 if exception
== cups
.IPP_NOT_AUTHORIZED
:
3134 QMessageBox
.critical(self
, i18n('Not authorized'), i18n('The password may be incorrect.'))
3136 QMessageBox
.critical(self
, i18n('CUPS server error'), i18n("There was an error during the CUPS "\
3137 "operation: '%1'.", message
))
3139 def checkDriverExists(self
, name
, ppd
=None):
3140 """Check that the driver for an existing queue actually
3141 exists, and prompt to install the appropriate package
3144 ppd: cups.PPD object, if already created"""
3146 # Is this queue on the local machine? If not, we can't check
3148 server
= cups
.getServer ()
3149 if not (server
== 'localhost' or server
== '127.0.0.1' or
3150 server
== '::1' or server
[0] == '/'):
3153 # Fetch the PPD if we haven't already.
3156 filename
= self
.mainapp
.cups
.getPPD(name
)
3157 except cups
.IPPError
, (e
, msg
):
3158 if e
== cups
.IPP_NOT_FOUND
:
3159 # This is a raw queue. Nothing to check.
3162 self
.show_IPP_Error(e
, msg
)
3165 ppd
= cups
.PPD(filename
)
3168 (pkgs
, exes
) = cupshelpers
.missingPackagesAndExecutables (ppd
)
3169 if len (pkgs
) > 0 or len (exes
) > 0:
3170 # We didn't find a necessary executable. Complain.
3171 install
= "/usr/bin/system-install-packages"
3172 if len (pkgs
) > 0 and os
.access (install
, os
.X_OK
):
3174 install_text
= ('<span weight="bold" size="larger">' +
3175 i18n('Install driver') + '</span>\n\n' +
3176 i18n("Printer '%1' requires the %2 package but "
3177 "it is not currently installed.", name
, pkg
))
3178 dialog
= self
.InstallDialog
3179 self
.lblInstall
.set_markup(install_text
)
3181 error_text
= ('<span weight="bold" size="larger">' +
3182 i18n('Missing driver') + '</span>\n\n' +
3183 i18n("Printer '%1' requires the '%2' program but "
3184 "it is not currently installed. Please "
3185 "install it before using this printer.", name
, (exes
+ pkgs
)[0]))
3186 QMessageBox
.error(self
, "", error_text
)
3189 if pkg and response == gtk.RESPONSE_OK:
3190 # Install the package.
3191 def wait_child (sig, stack):
3192 (pid, status) = os.wait ()
3194 signal.signal (signal.SIGCHLD, wait_child)
3199 os.execv (install, [install, pkg])
3204 pass # should handle error
3208 def on_entNPTIPPQueuename_textChanged(self
, ent
):
3209 self
.update_IPP_URI_label ()
3212 def on_IPPBrowseBox_currentTextChanged(self
, text
):
3213 self
.update_IPP_URI_label()
3215 #FIXME not in gnome?
3217 def on_btnNPCancel_clicked(self
):
3219 #end of class NewPrinterGUI
3221 if __name__
== "__main__":
3222 """start the application"""
3224 appName
= "system-config-printer-kde"
3225 catalogue
= "system-config-printer-kde"
3226 programName
= ki18n("System Config Printer KDE")
3228 description
= ki18n("Printer configuration tool")
3229 license
= KAboutData
.License_GPL
3230 copyright
= ki18n("2007 Tim Waugh, Red Hat Inc, 2007-2008 Canonical Ltd")
3231 text
= KLocalizedString()
3232 homePage
= "https://launchpad.net/system-config-printer"
3235 aboutData
= KAboutData (appName
, catalogue
, programName
, version
, description
,
3236 license
, copyright
, text
, homePage
, bugEmail
)
3238 aboutData
.addAuthor(ki18n("Jonathan Riddell"), ki18n("Author"))
3239 aboutData
.addAuthor(ki18n("Tim Waugh/Red Hat"), ki18n("System Config Printer Author"))
3241 options
= KCmdLineOptions()
3243 KCmdLineArgs
.init(sys
.argv
, aboutData
)
3244 KCmdLineArgs
.addCmdLineOptions(options
)
3246 app
= KApplication()
3247 args
= KCmdLineArgs
.parsedArgs()
3250 sys
.exit(app
.exec_())