SVN_SILENT made messages (.desktop file)
[kdeadmin.git] / system-config-printer-kde / system-config-printer-kde.py
blob33d46fa7508939d055a2e6be5bea5d1d7d2994da
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
4 #############################################################################
5 ##
6 ## Copyright (C) 2007 Canonical Ltd
7 ## Author: Jonathan Riddell <jriddell@ubuntu.com>
8 ##
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
29 import locale
31 import sys, os, time, traceback, re, tempfile, httplib
32 #tempfile
33 import thread
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 *
42 from PyQt4 import uic
44 from PyKDE4.kdecore import *
45 from PyKDE4.kdeui import *
47 #use _() to keep code the same as gnome system-config-printer
48 def _(string):
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)
55 else:
56 if prop.text is None:
57 return ""
58 text = prop.text.encode("UTF-8")
59 return i18n(text)
61 uic.properties.Properties._string = translate
63 import cups
64 cups.require ("1.9.27")
66 # These come from system-config-printer
67 import config
68 import cupshelpers #, options
69 from smburi import SMBURI
70 from debug import *
72 import dbus
73 import dbus.mainloop.qt
74 import dbus.service
76 ellipsis = unichr(0x2026)
78 try:
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:
87 return True
88 return False
90 class GUI(QWidget):
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)
101 try:
102 self.language = locale.getlocale(locale.LC_MESSAGES)
103 self.encoding = locale.getlocale(locale.LC_CTYPE)
104 except:
105 nonfatalException()
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)
111 self.printer = None
112 self.conflicts = set() # of options
113 self.connect_server = (self.printer and self.printer.getServer()) \
114 or cups.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,))
126 try:
127 self.cups = cups.Connection()
128 except RuntimeError:
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:
134 pass
136 self.cups = None
138 if os.path.exists("system-config-printer.ui"):
139 APPDIR = QDir.currentPath()
140 else:
141 file = KStandardDirs.locate("appdata", "system-config-printer.ui")
142 APPDIR = file.left(file.lastIndexOf("/"))
144 uic.loadUi(APPDIR + "/" + "system-config-printer.ui", self)
145 self.show()
147 # New Printer Dialog
148 self.newPrinterGUI = np = NewPrinterGUI(self)
149 #np.NewPrinterWindow.set_transient_for(self.MainWindow)
151 self.setConnected()
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)
177 try:
178 self.populateList(start_printer, change_ppd)
179 except cups.HTTPError, (s,):
180 self.cups = None
181 self.setConnected()
182 self.populateList()
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):
207 if self.changed:
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
211 return
212 items = self.mainlist.selectedItems()
213 if len(items) < 1:
214 return
215 item = items[0]
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
225 item_selected = True
226 if type == "New":
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)
232 if self.cups:
233 self.fillServerTab()
234 else:
235 # No connection to CUPS. Make sure the Apply/Revert buttons
236 # are not sensitive.
237 self.setDataButtonState()
238 item_selected = False
239 elif type in ['Printer', 'Class']:
240 try:
241 self.fillPrinterTab(name)
242 self.fillPrinterOptions()
243 self.setDataButtonState()
244 except RuntimeError:
245 # Perhaps cupsGetPPD2 failed for a browsed printer.
246 self.ntbkMain.setCurrentIndex(3)
247 #self.ntbkMain.set_current_page(2)
248 return
250 #self.ntbkMain.set_current_page(1)
251 self.ntbkMain.setCurrentIndex(2)
252 elif type == "None":
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"""
275 if self.changed:
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):
283 try:
284 if not win:
285 win = self
286 win.setCursor(Qt.WaitCursor)
287 QApplication.processEvents()
288 except:
289 nonfatalException ()
291 def ready (self, win = None):
292 try:
293 if not win:
294 win = self
295 win.setCursor(Qt.ArrowCursor)
296 QApplication.processEvents()
297 except:
298 nonfatalException ()
300 def setConnected(self):
301 connected = bool(self.cups)
303 host = cups.getServer()
305 if host[0] == '/':
306 host = 'localhost'
307 self.setWindowTitle(i18n("Printer configuration - %1", host))
309 if connected:
310 status_msg = i18n("Connected to %1", host)
311 else:
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)
327 try:
328 del self.server_settings
329 except:
330 pass
332 def populateList(self, start_printer = None, change_ppd = False):
333 #FIXMEold_name, old_type = self.getSelectedItem()
334 old_name = ""
335 old_type = ""
337 select_path = None
339 if self.cups:
340 try:
341 # get Printers
342 self.printers = cupshelpers.getPrinters(self.cups)
344 # Get default printer.
345 try:
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
353 else:
354 self.default_printer = None
355 except cups.IPPError, (e, m):
356 self.show_IPP_Error(e, m)
357 self.printers = {}
358 self.default_printer = None
359 else:
360 self.printers = {}
361 self.default_printer = None
363 local_printers = []
364 local_classes = []
365 remote_printers = []
366 remote_classes = []
368 for name, printer in self.printers.iteritems():
369 if printer.default:
370 self.default_printer = name
371 self.servers.add(printer.getServer())
373 if printer.remote:
374 if printer.is_class: remote_classes.append(name)
375 else: remote_printers.append(name)
376 else:
377 if printer.is_class: local_classes.append(name)
378 else: local_printers.append(name)
380 local_printers.sort()
381 local_classes.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.
391 old_name = ""
393 if (self.default_printer != None and
394 start_printer == None and
395 old_name == ""):
396 start_printer = self.default_printer
398 if not start_printer:
399 start_printer = old_name
401 expanded = {
402 "_printers" : True,
403 "_classes" : True,
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
412 while iter:
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'])
423 # add new
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
444 if expanded[name]:
445 rootTreeItem.setExpanded(True)
446 #self.tvMainList.expand_row(path, False)
447 self.on_tvMainList_cursor_changed()
448 self.setDataButtonState()
450 """FIXME
451 if change_ppd:
452 self.on_btnChangePPD_clicked (self.btnChangePPD)
455 #TODO
456 # Connect to Server
458 def on_printer_changed(self, text):
459 widget = self.sender()
460 if not widget: #method called as a method not a slot
461 return
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())
470 else:
471 raise ValueError, "Widget type not supported (yet)"
473 p = self.printer
474 old_values = {
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)
492 else:
493 self.changed.add(widget)
494 self.setDataButtonState()
496 #TODO
497 # Access control
499 #TODO
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)
515 except:
516 debugprint ("exception in setDataButtonState")
517 pass
519 installablebold = False
520 optionsbold = False
521 if self.conflicts:
522 debugprint ("Conflicts detected")
523 self.btnConflict.show()
524 for option in self.conflicts:
525 if option.tab_label == self.lblPInstallOptions:
526 installablebold = True
527 else:
528 optionsbold = True
529 else:
530 self.btnConflict.hide()
531 installabletext = i18n("Installable Options")
532 optionstext = i18n("Printer Options")
533 if installablebold:
534 installabletext = i18nc("Conflicted entry", "<b>%1</b>", installabletext)
535 if optionsbold:
536 optionstext = i18nc("Conflicted entry", "<b>%1</b>", optionstext)
537 self.lblPInstallOptions.setText(installabletext)
538 self.lblPOptions.setText(optionstext)
540 """ FIXME
541 store = self.tvPrinterProperties.get_model ()
542 if store:
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
559 name = printer.name
561 try:
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)
568 #FIXME classes
569 if printer.is_class:
570 pass
572 # update member list
573 new_members = self.getCurrentClassMembers(self.tvClassMembers)
574 if not new_members:
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()
581 dialog.destroy()
582 if result==gtk.RESPONSE_NO:
583 return True
584 class_deleted = True
586 # update member list
587 old_members = printer.class_members[:]
589 for member in new_members:
590 if member in old_members:
591 old_members.remove(member)
592 else:
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)
646 """FIXME TODO access
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)
664 return True
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.
671 try:
672 self.server_settings = self.cups.adminGetServerSettings()
673 except:
674 nonfatalException()
676 if class_deleted:
677 self.populateList ()
678 else:
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)
684 return False
686 def getPrinterSettings(self):
687 #self.ppd.markDefaults()
688 for option in self.options.itervalues():
689 option.writeback()
691 @pyqtSignature("")
692 def on_btnPrintTestPage_clicked(self):
693 if self.test_button_cancels:
694 jobs = self.printer.testsQueued ()
695 for job in jobs:
696 debugprint ("Canceling job %s" % job)
697 try:
698 self.cups.cancelJob (job)
699 except cups.IPPError, (e, msg):
700 self.show_IPP_Error(e, msg)
701 self.setTestButton (self.printer)
702 return
703 try:
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")
708 if opt:
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)
715 else:
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 "
721 "job %d") % job_id)
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 "
731 "shared."))
732 self.ErrorDialog.set_transient_for (self.MainWindow)
733 self.ErrorDialog.run ()
734 self.ErrorDialog.hide ()
735 else:
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)
741 os.close (tmpfd)
742 try:
743 format = "application/vnd.cups-command"
744 job_id = self.cups.printTestPage (self.printer.name,
745 format=format,
746 file=tmpfname,
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 "
751 "job %d") % job_id)
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 "
763 "shared."))
764 self.ErrorDialog.set_transient_for (self.MainWindow)
765 self.ErrorDialog.run ()
766 self.ErrorDialog.hide ()
767 else:
768 self.show_IPP_Error(e, msg)
770 @pyqtSignature("")
771 def on_btnSelfTest_clicked(self):
772 self.maintenance_command ("PrintSelfTestPage")
774 @pyqtSignature("")
775 def on_btnCleanHeads_clicked(self):
776 self.maintenance_command ("Clean all")
778 def fillComboBox(self, combobox, values, value):
779 combobox.clear()
780 for nr, val in enumerate(values):
781 combobox.addItem(val)
782 if val == value:
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
797 try:
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.
803 self.ppd = False
804 except RuntimeError:
805 # The underlying cupsGetPPD2() function returned NULL without
806 # setting an IPP error, so it'll be something like a failed
807 # connection.
808 #FIXME show a dialogue
809 debugprint("Error!")
811 self.lblError.set_markup('<span size="larger">' +
812 _("<b>Error</b>") + '</span>\n\n' +
813 _("There was a problem connecting to "
814 "the CUPS server."))
815 self.ErrorDialog.set_transient_for(self.MainWindow)
816 self.ErrorDialog.run()
817 self.ErrorDialog.hide()
819 raise
821 for widget in (self.entPDescription, self.entPLocation,
822 self.entPDevice):
823 widget.setReadOnly(not editable)
825 for widget in (self.btnSelectDevice, self.btnChangePPD):
826 """,FIXME
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)
835 # Description page
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://"):
843 (group, host, share,
844 user, password) = SMBURI (uri=uri[6:]).separate ()
845 if password:
846 uri = "smb://"
847 if len (user) or len (password):
848 uri += ellipsis
849 uri += SMBURI (group=group, host=host, share=share).get_uri ()
850 self.entPDevice.setEnabled(False)
851 else:
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):
860 if printer.is_class:
861 widget.hide()
862 else:
863 widget.show()
866 # default printer
867 self.btnPMakeDefault.setEnabled(not printer.default)
868 if printer.default:
869 self.lblPDefault.setText(i18n("This is the default printer"))
870 elif self.default_printer:
871 self.lblPDefault.setText(self.default_printer)
872 else:
873 self.lblPDefault.setText(i18n("No default printer set."))
875 self.setTestButton (printer)
877 # Policy tab
878 # ----------
880 # State
881 self.chkPEnabled.setChecked(printer.enabled)
882 self.chkPAccepting.setChecked(not printer.rejecting)
883 self.chkPShared.setChecked(printer.is_shared)
884 try:
885 if printer.is_shared:
886 flag = cups.CUPS_SERVER_SHARE_PRINTERS
887 publishing = int (self.server_settings[flag])
888 if publishing:
889 self.lblNotPublished.hide()
890 else:
891 self.lblNotPublished.show()
892 else:
893 self.lblNotPublished.hide()
894 except:
895 self.lblNotPublished.hide()
897 # Job sheets
898 self.cmbPStartBanner.setEnabled(editable)
899 self.cmbPEndBanner.setEnabled(editable)
901 # Policies
902 self.cmbPErrorPolicy.setEnabled(editable)
903 self.cmbPOperationPolicy.setEnabled(editable)
906 # Access control
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")
921 if opt:
922 option.set_default (opt.defchoice)
924 option_editable = editable
925 try:
926 value = self.printer.attributes[option.name]
927 except KeyError:
928 option.reinit (None)
929 else:
930 try:
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)
941 else:
942 option.reinit (value)
944 self.server_side_options[option.name] = option
945 except:
946 option_editable = False
947 self.lblError.set_markup ('<span ' +
948 'size="larger">' +
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)
957 if not 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):
963 continue
964 supported = ""
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,
969 editable=editable)
970 self.entNewJobOption.set_text ('')
971 self.entNewJobOption.set_sensitive (editable)
972 self.btnNewJobOption.set_sensitive (False)
974 if printer.is_class:
975 # remove InstallOptions tab
976 tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
977 if tab_nr != -1:
978 self.ntbkPrinter.remove_page(tab_nr)
979 self.fillClassMembers(name, editable)
980 else:
981 # real Printer
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):
993 @pyqtSignature("")
994 def on_btnDelete_clicked(self):
995 name, type = self.getSelectedItem()
996 print name
998 # Confirm
999 if type == "Printer":
1000 message_format = i18n("Really delete printer %s?")
1001 else:
1002 message_format = i18n("Really delete class %s?")
1004 cancel = QMessageBox.question(self,"",
1005 unicode(message_format) % name,
1006 i18n("&Yes"), i18n("&No"),
1007 QString(), 0, 1)
1009 if cancel:
1010 return
1011 try:
1012 self.cups.deletePrinter(name)
1013 except cups.IPPError, (e, msg):
1014 self.show_IPP_Error(e, msg)
1016 self.changed = set()
1017 self.populateList()
1018 self.mainlist.setCurrentItem(self.mainlist.itemAt(0,0))
1020 #in Gnome side is now set_default_printer (self, name):
1021 @pyqtSignature("")
1022 def on_btnPMakeDefault_clicked(self):
1023 try:
1024 self.cups.setDefault(self.printer.name)
1025 except cups.IPPError, (e, msg):
1026 self.show_IPP_Error(e, msg)
1027 return
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 ()
1032 success = False
1033 try:
1034 resource = "/admin/conf/lpoptions"
1035 self.cups.getFile(resource, tmpfname)
1036 #success = True
1037 except cups.HTTPError, (s,):
1038 try:
1039 os.remove (tmpfname)
1040 except OSError:
1041 pass
1043 if s != cups.HTTP_NOT_FOUND:
1044 self.show_HTTP_Error(s)
1045 return
1047 if success:
1048 lines = file (tmpfname).readlines ()
1049 changed = False
1050 i = 0
1051 for line in lines:
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:]
1058 changed = True
1059 i += 1
1061 if changed:
1062 file (tmpfname, 'w').writelines (lines)
1063 try:
1064 self.cups.putFile (resource, tmpfname)
1065 except cups.HTTPError, (s,):
1066 os.remove (tmpfname)
1067 print s
1068 self.show_HTTP_Error(s)
1069 return
1071 # Now reconnect because the server needs to reload.
1072 self.reconnect ()
1074 try:
1075 os.remove (tmpfname)
1076 except OSError:
1077 pass
1079 try:
1080 self.populateList()
1081 except cups.HTTPError, (s,):
1082 self.cups = None
1083 self.setConnected()
1084 self.populateList()
1085 self.show_HTTP_Error(s)
1087 ##########################################################################
1088 ### Server settings
1089 ##########################################################################
1091 def fillServerTab(self):
1092 self.changed = set()
1093 try:
1094 self.server_settings = self.cups.adminGetServerSettings()
1095 except cups.IPPError, (e, m):
1096 #FIXME
1097 self.show_IPP_Error(e, m)
1098 self.tvMainList.get_selection().unselect_all()
1099 self.on_tvMainList_cursor_changed(self.tvMainList)
1100 return
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)
1115 else:
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)
1134 else:
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()))
1154 try:
1155 self.cups.adminSetServerSettings(setting_dict)
1156 except cups.IPPError, (e, m):
1157 self.show_IPP_Error(e, m)
1158 return True
1159 except RuntimeError, s:
1160 self.show_IPP_Error(None, s)
1161 return True
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.
1167 self.reconnect ()
1169 # Refresh the server settings in case they have changed in the
1170 # mean time.
1171 try:
1172 self.fillServerTab()
1173 except:
1174 nonfatalException()
1176 # ====================================================================
1177 # == New Printer Dialog ==============================================
1178 # ====================================================================
1180 # new printer
1181 def on_new_printer_activate(self):
1182 self.busy()
1183 self.newPrinterGUI.init("printer")
1184 self.ready()
1186 # new class
1187 def on_new_class_activate(self):
1188 self.newPrinterGUI.init("class")
1190 @pyqtSignature("")
1191 def on_btnSelectDevice_clicked(self):
1192 self.newPrinterGUI.init("device")
1194 @pyqtSignature("")
1195 def on_btnChangePPD_clicked(self):
1196 self.busy(self)
1197 self.newPrinterGUI.init("ppd")
1198 self.ready(self)
1200 def checkNPName(self, name):
1201 if not name: return False
1202 name = name.lower()
1203 for printer in self.printers.values():
1204 if not printer.discovered and printer.name.lower()==name:
1205 return False
1206 return True
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):
1214 suffix=2
1215 while not self.checkNPName (name + str (suffix)):
1216 suffix += 1
1217 if suffix == 100:
1218 break
1219 name += str (suffix)
1220 return name
1222 #TODO
1223 ## Watcher interface helpers
1225 @pyqtSignature("")
1226 def on_btnRevert_clicked(self):
1227 self.changed = set() # avoid asking the user
1228 self.on_tvMainList_cursor_changed()
1230 @pyqtSignature("")
1231 def on_btnPrinterPropertiesApply_clicked(self):
1232 err = self.printer_properties_response()
1233 if not err:
1234 self.populateList()
1235 else:
1236 nonfatalException()
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.'))
1241 else:
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.'))
1251 else:
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")
1262 elif status == -1:
1263 msg = i18nc("HTTP error", "Not connected")
1264 else:
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()
1273 if len(items) < 1:
1274 return ("", 'None')
1275 item = items[0]
1276 name = item.text(0)
1277 type = item.text(1)
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.
1290 # Disconnect.
1291 self.cups = None
1292 self.setConnected()
1294 cups.setServer(self.connect_server)
1295 cups.setUser(self.connect_user)
1296 attempt = 1
1297 while attempt <= 5:
1298 try:
1299 self.cups = cups.Connection ()
1300 break
1301 except RuntimeError:
1302 # Connection failed.
1303 time.sleep(1)
1304 attempt += 1
1306 self.setConnected()
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")
1320 # State
1321 self.chkPEnabled.setEnabled(printer.enabled)
1322 self.chkPAccepting.setEnabled(not printer.rejecting)
1323 self.chkPShared.setEnabled(printer.is_shared)
1325 # Job sheets
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)
1332 # Policies
1333 self.fillComboBox(self.cmbPErrorPolicy,
1334 printer.error_policy_supported,
1335 printer.error_policy)
1336 self.fillComboBox(self.cmbPOperationPolicy,
1337 printer.op_policy_supported,
1338 printer.op_policy)
1341 # Access control
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)
1352 else:
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()
1359 result = []
1360 for i in range(count):
1361 result.append(listwidget.item(i).text())
1362 result.sort()
1363 return result
1365 def moveClassMembers(self, treeview_from, treeview_to):
1366 rows = treeview_from.selectedItems()
1367 for row in rows:
1368 treeview_from.takeItem(treeview_from.row(row))
1369 treeview_to.addItem(row)
1371 # Password handling
1373 #FIXME obsolete?
1374 def cupsPasswdCallback(self, querystring):
1375 return "" #FIXME
1376 if self.passwd_retry or len(self.password) == 0:
1377 waiting = self.WaitWindow.get_property('visible')
1378 if waiting:
1379 self.WaitWindow.hide ()
1380 self.lblPasswordPrompt.set_label (self.prompt_primary +
1381 querystring)
1382 self.PasswordDialog.set_transient_for (self.MainWindow)
1383 self.entPasswd.grab_focus ()
1385 result = self.PasswordDialog.run()
1386 self.PasswordDialog.hide()
1387 if waiting:
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()
1393 else:
1394 self.password = ''
1395 self.passwd_retry = False
1396 else:
1397 self.passwd_retry = True
1398 return self.password
1400 class NewPrinterGUI(QDialog):
1402 new_printer_device_tabs = {
1403 "parallel" : 0, # empty tab
1404 "usb" : 0,
1405 "hal" : 0,
1406 "beh" : 0,
1407 "hp" : 0,
1408 "hpfax" : 0,
1409 "socket": 2,
1410 "ipp" : 3,
1411 "http" : 3,
1412 "lpd" : 4,
1413 "scsi" : 5,
1414 "serial" : 6,
1415 "smb" : 7,
1418 ntbkNewPrinterPages = {
1419 "name" : 0,
1420 "device" : 1,
1421 "make" : 2,
1422 "model" : 3,
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()
1438 else:
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()
1460 self.ppd = None
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)
1496 # SMB browser
1497 self.smb_store = gtk.TreeStore (str, # host or share
1498 str, # comment
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)
1504 # SMB list columns
1505 col = gtk.TreeViewColumn (_("Share"), gtk.CellRendererText (),
1506 text=0)
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 (),
1512 text=1)
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)
1519 # IPP browser
1520 self.ipp_store = gtk.TreeStore (str, # queue
1521 str, # location
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)
1526 # IPP list columns
1527 col = gtk.TreeViewColumn (_("Queue"), gtk.CellRendererText (),
1528 text=0)
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 (),
1534 text=1)
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)
1567 else:
1568 self.changed.discard(option)
1570 if option.conflicts:
1571 self.conflicts.add(option)
1572 else:
1573 self.conflicts.discard(option)
1574 self.setDataButtonState()
1576 return
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
1605 self.queryPPDs ()
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 ()
1616 self.loadPPDs()
1617 self.fillDeviceTab(self.mainapp.printer.device_uri)
1618 # Start fetching information from CUPS in the background
1619 self.new_printer_PPDs_loaded = False
1620 self.queryPPDs ()
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
1628 if ppd:
1629 attr = ppd.findAttr("Manufacturer")
1630 if attr:
1631 self.auto_make = attr.value
1632 else:
1633 self.auto_make = ""
1634 attr = ppd.findAttr("ModelName")
1635 if not attr: attr = ppd.findAttr("ShortNickName")
1636 if not attr: attr = ppd.findAttr("NickName")
1637 if attr:
1638 if attr.value.startswith(self.auto_make):
1639 self.auto_model = attr.value[len(self.auto_make):].strip ()
1640 else:
1641 try:
1642 self.auto_model = attr.value.split(" ", 1)[1]
1643 except IndexError:
1644 self.auto_model = ""
1645 else:
1646 self.auto_model = ""
1647 else:
1648 # Special CUPS names for a raw queue.
1649 self.auto_make = 'Raw'
1650 self.auto_model = 'Queue'
1652 self.loadPPDs ()
1653 self.fillMakeList()
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]:
1662 widget.setText('')
1664 try:
1665 p = os.popen ('/bin/hostname', 'r')
1666 hostname = p.read ().strip ()
1667 p.close ()
1668 self.entNPLocation.setText(hostname)
1669 except:
1670 nonfatalException ()
1672 self.entNPTDirectJetPort.setText('9100')
1673 self.setNPButtons()
1674 self.exec_()
1676 # get PPDs
1678 def queryPPDs(self):
1679 debugprint ("queryPPDs")
1680 if not self.ppds_lock.acquire(0):
1681 debugprint ("queryPPDs: in progress")
1682 return
1683 debugprint ("Lock acquired for PPDs thread")
1684 # Start new thread
1685 thread.start_new_thread (self.getPPDs_thread, (self.language[0],))
1686 debugprint ("PPDs thread started")
1688 def getPPDs_thread(self, language):
1689 try:
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,
1699 language=language)
1700 debugprint ("Closing connection (PPDs)")
1701 del c
1702 except cups.IPPError, (e, msg):
1703 self.ppds_result = cups.IPPError (e, msg)
1704 except:
1705 nonfatalException()
1706 self.ppds_result = { }
1708 debugprint ("Releasing PPDs lock")
1709 self.ppds_lock.release ()
1711 def fetchPPDs(self, parent=None):
1712 debugprint ("fetchPPDs")
1713 self.queryPPDs()
1714 time.sleep (0.1)
1716 # Keep the UI refreshed while we wait for the devices to load.
1717 waiting = False
1718 while (self.ppds_lock.locked()):
1719 if not waiting:
1720 waiting = True
1721 self.WaitWindow.setText(i18n('<b>Searching</b>') + '<br /><br />' +
1722 i18n('Searching for drivers'))
1723 self.WaitWindow.show ()
1725 QApplication.processEvents()
1727 time.sleep (0.1)
1729 if waiting:
1730 self.WaitWindow.hide ()
1732 debugprint ("Got PPDs")
1733 result = self.ppds_result # atomic operation
1734 if isinstance (result, cups.IPPError):
1735 # Propagate exception.
1736 raise result
1737 return result
1739 def loadPPDs(self, parent=None):
1740 try:
1741 return self.ppds
1742 except:
1743 self.ppds = self.fetchPPDs (parent=parent)
1744 return self.ppds
1746 def dropPPDs(self):
1747 try:
1748 del self.ppds
1749 except:
1750 pass
1752 # Class members
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)
1760 @pyqtSignature("")
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)))
1766 @pyqtSignature("")
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)))
1772 @pyqtSignature("")
1773 def on_btnNPBack_clicked(self):
1774 self.nextNPTab(-1)
1776 @pyqtSignature("")
1777 def on_btnNPForward_clicked(self):
1778 self.nextNPTab()
1780 def nextNPTab(self, step=1):
1781 page_nr = self.ntbkNewPrinter.currentIndex()
1783 if self.dialog_mode == "class":
1784 #order = [0, 4, 5]
1785 order = [self.ntbkNewPrinterPages["name"], self.ntbkNewPrinterPages["class-members"]]
1786 elif self.dialog_mode == "printer":
1787 #self.busy(self)
1788 if page_nr == 1: # Device (first page)
1789 # Choose an appropriate name.
1790 name = 'printer'
1791 try:
1792 if self.device.id:
1793 name = self.device.id_dict["MDL"]
1794 name = self.mainapp.makeNameUnique (name)
1795 self.entNPName.setText(name)
1796 except:
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)
1802 faxuri = None
1803 if host:
1804 faxuri = self.get_hplip_uri_for_network_printer(host,
1805 "fax")
1806 if faxuri:
1807 ##FIXME
1808 dialog = gtk.Dialog(self.device.info,
1809 self.NewPrinterWindow,
1810 gtk.DIALOG_MODAL |
1811 gtk.DIALOG_DESTROY_WITH_PARENT,
1812 (i18n("Printer"), 1,
1813 i18n("Fax"), 2))
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)
1819 label.show()
1820 queue_type = dialog.run()
1821 dialog.destroy()
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 + \
1830 ";DES:" + \
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)
1842 if res:
1843 resg = res.groups()
1844 try:
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]
1849 except:
1850 debugprint("exception in getting remotecupsqueue")
1851 pass
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()
1858 try:
1859 cups.setServer (resg[0])
1860 if len (resg[1]) > 0:
1861 cups.setPort (int (resg[1]))
1862 else:
1863 cups.setPort (631)
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', '')
1871 if len (info) > 0:
1872 self.entNPDescription.setText(info)
1873 if len (location) > 0:
1874 self.entNPLocation.setText(location)
1875 except:
1876 nonfatalException ()
1878 cups.setServer (oldserver)
1879 cups.setPort (oldport)
1881 if (not self.remotecupsqueue and
1882 not self.new_printer_PPDs_loaded):
1883 try:
1884 self.loadPPDs(self)
1885 except cups.IPPError, (e, msg):
1886 #self.ready (self)
1887 self.show_IPP_Error(e, msg)
1888 return
1889 except:
1890 self.ready (self)
1891 return
1892 self.new_printer_PPDs_loaded = True
1894 ppdname = None
1895 try:
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
1899 ppdname = 'raw'
1900 self.ppd = ppdname
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"],
1908 id_dict["MDL"],
1909 id_dict["DES"],
1910 id_dict["CMD"],
1911 self.device.uri)
1912 else:
1913 (status, ppdname) = self.ppds.\
1914 getPPDNameFromDeviceID ("Generic",
1915 "Printer",
1916 "Generic Printer",
1918 self.device.uri)
1920 if ppdname and not self.remotecupsqueue:
1921 ppddict = self.ppds.getInfoFromPPDName (ppdname)
1922 make_model = ppddict['ppd-make-and-model']
1923 (make, model) = \
1924 cupshelpers.ppds.ppdMakeModelSplit (make_model)
1925 self.auto_make = make
1926 self.auto_model = model
1927 except:
1928 nonfatalException ()
1929 if not self.remotecupsqueue:
1930 self.fillMakeList()
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
1935 # selected.
1936 try:
1937 items = self.tvNPModels.selectedItems()
1938 name = unicode(items[0].text())
1939 name = self.mainapp.makeNameUnique (name)
1940 self.entNPName.setText(name)
1941 except:
1942 nonfatalException ()
1944 ##self.ready (self.NewPrinterWindow)
1945 if self.remotecupsqueue:
1946 order = [1, 0]
1947 elif self.rbtnNPFoomatic.isChecked():
1948 order = [1, 2, 3, 6, 0]
1949 elif self.rbtnNPPPD.isChecked():
1950 order = [1, 2, 6, 0]
1951 else:
1952 # Downloadable driver
1953 order = [1, 2, 7, 6, 0]
1954 elif self.dialog_mode == "device":
1955 order = [1]
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():
1961 order = [2, 5, 6]
1962 else:
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()
1972 """FIXME todo
1973 if next_page_nr == 6:
1974 # Prepare Installable Options screen.
1975 if isinstance(self.ppd, cups.PPD):
1976 self.fillNPInstallableOptions()
1977 else:
1978 self.installable_options = None
1979 # Put a label there explaining why the page is empty.
1980 ppd = self.ppd
1981 self.ppd = None
1982 self.fillNPInstallableOptions()
1983 self.ppd = ppd
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 ()
2003 self.busy(self)
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 ()
2010 time.sleep (0.1)
2012 self.ready (self.NewPrinterWindow)
2013 self.WaitWindow.hide ()
2015 self.fillDownloadableDrivers()
2017 self.ntbkNewPrinter.setCurrentIndex(next_page_nr)
2019 self.setNPButtons()
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))
2030 return
2032 if self.dialog_mode == "ppd":
2033 if nr == 5: # Apply
2034 if not self.installable_options:
2035 # There are no installable options, so this is the
2036 # last page.
2037 debugprint ("No installable options")
2038 self.btnNPForward.hide ()
2039 self.btnNPApply.show ()
2040 else:
2041 self.btnNPForward.show ()
2042 self.btnNPApply.hide ()
2043 return
2044 elif nr == 6:
2045 self.btnNPForward.hide()
2046 self.btnNPApply.show()
2047 return
2048 else:
2049 self.btnNPForward.show()
2050 self.btnNPApply.hide()
2051 if nr == 2:
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))
2065 return
2066 else:
2067 self.btnNPBack.show()
2069 # class/printer
2071 if nr == 1: # Device
2072 valid = False
2073 try:
2074 uri = self.getDeviceURI ()
2075 valid = validDeviceURI (uri)
2076 except:
2077 pass
2078 self.btnNPForward.setEnabled(valid)
2079 self.btnNPBack.hide ()
2080 else:
2081 self.btnNPBack.show()
2083 self.btnNPForward.show()
2084 self.btnNPApply.hide()
2086 if nr == 0: # Name
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 ()
2118 else:
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()
2127 fd = QFileDialog()
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):
2134 try:
2135 debugprint ("Connecting (devices)")
2136 cups.setServer (self.mainapp.connect_server)
2137 #cups.setUser (self.mainapp.connect_user)
2138 cups.setUser ("jr")
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)
2146 except:
2147 debugprint ("Exception in getDevices_thread")
2148 self.devices_result = {}
2150 try:
2151 debugprint ("Closing connection (devices)")
2152 del c
2153 except:
2154 pass
2156 debugprint ("Releasing devices lock")
2157 self.devices_lock.release ()
2159 # Device URI
2160 def queryDevices(self):
2161 if not self.devices_lock.acquire(0):
2162 debugprint ("queryDevices: in progress")
2163 return
2164 debugprint ("Lock acquired for devices thread")
2165 # Start new 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 ()
2173 time.sleep (0.1)
2175 # Keep the UI refreshed while we wait for the devices to load.
2176 waiting = False
2177 while (self.devices_lock.locked()):
2178 if not waiting:
2179 waiting = True
2180 self.WaitWindow.setText (i18n('<b>Searching</b>') + '<br/><br/>' +
2181 i18n('Searching for printers'))
2182 #if not parent:
2183 # parent = self.mainapp.MainWindow
2184 #self.WaitWindow.set_transient_for (parent)
2185 #self.WaitWindow.show ()
2186 self.WaitWindow.show()
2188 QApplication.processEvents()
2190 time.sleep (0.1)
2192 if waiting:
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.
2200 raise result
2201 return result
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)
2207 try:
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 ()
2213 except:
2214 # Problem executing command.
2215 return None
2217 for line in stdout.split ("\n"):
2218 if line.find ("fax-type") == -1:
2219 continue
2220 faxtype = -1
2221 res = re.search ("(\d+)", line)
2222 if res:
2223 resg = res.groups()
2224 faxtype = resg[0]
2225 if faxtype >= 0: break
2226 if faxtype < 0:
2227 return None
2228 elif faxtype == 4:
2229 return cupshelpers.parseDeviceID ('MFG:HP;MDL:Fax 2;DES:HP Fax 2;')
2230 else:
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"
2237 else: mod = "-c"
2238 cmd = 'hp-makeuri ' + mod + ' "${HOST}"'
2239 debugprint (host + ": " + cmd)
2240 uri = None
2241 try:
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 ()
2247 except:
2248 # Problem executing command.
2249 return None
2251 uri = stdout.strip ()
2252 return uri
2254 def getNetworkPrinterMakeModel(self, device):
2255 # Determine host name/IP
2256 host = None
2257 s = device.uri.find ("://")
2258 if s != -1:
2259 s += 3
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
2266 if host:
2267 os.environ["HOST"] = host
2268 cmd = '/usr/lib/cups/backend/snmp "${HOST}"'
2269 debugprint (host + ": " + cmd)
2270 stdout = None
2271 try:
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 ()
2277 except:
2278 # Problem executing command.
2279 pass
2281 if stdout != None:
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:
2294 mk = None
2295 md = None
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
2301 if host:
2302 hplipuri = self.get_hplip_uri_for_network_printer(host, "print")
2303 if hplipuri:
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/")
2308 if s != -1:
2309 s += 5
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 "):
2314 mdl = mdl[3:]
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
2319 return host
2321 def fillDeviceTab(self, current_uri=None, query=True):
2322 if query:
2323 try:
2324 devices = self.fetchDevices()
2325 except cups.IPPError, (e, msg):
2326 self.show_IPP_Error(e, msg)
2327 devices = {}
2328 except:
2329 nonfatalException()
2330 devices = {}
2332 if current_uri:
2333 if devices.has_key (current_uri):
2334 current = devices.pop(current_uri)
2335 else:
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
2346 ser = None
2347 s = device.uri.find ("?serial=")
2348 if s != -1:
2349 s += 8
2350 e = device.uri[s:].find ("?")
2351 if e == -1: e = len (device.uri)
2352 ser = device.uri[s:s+e]
2353 mod = None
2354 s = device.uri[6:].find ("/")
2355 if s != -1:
2356 s += 7
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_"):
2361 mod = mod[3:]
2362 matchfound = 0
2363 for hpdevice in self.devices:
2364 hpser = None
2365 hpmod = None
2366 uri = hpdevice.uri
2367 if not uri.startswith ("hp:"): continue
2368 if ser:
2369 s = uri.find ("?serial=")
2370 if s != -1:
2371 s += 8
2372 e = uri[s:].find ("?")
2373 if e == -1: e = len (uri)
2374 hpser = uri[s:s+e]
2375 if hpser != ser: continue
2376 matchfound = 1
2377 if mod and not (ser and hpser):
2378 s = uri.find ("/usb/")
2379 if s != -1:
2380 s += 5
2381 e = uri[s:].find ("?")
2382 if e == -1: e = len (uri)
2383 hpmod = uri[s:s+e].lower ()
2384 if hpmod.startswith ("hp_"):
2385 hpmod = hpmod[3:]
2386 if hpmod != mod: continue
2387 matchfound = 1
2388 if matchfound == 1: break
2389 if matchfound == 1:
2390 device.uri = "delete"
2391 if device.type == "hal":
2392 # Remove HAL USB URIs, for these printers there are already
2393 # USB URIs
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", "")
2399 try:
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)
2405 faxuri = None
2406 if host:
2407 faxuri = self.get_hplip_uri_for_network_printer(host,
2408 "fax")
2409 if faxuri:
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:"):
2416 device.type = "hp"
2417 device.info += (" HPLIP")
2418 except:
2419 nonfatalException ()
2420 # Mark duplicate URIs for deletion
2421 for i in range (len (self.devices)):
2422 for j in range (len (self.devices)):
2423 if i == j: continue
2424 device1 = self.devices[i]
2425 device2 = self.devices[j]
2426 if device1.uri == "delete" or device2.uri == "delete":
2427 continue
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"
2436 else:
2437 device2.uri = "delete"
2438 self.devices = filter(lambda x: x.uri not in ("hp:/no_device_found",
2439 "hpfax:/no_device_found",
2440 "hp", "hpfax",
2441 "hal", "beh",
2442 "scsi", "http", "delete"),
2443 self.devices)
2444 self.devices.sort()
2446 self.devices.append(cupshelpers.Device('',
2447 **{'device-info' :i18nc("Other device", "Other")}))
2448 if current_uri:
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):
2462 self.setNPButtons()
2464 #TODO
2465 ## SMB browsing
2466 def on_entSMBURI_changed (self, text):
2467 uri = unicode(text)
2468 (group, host, share, user, password) = SMBURI (uri=uri).separate ()
2469 if user:
2470 self.entSMBUsername.setText(user)
2471 if password:
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 ()
2490 ### IPP Browsing
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/'
2496 if valid:
2497 uri = "ipp://%s%s" % (hostname, queue)
2498 self.lblIPPURI.setText(uri)
2499 self.lblIPPURI.show ()
2500 self.entNPTIPPQueuename.show ()
2501 else:
2502 self.lblIPPURI.hide ()
2504 self.btnIPPVerify.setEnabled(valid)
2505 self.setNPButtons ()
2507 #FIXME this seems totally different from the Gnome one
2508 @pyqtSignature("")
2509 def on_btnIPPFindQueue_clicked(self):
2510 self.IPPBrowseBox.clear()
2511 host = str(self.entNPTIPPHostname.text())
2512 cups.setServer (host)
2513 printers = classes = {}
2514 try:
2515 c = cups.Connection()
2516 printers = c.getPrinters ()
2517 classes = c.getClasses ()
2518 del c
2519 except RuntimeError:
2520 pass
2521 except cups.IPP_Error, (e, msg):
2522 pass
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 ():
2531 pass
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)
2548 type = device.type
2549 url = device.uri.split(":", 1)[-1]
2550 if page == 0:
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).")
2566 else:
2567 text = device.uri
2569 self.lblNPDeviceDescription.setText(text)
2570 elif device.type=="socket":
2571 if device.uri.startswith ("socket"):
2572 host = device.uri[9:]
2573 i = host.find (":")
2574 if i != -1:
2575 port = int (host[i + 1:])
2576 host = host[:i]
2577 else:
2578 port = 9100
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("+")
2586 option_dict = {}
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()
2602 nr = 0
2603 while iter:
2604 value = model.get(iter,0)[0]
2605 if value == option_dict[name]:
2606 widget.set_active(nr)
2607 break
2608 iter = model.iter_next(iter)
2609 nr += 1
2610 else: # use optionvalues
2611 nr = optionvalues.index(
2612 option_dict[name])
2613 widget.set_active(nr+1) # compensate "Default"
2614 else:
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)
2622 if match:
2623 server = match.group (2)
2624 printer = match.group (3)
2625 else:
2626 server = ""
2627 printer = ""
2629 self.entNPTIPPHostname.setText(server)
2630 self.entNPTIPPQueuename.setText(printer)
2631 self.lblIPPURI.setText(device.uri)
2632 self.lblIPPURI.show()
2633 self.entNPTIPPQueuename.show()
2634 else:
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:]
2642 i = host.find ("/")
2643 if i != -1:
2644 printer = host[i + 1:]
2645 host = host[:i]
2646 else:
2647 printer = ""
2648 self.cmbentNPTLpdHost.addItem(host)
2649 self.cmbentNPTLpdQueue.addItem(printer)
2650 elif device.uri == "lpd":
2651 pass
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)
2660 else:
2661 self.entNPTDevice.setText(device.uri)
2663 self.setNPButtons()
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
2671 if port:
2672 device = device + ':' + port
2673 elif type in ("http", "ipp"): # IPP
2674 if self.lblIPPURI.isVisible:
2675 device = unicode(self.lblIPPURI.text())
2676 else:
2677 device = "ipp"
2678 elif type == "lpd": # LPD
2679 host = unicode(self.cmbentNPTLpdHost.currentText())
2680 printer = unicode(self.cmbentNPTLpdQueue.currentText())
2681 device = "lpd://" + host
2682 if printer:
2683 device = device + "/" + printer
2684 elif type == "parallel": # Parallel
2685 device = self.device.uri
2686 elif type == "scsi": # SCSII
2687 device = ""
2688 elif type == "serial": # Serial
2689 options = []
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()
2698 if nr:
2699 if optionvalues is not None:
2700 option = optionvalues[nr-1]
2701 else:
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"
2706 if options:
2707 device = device + "?" + options
2708 elif type == "smb":
2709 uri = unicode(self.entSMBURI.text())
2710 (group, host, share, u, p) = SMBURI (uri=uri).separate ()
2711 user = ''
2712 password = ''
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
2721 else:
2722 device = str(self.entNPTDevice.text())
2723 return device
2724 # class/printer
2726 if nr == self.ntbkNewPrinterPages["device"]: # Device
2727 valid = False
2728 try:
2729 uri = self.getDeviceURI ()
2730 valid = validDeviceURI (uri)
2731 except:
2732 debugprint("exception in getDeviceURI()")
2733 pass
2734 self.btnNPForward.setEnabled(valid)
2735 self.btnNPBack.hide ()
2736 else:
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 ()
2772 else:
2773 accepted = True
2775 self.btnNPForward.set_sensitive(accepted)
2777 # PPD
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)
2786 """FIXME
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))
2802 self.setNPButtons()
2804 # PPD from foomatic
2806 def fillMakeList(self):
2807 makes = self.ppds.getMakes()
2808 self.tvNPMakes.clear()
2809 found = False
2810 index = 0
2811 for make in makes:
2812 self.tvNPMakes.addItem(make)
2813 index = index + 1
2814 if make==self.auto_make:
2815 self.tvNPMakes.setCurrentRow(index-1)
2816 found = True
2818 self.on_tvNPMakes_cursor_changed()
2820 def on_tvNPMakes_cursor_changed(self):
2821 items = self.tvNPMakes.selectedItems()
2822 if len(items) > 0:
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()
2829 selected = False
2830 index = 0
2831 selected = False
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)
2836 selected = True
2837 index = index + 1
2838 if not selected:
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)", "")
2855 try:
2856 lpostfix = " [%s]" % ppd["ppd-natural-language"]
2857 driver += lpostfix
2858 except KeyError:
2859 pass
2861 if i == 0:
2862 self.tvNPDrivers.addItem(i18nc("Recommended driver", "%1 (recommended)", driver))
2863 self.tvNPDrivers.setCurrentRow(0)
2864 else:
2865 self.tvNPDrivers.addItem(driver)
2866 ##self.tvNPDrivers.columns_autosize()
2868 def on_tvNPModels_cursor_changed(self):
2869 items = self.tvNPModels.selectedItems()
2870 if len(items) > 0:
2871 pmodel = unicode(items[0].text())
2872 self.fillDriverList(self.NPMake, pmodel)
2874 def getNPPPD(self):
2875 try:
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()))
2883 else:
2884 """FIXME
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
2894 ppd = "XXX"
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().
2901 get_selected())
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")
2911 else:
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 '
2919 'follows:') + '\n'
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 ()
2925 p.close ()
2926 err += reduce (lambda x, y: x + y, output)
2927 else:
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)
2936 return None
2938 if isinstance(ppd, str) or isinstance(ppd, unicode):
2939 try:
2940 if (ppd != "raw"):
2941 f = self.mainapp.cups.getServerPPD(ppd)
2942 ppd = cups.PPD(f)
2943 os.unlink(f)
2944 except AttributeError:
2945 nonfatalException()
2946 debugprint ("pycups function getServerPPD not available: never mind")
2947 except RuntimeError:
2948 nonfatalException()
2949 debugprint ("libcups from CUPS 1.3 not available: never mind")
2950 except cups.IPPError:
2951 nonfatalException()
2952 debugprint ("CUPS 1.3 server not available: never mind")
2954 return ppd
2956 # Create new Printer
2957 @pyqtSignature("")
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())
2963 else:
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.
2971 check = False
2972 checkppd = None
2973 ppd = self.ppd
2975 if self.dialog_mode=="class":
2976 members = self.mainapp.getCurrentClassMembers(self.tvNCMembers)
2977 try:
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)
2983 return
2984 elif self.dialog_mode=="printer":
2985 self.device.uri = unicode(self.device.uri)
2986 uri = None
2987 if self.device.uri:
2988 uri = self.device.uri
2989 else:
2990 uri = self.getDeviceURI()
2991 if not self.ppd: # XXX needed?
2992 # Go back to previous page to re-select driver.
2993 self.nextNPTab(-1)
2994 return
2996 # write Installable Options to ppd
2997 for option in self.options.itervalues():
2998 option.writeback()
3000 #self.busy(self)
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()
3006 try:
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)
3011 check = True
3012 elif ppd is None: # raw queue
3013 self.mainapp.cups.addPrinter(name, device=uri,
3014 info=info, location=location)
3015 else:
3016 cupshelpers.setPPDPageSize(ppd, self.language[0])
3017 self.mainapp.cups.addPrinter(name, ppd=ppd,
3018 device=uri, info=info, location=location)
3019 check = True
3020 checkppd = ppd
3021 cupshelpers.activateNewPrinter (self.mainapp.cups, name)
3022 except cups.IPPError, (e, msg):
3023 #self.ready(self)
3024 self.WaitWindow.hide ()
3025 self.show_IPP_Error(e, msg)
3026 return
3027 except:
3028 ##self.ready (self.NewPrinterWindow)
3029 self.WaitWindow.hide ()
3030 fatalException (1)
3031 self.WaitWindow.hide ()
3032 ##self.ready (self.NewPrinterWindow)
3033 #comment
3034 if self.dialog_mode in ("class", "printer"):
3035 try:
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)
3042 return
3043 elif self.dialog_mode == "device":
3044 try:
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)
3049 return
3050 elif self.dialog_mode == "ppd":
3051 if not ppd:
3052 ppd = self.ppd = self.getNPPPD()
3053 if not ppd:
3054 # Go back to previous page to re-select driver.
3055 self.nextNPTab(-1)
3056 return
3058 # set ppd on server and retrieve it
3059 # cups doesn't offer a way to just download a ppd ;(=
3060 raw = False
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.
3066 try:
3067 self.mainapp.cups.addPrinter(name, ppdname='raw')
3068 except cups.IPPError, (e, msg):
3069 self.show_IPP_Error(e, msg)
3070 try:
3071 self.mainapp.cups.addPrinter(name, ppdname=ppd)
3072 except cups.IPPError, (e, msg):
3073 self.show_IPP_Error(e, msg)
3074 return
3076 try:
3077 filename = self.mainapp.cups.getPPD(name)
3078 ppd = cups.PPD(filename)
3079 os.unlink(filename)
3080 except cups.IPPError, (e, msg):
3081 if e == cups.IPP_NOT_FOUND:
3082 raw = True
3083 else:
3084 self.show_IPP_Error(e, msg)
3085 return
3086 else:
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
3090 else:
3091 # write Installable Options to ppd
3092 for option in self.options.itervalues():
3093 option.writeback()
3094 cupshelpers.setPPDPageSize(ppd, self.language[0])
3096 try:
3097 self.mainapp.cups.addPrinter(name, ppd=ppd)
3098 except cups.IPPError, (e, msg):
3099 self.show_IPP_Error(e, msg)
3101 if not raw:
3102 check = True
3103 checkppd = ppd
3105 self.accept()
3106 self.mainapp.populateList(start_printer=name)
3107 if check:
3108 try:
3109 self.checkDriverExists (name, ppd=checkppd)
3110 except:
3111 nonfatalException()
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).
3118 try:
3119 option = self.mainapp.server_side_options['media']
3120 if option.get_current_value () == None:
3121 debugprint ("Invalid media option: resetting")
3122 option.reset ()
3123 self.mainapp.changed.add (option)
3124 self.mainapp.save_printer (self.mainapp.printer)
3125 except KeyError:
3126 pass
3127 except:
3128 print "exception in check to see whether the media option has become invalid"
3129 nonfatalException()
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.'))
3135 else:
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
3142 if not.
3144 ppd: cups.PPD object, if already created"""
3146 # Is this queue on the local machine? If not, we can't check
3147 # anything at all.
3148 server = cups.getServer ()
3149 if not (server == 'localhost' or server == '127.0.0.1' or
3150 server == '::1' or server[0] == '/'):
3151 return
3153 # Fetch the PPD if we haven't already.
3154 if not ppd:
3155 try:
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.
3160 return
3161 else:
3162 self.show_IPP_Error(e, msg)
3163 return
3165 ppd = cups.PPD(filename)
3166 os.unlink(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):
3173 pkg = pkgs[0]
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)
3180 else:
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)
3195 pid = os.fork ()
3196 if pid == 0:
3197 # Child.
3198 try:
3199 os.execv (install, [install, pkg])
3200 except:
3201 pass
3202 sys.exit (1)
3203 elif pid == -1:
3204 pass # should handle error
3207 #FIXME obsolete?
3208 def on_entNPTIPPQueuename_textChanged(self, ent):
3209 self.update_IPP_URI_label ()
3211 #FIXME obsolete?
3212 def on_IPPBrowseBox_currentTextChanged(self, text):
3213 self.update_IPP_URI_label()
3215 #FIXME not in gnome?
3216 @pyqtSignature("")
3217 def on_btnNPCancel_clicked(self):
3218 self.hide()
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")
3227 version = "1.0"
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"
3233 bugEmail = ""
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()
3249 applet = GUI()
3250 sys.exit(app.exec_())