Back to index

system-config-printer  1.3.9+20120706
printerproperties.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 
00003 ## system-config-printer
00004 
00005 ## Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00006 ## Authors:
00007 ##  Tim Waugh <twaugh@redhat.com>
00008 ##  Florian Festi <ffesti@redhat.com>
00009 
00010 ## This program is free software; you can redistribute it and/or modify
00011 ## it under the terms of the GNU General Public License as published by
00012 ## the Free Software Foundation; either version 2 of the License, or
00013 ## (at your option) any later version.
00014 
00015 ## This program is distributed in the hope that it will be useful,
00016 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00017 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018 ## GNU General Public License for more details.
00019 
00020 ## You should have received a copy of the GNU General Public License
00021 ## along with this program; if not, write to the Free Software
00022 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00023 
00024 # config is generated from config.py.in by configure
00025 import config
00026 
00027 import os, tempfile
00028 import gtk
00029 import cups
00030 import locale
00031 from gettext import gettext as _
00032 
00033 import cupshelpers, options
00034 import gobject
00035 from gui import GtkGUI
00036 from optionwidgets import OptionWidget
00037 from debug import *
00038 import authconn
00039 from errordialogs import *
00040 import gtkinklevel
00041 import ppdcache
00042 import statereason
00043 import monitor
00044 import newprinter
00045 from newprinter import busy, ready
00046 
00047 import ppdippstr
00048 pkgdata = config.pkgdatadir
00049 
00050 def CUPS_server_hostname ():
00051     host = cups.getServer ()
00052     if host[0] == '/':
00053         return 'localhost'
00054     return host
00055 
00056 def on_delete_just_hide (widget, event):
00057     widget.hide ()
00058     return True # stop other handlers
00059 
00060 class PrinterPropertiesDialog(GtkGUI):
00061 
00062     __gsignals__ = {
00063         'destroy':       ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
00064         'dialog-closed': ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
00065         }
00066 
00067     printer_states = { cups.IPP_PRINTER_IDLE: _("Idle"),
00068                        cups.IPP_PRINTER_PROCESSING: _("Processing"),
00069                        cups.IPP_PRINTER_BUSY: _("Busy"),
00070                        cups.IPP_PRINTER_STOPPED: _("Stopped") }
00071 
00072     def __init__(self):
00073         gobject.GObject.__init__ (self)
00074 
00075         try:
00076             self.language = locale.getlocale(locale.LC_MESSAGES)
00077             self.encoding = locale.getlocale(locale.LC_CTYPE)
00078         except:
00079             nonfatalException()
00080             os.environ['LC_ALL'] = 'C'
00081             locale.setlocale (locale.LC_ALL, "")
00082             self.language = locale.getlocale(locale.LC_MESSAGES)
00083             self.encoding = locale.getlocale(locale.LC_CTYPE)
00084 
00085         self.printer = self.ppd = None
00086         self.conflicts = set() # of options
00087         self.changed = set() # of options
00088         self.signal_ids = dict()
00089 
00090         # WIDGETS
00091         # =======
00092         self.updating_widgets = False
00093         self.getWidgets({"PrinterPropertiesDialog":
00094                              ["PrinterPropertiesDialog",
00095                               "tvPrinterProperties",
00096                               "btnPrinterPropertiesCancel",
00097                               "btnPrinterPropertiesOK",
00098                               "btnPrinterPropertiesApply",
00099                               "btnPrinterPropertiesClose",
00100                               "ntbkPrinter",
00101                               "entPDescription",
00102                               "entPLocation",
00103                               "entPMakeModel",
00104                               "lblPMakeModel2",
00105                               "entPState",
00106                               "entPDevice",
00107                               "lblPDevice2",
00108                               "btnSelectDevice",
00109                               "btnChangePPD",
00110                               "chkPEnabled",
00111                               "chkPAccepting",
00112                               "chkPShared",
00113                               "lblNotPublished",
00114                               "btnPrintTestPage",
00115                               "btnSelfTest",
00116                               "btnCleanHeads",
00117                               "btnConflict",
00118 
00119                               "cmbPStartBanner",
00120                               "cmbPEndBanner",
00121                               "cmbPErrorPolicy",
00122                               "cmbPOperationPolicy",
00123 
00124                               "rbtnPAllow",
00125                               "rbtnPDeny",
00126                               "tvPUsers",
00127                               "entPUser",
00128                               "btnPAddUser",
00129                               "btnPDelUser",
00130 
00131                               "lblPInstallOptions",
00132                               "swPInstallOptions",
00133                               "vbPInstallOptions",
00134                               "swPOptions",
00135                               "lblPOptions",
00136                               "vbPOptions",
00137                               "algnClassMembers",
00138                               "vbClassMembers",
00139                               "lblClassMembers",
00140                               "tvClassMembers",
00141                               "tvClassNotMembers",
00142                               "btnClassAddMember",
00143                               "btnClassDelMember",
00144                               "btnRefreshMarkerLevels",
00145                               "tvPrinterStateReasons",
00146                               "ntbkPrinterStateReasons",
00147 
00148                               # Job options
00149                               "sbJOCopies", "btnJOResetCopies",
00150                               "cmbJOOrientationRequested", "btnJOResetOrientationRequested",
00151                               "cbJOFitplot", "btnJOResetFitplot",
00152                               "cmbJONumberUp", "btnJOResetNumberUp",
00153                               "cmbJONumberUpLayout", "btnJOResetNumberUpLayout",
00154                               "sbJOBrightness", "btnJOResetBrightness",
00155                               "cmbJOFinishings", "btnJOResetFinishings",
00156                               "sbJOJobPriority", "btnJOResetJobPriority",
00157                               "cmbJOMedia", "btnJOResetMedia",
00158                               "cmbJOSides", "btnJOResetSides",
00159                               "cmbJOHoldUntil", "btnJOResetHoldUntil",
00160                               "cmbJOOutputOrder", "btnJOResetOutputOrder",
00161                               "cmbJOPrintQuality", "btnJOResetPrintQuality",
00162                               "cmbJOPrinterResolution",
00163                               "btnJOResetPrinterResolution",
00164                               "cmbJOOutputBin", "btnJOResetOutputBin",
00165                               "cbJOMirror", "btnJOResetMirror",
00166                               "sbJOScaling", "btnJOResetScaling",
00167                               "sbJOSaturation", "btnJOResetSaturation",
00168                               "sbJOHue", "btnJOResetHue",
00169                               "sbJOGamma", "btnJOResetGamma",
00170                               "sbJOCpi", "btnJOResetCpi",
00171                               "sbJOLpi", "btnJOResetLpi",
00172                               "sbJOPageLeft", "btnJOResetPageLeft",
00173                               "sbJOPageRight", "btnJOResetPageRight",
00174                               "sbJOPageTop", "btnJOResetPageTop",
00175                               "sbJOPageBottom", "btnJOResetPageBottom",
00176                               "cbJOPrettyPrint", "btnJOResetPrettyPrint",
00177                               "cbJOWrap", "btnJOResetWrap",
00178                               "sbJOColumns", "btnJOResetColumns",
00179                               "tblJOOther",
00180                               "entNewJobOption", "btnNewJobOption",
00181 
00182                               # Marker levels
00183                               "vboxMarkerLevels",
00184                               "btnRefreshMarkerLevels"]},
00185 
00186                         domain=config.PACKAGE)
00187 
00188 
00189         self.dialog = self.PrinterPropertiesDialog
00190 
00191         # Don't let delete-event destroy the dialog.
00192         self.dialog.connect ("delete-event", self.on_delete)
00193 
00194         # Printer properties combo boxes
00195         for combobox in [self.cmbPStartBanner,
00196                          self.cmbPEndBanner,
00197                          self.cmbPErrorPolicy,
00198                          self.cmbPOperationPolicy]:
00199             cell = gtk.CellRendererText ()
00200             combobox.clear ()
00201             combobox.pack_start (cell, True)
00202             combobox.add_attribute (cell, 'text', 0)
00203 
00204         btn = self.btnRefreshMarkerLevels
00205         btn.connect ("clicked", self.on_btnRefreshMarkerLevels_clicked)
00206 
00207         # Printer state reasons list
00208         column = gtk.TreeViewColumn (_("Message"))
00209         icon = gtk.CellRendererPixbuf ()
00210         column.pack_start (icon, False)
00211         text = gtk.CellRendererText ()
00212         column.pack_start (text, False)
00213         column.set_cell_data_func (icon, self.set_printer_state_reason_icon)
00214         column.set_cell_data_func (text, self.set_printer_state_reason_text)
00215         column.set_resizable (True)
00216         self.tvPrinterStateReasons.append_column (column)
00217         selection = self.tvPrinterStateReasons.get_selection ()
00218         selection.set_mode (gtk.SELECTION_NONE)
00219         store = gtk.ListStore (int, str)
00220         self.tvPrinterStateReasons.set_model (store)
00221         self.PrinterPropertiesDialog.connect ("delete-event",
00222                                               on_delete_just_hide)
00223 
00224         self.static_tabs = 3
00225 
00226         # setup some lists
00227         for name, treeview in (
00228             (_("Members of this class"), self.tvClassMembers),
00229             (_("Others"), self.tvClassNotMembers),
00230             (_("Users"), self.tvPUsers),
00231             ):
00232 
00233             model = gtk.ListStore(str)
00234             cell = gtk.CellRendererText()
00235             column = gtk.TreeViewColumn(name, cell, text=0)
00236             treeview.set_model(model)
00237             treeview.append_column(column)
00238             treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
00239 
00240         # Printer Properties dialog
00241         self.dialog.connect ('response', self.printer_properties_response)
00242 
00243         # Printer Properties tree view
00244         col = gtk.TreeViewColumn ('', gtk.CellRendererText (), markup=0)
00245         self.tvPrinterProperties.append_column (col)
00246         sel = self.tvPrinterProperties.get_selection ()
00247         sel.connect ('changed', self.on_tvPrinterProperties_selection_changed)
00248         sel.set_mode (gtk.SELECTION_SINGLE)
00249 
00250         # Job Options widgets.
00251         for (widget,
00252              opts) in [(self.cmbJOOrientationRequested,
00253                         [[_("Portrait (no rotation)")],
00254                          [_("Landscape (90 degrees)")],
00255                          [_("Reverse landscape (270 degrees)")],
00256                          [_("Reverse portrait (180 degrees)")]]),
00257 
00258                        (self.cmbJONumberUp,
00259                         [["1"], ["2"], ["4"], ["6"], ["9"], ["16"]]),
00260 
00261                        (self.cmbJONumberUpLayout,
00262                         [[_("Left to right, top to bottom")],
00263                          [_("Left to right, bottom to top")],
00264                          [_("Right to left, top to bottom")],
00265                          [_("Right to left, bottom to top")],
00266                          [_("Top to bottom, left to right")],
00267                          [_("Top to bottom, right to left")],
00268                          [_("Bottom to top, left to right")],
00269                          [_("Bottom to top, right to left")]]),
00270 
00271                        (self.cmbJOFinishings,
00272   # See section 4.2.6 of this document for explanation of finishing types:
00273   # ftp://ftp.pwg.org/pub/pwg/candidates/cs-ippfinishings10-20010205-5100.1.pdf
00274                         [[_("None")],
00275                          [_("Staple")],
00276                          [_("Punch")],
00277                          [_("Cover")],
00278                          [_("Bind")],
00279                          [_("Saddle stitch")],
00280                          [_("Edge stitch")],
00281                          [_("Fold")],
00282                          [_("Trim")],
00283                          [_("Bale")],
00284                          [_("Booklet maker")],
00285                          [_("Job offset")],
00286                          [_("Staple (top left)")],
00287                          [_("Staple (bottom left)")],
00288                          [_("Staple (top right)")],
00289                          [_("Staple (bottom right)")],
00290                          [_("Edge stitch (left)")],
00291                          [_("Edge stitch (top)")],
00292                          [_("Edge stitch (right)")],
00293                          [_("Edge stitch (bottom)")],
00294                          [_("Staple dual (left)")],
00295                          [_("Staple dual (top)")],
00296                          [_("Staple dual (right)")],
00297                          [_("Staple dual (bottom)")],
00298                          [_("Bind (left)")],
00299                          [_("Bind (top)")],
00300                          [_("Bind (right)")],
00301                          [_("Bind (bottom)")]]),
00302 
00303                        (self.cmbJOMedia, []),
00304 
00305                        (self.cmbJOSides,
00306                         [[_("One-sided")],
00307                          [_("Two-sided (long edge)")],
00308                          [_("Two-sided (short edge)")]]),
00309 
00310                        (self.cmbJOHoldUntil, []),
00311 
00312                        (self.cmbJOOutputOrder,
00313                         [[_("Normal")],
00314                          [_("Reverse")]]),
00315 
00316                        (self.cmbJOPrintQuality,
00317                         [[_("Draft")],
00318                          [_("Normal")],
00319                          [_("High")]]),
00320 
00321                        (self.cmbJOPrinterResolution, []),
00322 
00323                        (self.cmbJOOutputBin, []),
00324                        ]:
00325             model = gtk.ListStore (gobject.TYPE_STRING)
00326             for row in opts:
00327                 model.append (row=row)
00328 
00329             cell = gtk.CellRendererText ()
00330             widget.pack_start (cell, True)
00331             widget.add_attribute (cell, 'text', 0)
00332             widget.set_model (model)
00333 
00334         opts = [ options.OptionAlwaysShown ("copies", int, 1,
00335                                             self.sbJOCopies,
00336                                             self.btnJOResetCopies),
00337 
00338                  options.OptionAlwaysShownSpecial \
00339                  ("orientation-requested", int, 3,
00340                   self.cmbJOOrientationRequested,
00341                   self.btnJOResetOrientationRequested,
00342                   combobox_map = [3, 4, 5, 6],
00343                   special_choice=_("Automatic rotation")),
00344 
00345                  options.OptionAlwaysShown ("fitplot", bool, False,
00346                                             self.cbJOFitplot,
00347                                             self.btnJOResetFitplot),
00348 
00349                  options.OptionAlwaysShown ("number-up", int, 1,
00350                                             self.cmbJONumberUp,
00351                                             self.btnJOResetNumberUp,
00352                                             combobox_map=[1, 2, 4, 6, 9, 16]),
00353 
00354                  options.OptionAlwaysShown ("number-up-layout", str, "lrtb",
00355                                             self.cmbJONumberUpLayout,
00356                                             self.btnJOResetNumberUpLayout,
00357                                             combobox_map = [ "lrtb",
00358                                                              "lrbt",
00359                                                              "rltb",
00360                                                              "rlbt",
00361                                                              "tblr",
00362                                                              "tbrl",
00363                                                              "btlr",
00364                                                              "btrl" ]),
00365 
00366                  options.OptionAlwaysShown ("brightness", int, 100,
00367                                             self.sbJOBrightness,
00368                                             self.btnJOResetBrightness),
00369 
00370                  options.OptionAlwaysShown ("finishings", int, 3,
00371                                             self.cmbJOFinishings,
00372                                             self.btnJOResetFinishings,
00373                                             combobox_map = [ 3, 4, 5, 6,
00374                                                              7, 8, 9, 10,
00375                                                              11, 12, 13, 14,
00376                                                              20, 21, 22, 23,
00377                                                              24, 25, 26, 27,
00378                                                              28, 29, 30, 31,
00379                                                              50, 51, 52, 53 ],
00380                                             use_supported = True),
00381 
00382                  options.OptionAlwaysShown ("job-priority", int, 50,
00383                                             self.sbJOJobPriority,
00384                                             self.btnJOResetJobPriority),
00385 
00386                  options.OptionAlwaysShown ("media", str,
00387                                             "A4", # This is the default for
00388                                                   # when media-default is
00389                                                   # not supplied by the IPP
00390                                                   # server.  Fortunately it
00391                                                   # is a mandatory attribute.
00392                                             self.cmbJOMedia,
00393                                             self.btnJOResetMedia,
00394                                             use_supported = True),
00395 
00396                  options.OptionAlwaysShown ("sides", str, "one-sided",
00397                                             self.cmbJOSides,
00398                                             self.btnJOResetSides,
00399                                             combobox_map =
00400                                             [ "one-sided",
00401                                               "two-sided-long-edge",
00402                                               "two-sided-short-edge" ]),
00403 
00404                  options.OptionAlwaysShown ("job-hold-until", str,
00405                                             "no-hold",
00406                                             self.cmbJOHoldUntil,
00407                                             self.btnJOResetHoldUntil,
00408                                             use_supported = True),
00409 
00410                  options.OptionAlwaysShown ("outputorder", str,
00411                                             "normal",
00412                                             self.cmbJOOutputOrder,
00413                                             self.btnJOResetOutputOrder,
00414                                             combobox_map =
00415                                             [ "normal",
00416                                               "reverse" ]),
00417 
00418                  options.OptionAlwaysShown ("print-quality", int, 3,
00419                                             self.cmbJOPrintQuality,
00420                                             self.btnJOResetPrintQuality,
00421                                             combobox_map = [ 3, 4, 5 ]),
00422 
00423                  options.OptionAlwaysShown ("printer-resolution",
00424                                             options.IPPResolution,
00425                                             options.IPPResolution((300,300,3)),
00426                                             self.cmbJOPrinterResolution,
00427                                             self.btnJOResetPrinterResolution,
00428                                             use_supported = True),
00429 
00430                  options.OptionAlwaysShown ("output-bin", str,
00431                                             "face-up",
00432                                             self.cmbJOOutputBin,
00433                                             self.btnJOResetOutputBin,
00434                                             use_supported = True),
00435 
00436                  options.OptionAlwaysShown ("mirror", bool, False,
00437                                             self.cbJOMirror,
00438                                             self.btnJOResetMirror),
00439 
00440                  options.OptionAlwaysShown ("scaling", int, 100,
00441                                             self.sbJOScaling,
00442                                             self.btnJOResetScaling),
00443 
00444                  options.OptionAlwaysShown ("saturation", int, 100,
00445                                             self.sbJOSaturation,
00446                                             self.btnJOResetSaturation),
00447 
00448                  options.OptionAlwaysShown ("hue", int, 0,
00449                                             self.sbJOHue,
00450                                             self.btnJOResetHue),
00451 
00452                  options.OptionAlwaysShown ("gamma", int, 1000,
00453                                             self.sbJOGamma,
00454                                             self.btnJOResetGamma),
00455 
00456                  options.OptionAlwaysShown ("cpi", float, 10.0,
00457                                             self.sbJOCpi, self.btnJOResetCpi),
00458 
00459                  options.OptionAlwaysShown ("lpi", float, 6.0,
00460                                             self.sbJOLpi, self.btnJOResetLpi),
00461 
00462                  options.OptionAlwaysShown ("page-left", int, 0,
00463                                             self.sbJOPageLeft,
00464                                             self.btnJOResetPageLeft),
00465 
00466                  options.OptionAlwaysShown ("page-right", int, 0,
00467                                             self.sbJOPageRight,
00468                                             self.btnJOResetPageRight),
00469 
00470                  options.OptionAlwaysShown ("page-top", int, 0,
00471                                             self.sbJOPageTop,
00472                                             self.btnJOResetPageTop),
00473 
00474                  options.OptionAlwaysShown ("page-bottom", int, 0,
00475                                             self.sbJOPageBottom,
00476                                             self.btnJOResetPageBottom),
00477 
00478                  options.OptionAlwaysShown ("prettyprint", bool, False,
00479                                             self.cbJOPrettyPrint,
00480                                             self.btnJOResetPrettyPrint),
00481 
00482                  options.OptionAlwaysShown ("wrap", bool, False, self.cbJOWrap,
00483                                             self.btnJOResetWrap),
00484 
00485                  options.OptionAlwaysShown ("columns", int, 1,
00486                                             self.sbJOColumns,
00487                                             self.btnJOResetColumns),
00488                  ]
00489         self.job_options_widgets = {}
00490         self.job_options_buttons = {}
00491         for option in opts:
00492             self.job_options_widgets[option.widget] = option
00493             self.job_options_buttons[option.button] = option
00494 
00495         self._monitor = None
00496         self._ppdcache = None
00497         debugprint ("+%s" % self)
00498 
00499     def __del__ (self):
00500         debugprint ("-%s" % self)
00501         del self._monitor
00502 
00503     def _connect (self, collection, obj, name, handler):
00504         c = self.signal_ids.get (collection, [])
00505         c.append ((obj, obj.connect (name, handler)))
00506         self.signal_ids[collection] = c
00507 
00508     def _disconnect (self, collection=None):
00509         if collection:
00510             collection = [collection]
00511         else:
00512             collection = self.signal_ids.keys ()
00513 
00514         for coll in collection:
00515             if self.signal_ids.has_key (coll):
00516                 for (obj, signal_id) in self.signal_ids[coll]:
00517                     obj.disconnect (signal_id)
00518 
00519                 del self.signal_ids[coll]
00520 
00521     def do_destroy (self):
00522         if self.PrinterPropertiesDialog:
00523             self.PrinterPropertiesDialog.destroy ()
00524             self.PrinterPropertiesDialog = None
00525 
00526     def destroy (self):
00527         debugprint ("DESTROY: %s" % self)
00528         self._disconnect ()
00529         self.ppd = None
00530         self.ppd_local = None
00531         self.printer = None
00532         self.emit ('destroy')
00533 
00534     def set_monitor (self, monitor):
00535         self._monitor = monitor
00536         if not monitor:
00537             return
00538 
00539         self._monitor.connect ('printer-event', self.on_printer_event)
00540         self._monitor.connect ('printer-removed', self.on_printer_removed)
00541         self._monitor.connect ('state-reason-added', self.on_state_reason_added)
00542         self._monitor.connect ('state-reason-removed',
00543                                self.on_state_reason_removed)
00544         self._monitor.connect ('cups-connection-error',
00545                                self.on_cups_connection_error)
00546 
00547     def show (self, name, host=None, encryption=None, parent=None):
00548         self.parent = parent
00549         self._host = host
00550         self._encryption = encryption
00551         if not host:
00552             self._host = cups.getServer()
00553         if not encryption:
00554             self._encryption = cups.getEncryption ()
00555 
00556         if self._monitor == None:
00557             self.set_monitor (monitor.Monitor (monitor_jobs=False))
00558 
00559         self._ppdcache = self._monitor.get_ppdcache ()
00560 
00561         self._disconnect ("newPrinterGUI")
00562         self.newPrinterGUI = newprinter.NewPrinterGUI ()
00563         self._connect ("newPrinterGUI", self.newPrinterGUI,
00564                        "printer-modified", self.on_printer_modified)
00565         if parent:
00566             self.dialog.set_transient_for (parent)
00567 
00568         self.load (name, host=host, encryption=encryption, parent=parent)
00569         if not self.printer:
00570             return
00571 
00572         for button in [self.btnPrinterPropertiesCancel,
00573                        self.btnPrinterPropertiesOK,
00574                        self.btnPrinterPropertiesApply]:
00575             if self.printer.discovered:
00576                 button.hide ()
00577             else:
00578                 button.show ()
00579         if self.printer.discovered:
00580             self.btnPrinterPropertiesClose.show ()
00581         else:
00582             self.btnPrinterPropertiesClose.hide ()
00583         self.setDataButtonState ()
00584         self.btnPrintTestPage.set_tooltip_text(_("CUPS test page"))
00585         self.btnSelfTest.set_tooltip_text(_("Typically shows whether all jets "
00586                                             "on a print head are functioning "
00587                                             "and that the print feed mechanisms"
00588                                             " are working properly."))
00589         treeview = self.tvPrinterProperties
00590         treeview.set_cursor ((0,))
00591         host = CUPS_server_hostname ()
00592         self.dialog.set_title (_("Printer Properties - "
00593                                  "'%s' on %s") % (name, host))
00594         self.dialog.show ()
00595 
00596     def printer_properties_response (self, dialog, response):
00597         if not self.printer:
00598             response = gtk.RESPONSE_CANCEL
00599 
00600         if response == gtk.RESPONSE_REJECT:
00601             # The Conflict button was pressed.
00602             message = _("There are conflicting options.\n"
00603                         "Changes can only be applied after\n"
00604                         "these conflicts are resolved.")
00605             message += "\n\n"
00606             for option in self.conflicts:
00607                 message += option.option.text + "\n"
00608 
00609             dialog = gtk.MessageDialog(self.dialog,
00610                                        gtk.DIALOG_DESTROY_WITH_PARENT |
00611                                        gtk.DIALOG_MODAL,
00612                                        gtk.MESSAGE_WARNING,
00613                                        gtk.BUTTONS_CLOSE,
00614                                        message)
00615             dialog.run()
00616             dialog.destroy()
00617             return
00618 
00619         if (response == gtk.RESPONSE_OK or
00620             response == gtk.RESPONSE_APPLY):
00621             if (response == gtk.RESPONSE_OK and len (self.changed) == 0):
00622                 failed = False
00623             else:
00624                 failed = self.save_printer (self.printer)
00625 
00626         if response == gtk.RESPONSE_APPLY and not failed:
00627             try:
00628                 self.load (self.printer.name)
00629             except:
00630                 pass
00631 
00632             self.setDataButtonState ()
00633 
00634         if ((response == gtk.RESPONSE_OK and not failed) or
00635             response == gtk.RESPONSE_CANCEL):
00636             self.ppd = None
00637             self.ppd_local = None
00638             self.printer = None
00639             dialog.hide ()
00640             self.emit ('dialog-closed')
00641 
00642             if self.newPrinterGUI.NewPrinterWindow.get_property ("visible"):
00643                 self.newPrinterGUI.on_NPCancel (None)
00644 
00645     # Data handling
00646 
00647     def on_delete(self, dialog, event):
00648         self.printer_properties_response (dialog, gtk.RESPONSE_CANCEL)
00649 
00650     def on_printer_changed(self, widget):
00651         if isinstance(widget, gtk.CheckButton):
00652             value = widget.get_active()
00653         elif isinstance(widget, gtk.Entry):
00654             value = widget.get_text()
00655         elif isinstance(widget, gtk.RadioButton):
00656             value = widget.get_active()
00657         elif isinstance(widget, gtk.ComboBox):
00658             model = widget.get_model ()
00659             iter = widget.get_active_iter()
00660             value = model.get_value (iter, 1)
00661         else:
00662             raise ValueError, "Widget type not supported (yet)"
00663 
00664         p = self.printer
00665         old_values = {
00666             self.entPDescription : p.info,
00667             self.entPLocation : p.location,
00668             self.entPDevice : p.device_uri,
00669             self.chkPEnabled : p.enabled,
00670             self.chkPAccepting : not p.rejecting,
00671             self.chkPShared : p.is_shared,
00672             self.cmbPStartBanner : p.job_sheet_start,
00673             self.cmbPEndBanner : p.job_sheet_end,
00674             self.cmbPErrorPolicy : p.error_policy,
00675             self.cmbPOperationPolicy : p.op_policy,
00676             self.rbtnPAllow: p.default_allow,
00677             }
00678 
00679         old_value = old_values[widget]
00680 
00681         if old_value == value:
00682             self.changed.discard(widget)
00683         else:
00684             self.changed.add(widget)
00685         self.setDataButtonState()
00686 
00687     def option_changed(self, option):
00688         if option.is_changed():
00689             self.changed.add(option)
00690         else:
00691             self.changed.discard(option)
00692 
00693         if option.conflicts:
00694             self.conflicts.add(option)
00695         else:
00696             self.conflicts.discard(option)
00697         self.setDataButtonState()
00698 
00699         if (self.option_manualfeed and self.option_inputslot and
00700             option == self.option_manualfeed):
00701             if option.get_current_value() == "True":
00702                 self.option_inputslot.disable ()
00703             else:
00704                 self.option_inputslot.enable ()
00705 
00706     # Access control
00707     def getPUsers(self):
00708         """return list of usernames from the GUI"""
00709         model = self.tvPUsers.get_model()
00710         result = []
00711         model.foreach(lambda model, path, iter:
00712                       result.append(model.get(iter, 0)[0]))
00713         result.sort()
00714         return result
00715 
00716     def setPUsers(self, users):
00717         """write list of usernames inot the GUI"""
00718         model = self.tvPUsers.get_model()
00719         model.clear()
00720         for user in users:
00721             model.append((user,))
00722 
00723         self.on_entPUser_changed(self.entPUser)
00724         self.on_tvPUsers_cursor_changed(self.tvPUsers)
00725 
00726     def checkPUsersChanged(self):
00727         """check if users in GUI and printer are different
00728         and set self.changed"""
00729         if not self.printer:
00730             return
00731 
00732         if self.getPUsers() != self.printer.except_users:
00733             self.changed.add(self.tvPUsers)
00734         else:
00735             self.changed.discard(self.tvPUsers)
00736 
00737         self.on_tvPUsers_cursor_changed(self.tvPUsers)
00738         self.setDataButtonState()
00739 
00740     def on_btnPAddUser_clicked(self, button):
00741         user = self.entPUser.get_text()
00742         if user:
00743             self.tvPUsers.get_model().insert(0, (user,))
00744             self.entPUser.set_text("")
00745         self.checkPUsersChanged()
00746 
00747     def on_btnPDelUser_clicked(self, button):
00748         model, rows = self.tvPUsers.get_selection().get_selected_rows()
00749         rows = [gtk.TreeRowReference(model, row) for row in rows]
00750         for row in rows:
00751             path = row.get_path()
00752             iter = model.get_iter(path)
00753             model.remove(iter)
00754         self.checkPUsersChanged()
00755 
00756     def on_entPUser_changed(self, widget):
00757         self.btnPAddUser.set_sensitive(bool(widget.get_text()))
00758 
00759     def on_tvPUsers_cursor_changed(self, widget):
00760         model, rows = widget.get_selection().get_selected_rows()
00761         self.btnPDelUser.set_sensitive(bool(rows))
00762 
00763     # Server side options
00764     def on_job_option_reset(self, button):
00765         option = self.job_options_buttons[button]
00766         option.reset ()
00767         # Remember to set this option for removal in the IPP request.
00768         if self.server_side_options.has_key (option.name):
00769             del self.server_side_options[option.name]
00770         if option.is_changed ():
00771             self.changed.add(option)
00772         else:
00773             self.changed.discard(option)
00774         self.setDataButtonState()
00775 
00776     def on_job_option_changed(self, widget):
00777         if not self.printer:
00778             return
00779         option = self.job_options_widgets[widget]
00780         option.changed ()
00781         if option.is_changed ():
00782             self.server_side_options[option.name] = option
00783             self.changed.add(option)
00784         else:
00785             if self.server_side_options.has_key (option.name):
00786                 del self.server_side_options[option.name]
00787             self.changed.discard(option)
00788         self.setDataButtonState()
00789         # Don't set the reset button insensitive if the option hasn't
00790         # changed from the original value: it's still meaningful to
00791         # reset the option to the system default.
00792 
00793     def draw_other_job_options (self, editable=True):
00794         n = len (self.other_job_options)
00795         if n == 0:
00796             self.tblJOOther.hide_all ()
00797             return
00798 
00799         self.tblJOOther.resize (n, 3)
00800         children = self.tblJOOther.get_children ()
00801         for child in children:
00802             self.tblJOOther.remove (child)
00803         i = 0
00804         for opt in self.other_job_options:
00805             self.tblJOOther.attach (opt.label, 0, 1, i, i + 1,
00806                                     xoptions=gtk.FILL,
00807                                     yoptions=gtk.FILL)
00808             opt.label.set_alignment (0.0, 0.5)
00809             self.tblJOOther.attach (opt.selector, 1, 2, i, i + 1,
00810                                     xoptions=gtk.FILL,
00811                                     yoptions=0)
00812             opt.selector.set_sensitive (editable)
00813 
00814             btn = gtk.Button(stock=gtk.STOCK_REMOVE)
00815             btn.connect("clicked", self.on_btnJOOtherRemove_clicked)
00816             btn.set_data("pyobject", opt)
00817             btn.set_sensitive (editable)
00818             self.tblJOOther.attach(btn, 2, 3, i, i + 1,
00819                                    xoptions=0,
00820                                    yoptions=0)
00821             i += 1
00822 
00823         self.tblJOOther.show_all ()
00824 
00825     def add_job_option(self, name, value = "", supported = "", is_new=True,
00826                        editable=True):
00827         try:
00828             option = options.OptionWidget(name, value, supported,
00829                                           self.option_changed)
00830         except ValueError:
00831             # We can't deal with this option type for some reason.
00832             nonfatalException ()
00833             return
00834 
00835         option.is_new = is_new
00836         self.other_job_options.append (option)
00837         self.draw_other_job_options (editable=editable)
00838         self.server_side_options[name] = option
00839         if name in self.changed: # was deleted before
00840             option.is_new = False
00841         self.changed.add(option)
00842         self.setDataButtonState()
00843         if is_new:
00844             option.selector.grab_focus ()
00845 
00846     def on_btnJOOtherRemove_clicked(self, button):
00847         option = button.get_data("pyobject")
00848         self.other_job_options.remove (option)
00849         self.draw_other_job_options ()
00850         if option.is_new:
00851             self.changed.discard(option)
00852         else:
00853             # keep name as reminder that option got deleted
00854             self.changed.add(option.name)
00855         del self.server_side_options[option.name]
00856         self.setDataButtonState()
00857 
00858     def on_btnNewJobOption_clicked(self, button):
00859         name = self.entNewJobOption.get_text()
00860         self.add_job_option(name)
00861         self.tblJOOther.show_all()
00862         self.entNewJobOption.set_text ('')
00863         self.btnNewJobOption.set_sensitive (False)
00864         self.setDataButtonState()
00865 
00866     def on_entNewJobOption_changed(self, widget):
00867         text = self.entNewJobOption.get_text()
00868         active = (len(text) > 0) and text not in self.server_side_options
00869         self.btnNewJobOption.set_sensitive(active)
00870 
00871     def on_entNewJobOption_activate(self, widget):
00872         self.on_btnNewJobOption_clicked (widget) # wrong widget but ok
00873 
00874     # set buttons sensitivity
00875     def setDataButtonState(self):
00876         try:
00877             attrs = self.printer.other_attributes
00878             formats = attrs.get('document-format-supported', [])
00879             printable = (not bool (self.changed) and
00880                          self.printer.enabled and
00881                          not self.printer.rejecting)
00882             try:
00883                 formats.index ('application/postscript')
00884                 testpage = printable
00885             except ValueError:
00886                 # PostScript not accepted
00887                 testpage = False
00888 
00889             self.btnPrintTestPage.set_sensitive (testpage)
00890             adjustable = not (self.printer.discovered or bool (self.changed))
00891             for button in [self.btnChangePPD,
00892                            self.btnSelectDevice]:
00893                 button.set_sensitive (adjustable)
00894 
00895             selftest = False
00896             cleanheads = False
00897             if (printable and
00898                 (self.printer.type & cups.CUPS_PRINTER_COMMANDS) != 0):
00899                 try:
00900                     # Is the command format supported?
00901                     formats.index ('application/vnd.cups-command')
00902 
00903                     # Yes...
00904                     commands = attrs.get('printer-commands', [])
00905                     for command in commands:
00906                         if command == "PrintSelfTestPage":
00907                             selftest = True
00908                             if cleanheads:
00909                                 break
00910 
00911                         elif command == "Clean":
00912                             cleanheads = True
00913                             if selftest:
00914                                 break
00915                 except ValueError:
00916                     # Command format not supported.
00917                     pass
00918 
00919             for cond, button in [(selftest, self.btnSelfTest),
00920                                  (cleanheads, self.btnCleanHeads)]:
00921                 if cond:
00922                     button.show ()
00923                 else:
00924                     button.hide ()
00925         except:
00926             nonfatalException()
00927 
00928         installablebold = False
00929         optionsbold = False
00930         if self.conflicts:
00931             debugprint ("Conflicts detected")
00932             self.btnConflict.show()
00933             for option in self.conflicts:
00934                 if option.tab_label.get_text () == self.lblPInstallOptions.get_text ():
00935                     installablebold = True
00936                 else:
00937                     optionsbold = True
00938         else:
00939             self.btnConflict.hide()
00940         installabletext = _("Installable Options")
00941         optionstext = _("Printer Options")
00942         if installablebold:
00943             installabletext = "<b>%s</b>" % installabletext
00944         if optionsbold:
00945             optionstext = "<b>%s</b>" % optionstext
00946         self.lblPInstallOptions.set_markup (installabletext)
00947         self.lblPOptions.set_markup (optionstext)
00948 
00949         store = self.tvPrinterProperties.get_model ()
00950         if store:
00951             for n in range (self.ntbkPrinter.get_n_pages ()):
00952                 page = self.ntbkPrinter.get_nth_page (n)
00953                 label = self.ntbkPrinter.get_tab_label (page).get_text ()
00954                 try:
00955                     if label == self.lblPInstallOptions.get_text():
00956                         iter = store.get_iter ((n,))
00957                         store.set_value (iter, 0, installabletext)
00958                     elif label == self.lblPOptions.get_text ():
00959                         iter = store.get_iter ((n,))
00960                         store.set_value (iter, 0, optionstext)
00961                 except ValueError:
00962                     # If we get here, the store has not yet been set
00963                     # up (trac #111).
00964                     pass
00965 
00966         self.btnPrinterPropertiesApply.set_sensitive (len (self.changed) > 0 and
00967                                                       not self.conflicts)
00968         self.btnPrinterPropertiesOK.set_sensitive (not self.conflicts)
00969 
00970     def save_printer(self, printer, saveall=False, parent=None):
00971         if parent == None:
00972             parent = self.dialog
00973         class_deleted = False
00974         name = printer.name
00975 
00976         if printer.is_class:
00977             self.cups._begin_operation (_("modifying class %s") % name)
00978         else:
00979             self.cups._begin_operation (_("modifying printer %s") % name)
00980 
00981         try:
00982             if not printer.is_class and self.ppd:
00983                 self.getPrinterSettings()
00984                 if self.ppd.nondefaultsMarked() or saveall:
00985                     self.cups.addPrinter(name, ppd=self.ppd)
00986 
00987             if printer.is_class:
00988                 # update member list
00989                 new_members = newprinter.getCurrentClassMembers(self.tvClassMembers)
00990                 if not new_members:
00991                     dialog = gtk.MessageDialog(
00992                         flags=0, type=gtk.MESSAGE_WARNING,
00993                         buttons=gtk.BUTTONS_NONE,
00994                         message_format=_("This will delete this class!"))
00995                     dialog.format_secondary_text(_("Proceed anyway?"))
00996                     dialog.add_buttons (gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
00997                                         gtk.STOCK_DELETE, gtk.RESPONSE_YES)
00998                     result = dialog.run()
00999                     dialog.destroy()
01000                     if result==gtk.RESPONSE_NO:
01001                         self.cups._end_operation ()
01002                         return True
01003                     class_deleted = True
01004 
01005                 # update member list
01006                 old_members = printer.class_members[:]
01007 
01008                 for member in new_members:
01009                     if member in old_members:
01010                         old_members.remove(member)
01011                     else:
01012                         self.cups.addPrinterToClass(member, name)
01013                 for member in old_members:
01014                     self.cups.deletePrinterFromClass(member, name)
01015 
01016             location = self.entPLocation.get_text()
01017             info = self.entPDescription.get_text()
01018             device_uri = self.entPDevice.get_text()
01019 
01020             enabled = self.chkPEnabled.get_active()
01021             accepting = self.chkPAccepting.get_active()
01022             shared = self.chkPShared.get_active()
01023 
01024             if info!=printer.info or saveall:
01025                 self.cups.setPrinterInfo(name, info)
01026             if location!=printer.location or saveall:
01027                 self.cups.setPrinterLocation(name, location)
01028             if (not printer.is_class and
01029                 (device_uri!=printer.device_uri or saveall)):
01030                 self.cups.setPrinterDevice(name, device_uri)
01031 
01032             if enabled != printer.enabled or saveall:
01033                 printer.setEnabled(enabled)
01034             if accepting == printer.rejecting or saveall:
01035                 printer.setAccepting(accepting)
01036             if shared != printer.is_shared or saveall:
01037                 printer.setShared(shared)
01038 
01039             def get_combo_value (cmb):
01040                 model = cmb.get_model ()
01041                 iter = cmb.get_active_iter ()
01042                 return model.get_value (iter, 1)
01043 
01044             job_sheet_start = get_combo_value (self.cmbPStartBanner)
01045             job_sheet_end = get_combo_value (self.cmbPEndBanner)
01046             error_policy = get_combo_value (self.cmbPErrorPolicy)
01047             op_policy = get_combo_value (self.cmbPOperationPolicy)
01048 
01049             if (job_sheet_start != printer.job_sheet_start or
01050                 job_sheet_end != printer.job_sheet_end) or saveall:
01051                 printer.setJobSheets(job_sheet_start, job_sheet_end)
01052             if error_policy != printer.error_policy or saveall:
01053                 printer.setErrorPolicy(error_policy)
01054             if op_policy != printer.op_policy or saveall:
01055                 printer.setOperationPolicy(op_policy)
01056 
01057             default_allow = self.rbtnPAllow.get_active()
01058             except_users = self.getPUsers()
01059 
01060             if (default_allow != printer.default_allow or
01061                 except_users != printer.except_users) or saveall:
01062                 printer.setAccess(default_allow, except_users)
01063 
01064             for option in printer.attributes:
01065                 if option not in self.server_side_options:
01066                     printer.unsetOption(option)
01067             for option in self.server_side_options.itervalues():
01068                 if (option.is_changed() or
01069                     (saveall and
01070                      option.get_current_value () != option.get_default())):
01071                     debugprint ("Set %s = %s" % (option.name,
01072                                                  option.get_current_value()))
01073                     printer.setOption(option.name, option.get_current_value())
01074 
01075         except cups.IPPError, (e, s):
01076             show_IPP_Error(e, s, parent)
01077             self.cups._end_operation ()
01078             return True
01079         self.cups._end_operation ()
01080         self.changed = set() # of options
01081 
01082         if not self.cups._use_pk and not self.__dict__.has_key ("server_settings"):
01083             # We can authenticate with the server correctly at this point,
01084             # but we have never fetched the server settings to see whether
01085             # the server is publishing shared printers.  Fetch the settings
01086             # now so that we can update the "not published" label if necessary.
01087             self.cups._begin_operation (_("fetching server settings"))
01088             try:
01089                 self.server_settings = self.cups.adminGetServerSettings()
01090             except:
01091                 nonfatalException()
01092 
01093             self.cups._end_operation ()
01094 
01095         if not class_deleted:
01096             # Update our copy of the printer's settings.
01097             try:
01098                 printer.getAttributes ()
01099                 self.updatePrinterProperties ()
01100             except cups.IPPError:
01101                 pass
01102 
01103         self._monitor.update ()
01104         return False
01105 
01106     def getPrinterSettings(self):
01107         #self.ppd.markDefaults()
01108         for option in self.options.itervalues():
01109             option.writeback()
01110 
01111     ### Printer Properties tree view signal handlers
01112     def on_tvPrinterProperties_selection_changed (self, selection):
01113         # Prevent selection from being de-selected.
01114         (model, iter) = selection.get_selected ()
01115         if iter:
01116             self.printer_properties_last_iter_selected = iter
01117         else:
01118             try:
01119                 iter = self.printer_properties_last_iter_selected
01120             except AttributeError:
01121                 # Not set yet.
01122                 return
01123 
01124             if model.iter_is_valid (iter):
01125                 selection.select_iter (iter)
01126 
01127     def on_tvPrinterProperties_cursor_changed (self, treeview):
01128         # Adjust notebook to reflect selected item.
01129         (path, column) = treeview.get_cursor ()
01130         if path != None:
01131             model = treeview.get_model ()
01132             iter = model.get_iter (path)
01133             n = model.get_value (iter, 1)
01134             self.ntbkPrinter.set_current_page (n)
01135 
01136     # print test page
01137 
01138     def printTestPage (self):
01139         self.btnPrintTestPage.clicked ()
01140 
01141     def on_btnPrintTestPage_clicked(self, button):
01142         printer = self.printer
01143         if not printer:
01144             # Printer has been deleted meanwhile
01145             return
01146 
01147         # if we have a page size specific custom test page, use it;
01148         # otherwise use cups' default one
01149         custom_testpage = None
01150         if self.ppd != False:
01151             opt = self.ppd.findOption ("PageSize")
01152             if opt:
01153                 custom_testpage = os.path.join(pkgdata,
01154                                                'testpage-%s.ps' %
01155                                                opt.defchoice.lower())
01156 
01157         # Connect as the current user so that the test page can be managed
01158         # as a normal job.
01159         user = cups.getUser ()
01160         cups.setUser ('')
01161         try:
01162             c = authconn.Connection (self.parent, try_as_root=False,
01163                                      host=self._host,
01164                                      encryption=self._encryption)
01165         except RuntimeError, s:
01166             show_IPP_Error (None, s, self.parent)
01167             return
01168 
01169         job_id = None
01170         c._begin_operation (_("printing test page"))
01171         try:
01172             if custom_testpage and os.path.exists(custom_testpage):
01173                 debugprint ('Printing custom test page ' + custom_testpage)
01174                 job_id = c.printTestPage(printer.name,
01175                                          file=custom_testpage)
01176             else:
01177                 debugprint ('Printing default test page')
01178                 job_id = c.printTestPage(printer.name)
01179         except cups.IPPError, (e, msg):
01180             if (e == cups.IPP_NOT_AUTHORIZED and
01181                 self._host != 'localhost' and
01182                 self._host[0] != '/'):
01183                 show_error_dialog (_("Not possible"),
01184                                    _("The remote server did not accept "
01185                                      "the print job, most likely "
01186                                      "because the printer is not "
01187                                      "shared."),
01188                                    self.parent)
01189             else:
01190                 show_IPP_Error(e, msg, self.parent)
01191 
01192         c._end_operation ()
01193         cups.setUser (user)
01194 
01195         if job_id != None:
01196             show_info_dialog (_("Submitted"),
01197                               _("Test page submitted as job %d") % job_id,
01198                               parent=self.parent)
01199 
01200     def maintenance_command (self, command):
01201         printer = self.printer
01202         if not printer:
01203             # Printer has been deleted meanwhile
01204             return
01205 
01206         (tmpfd, tmpfname) = tempfile.mkstemp ()
01207         os.write (tmpfd, "#CUPS-COMMAND\n%s\n" % command)
01208         os.close (tmpfd)
01209         self.cups._begin_operation (_("sending maintenance command"))
01210         try:
01211             format = "application/vnd.cups-command"
01212             job_id = self.cups.printTestPage (printer.name,
01213                                               format=format,
01214                                               file=tmpfname,
01215                                               user=cups.getUser ())
01216             show_info_dialog (_("Submitted"),
01217                               _("Maintenance command submitted as "
01218                                 "job %d") % job_id,
01219                               parent=self.parent)
01220         except cups.IPPError, (e, msg):
01221             if (e == cups.IPP_NOT_AUTHORIZED and
01222                 self.printer.name != 'localhost'):
01223                 show_error_dialog (_("Not possible"),
01224                                    _("The remote server did not accept "
01225                                      "the print job, most likely "
01226                                      "because the printer is not "
01227                                      "shared."),
01228                                    self.parent)
01229             else:
01230                 show_IPP_Error(e, msg, self.parent)
01231 
01232         self.cups._end_operation ()
01233 
01234         os.unlink (tmpfname)
01235 
01236     def on_btnSelfTest_clicked(self, button):
01237         self.maintenance_command ("PrintSelfTestPage")
01238 
01239     def on_btnCleanHeads_clicked(self, button):
01240         self.maintenance_command ("Clean all")
01241 
01242     def fillComboBox(self, combobox, values, value, translationdict=None):
01243         if translationdict == None:
01244             translationdict = ppdippstr.TranslationDict ()
01245 
01246         model = gtk.ListStore (gobject.TYPE_STRING,
01247                                gobject.TYPE_STRING)
01248         combobox.set_model (model)
01249         set_active = False
01250         for nr, val in enumerate(values):
01251             model.append ([(translationdict.get (val)), val])
01252             if val == value:
01253                 combobox.set_active(nr)
01254                 set_active = True
01255 
01256         if not set_active:
01257             combobox.set_active (0)
01258 
01259     def load (self, name, host=None, encryption=None, parent=None):
01260         self.changed = set() # of options
01261         self.options = {} # keyword -> Option object
01262         self.conflicts = set() # of options
01263 
01264         if not host:
01265             host = cups.getServer()
01266         if not encryption:
01267             encryption = cups.getEncryption ()
01268 
01269         c = authconn.Connection (parent=self.dialog,
01270                                  host=host,
01271                                  encryption=encryption)
01272         self.cups = c
01273 
01274         printer = cupshelpers.Printer (name, self.cups)
01275         self.printer = printer
01276         try:
01277             # CUPS 1.4
01278             publishing = printer.other_attributes['server-is-sharing-printers']
01279             self.server_is_publishing = publishing
01280         except KeyError:
01281             pass
01282 
01283         editable = not self.printer.discovered
01284 
01285         try:
01286             self.ppd = printer.getPPD()
01287             self.ppd_local = printer.getPPD()
01288             if self.ppd_local != False:
01289                 self.ppd_local.localize()
01290         except cups.IPPError, (e, m):
01291             # We might get IPP_INTERNAL_ERROR if this is a memberless
01292             # class.
01293             if e != cups.IPP_INTERNAL_ERROR:
01294                 # Some IPP error other than IPP_NOT_FOUND.
01295                 show_IPP_Error(e, m, self.parent)
01296 
01297             # Treat it as a raw queue.
01298             self.ppd = False
01299         except RuntimeError, e:
01300             # Either the underlying cupsGetPPD2() function returned
01301             # NULL without setting an IPP error (so it'll be something
01302             # like a failed connection), or the PPD could not be parsed.
01303             if e.message.startswith ("ppd"):
01304                 show_error_dialog (_("Error"),
01305                                    _("The PPD file for this queue "
01306                                      "is damaged."),
01307                                    self.parent)
01308             else:
01309                 show_error_dialog (_("Error"),
01310                                    _("There was a problem connecting to "
01311                                      "the CUPS server."),
01312                                    self.parent)
01313             raise
01314 
01315         for widget in (self.entPDescription, self.entPLocation,
01316                        self.entPDevice):
01317             widget.set_editable(editable)
01318 
01319         for widget in (self.btnSelectDevice, self.btnChangePPD,
01320                        self.chkPEnabled, self.chkPAccepting, self.chkPShared,
01321                        self.cmbPStartBanner, self.cmbPEndBanner,
01322                        self.cmbPErrorPolicy, self.cmbPOperationPolicy,
01323                        self.rbtnPAllow, self.rbtnPDeny, self.tvPUsers,
01324                        self.entPUser, self.btnPAddUser, self.btnPDelUser):
01325             widget.set_sensitive(editable)
01326 
01327         # Description page
01328         self.entPDescription.set_text(printer.info)
01329         self.entPLocation.set_text(printer.location)
01330 
01331         uri = printer.device_uri
01332         self.entPDevice.set_text(uri)
01333         self.changed.discard(self.entPDevice)
01334 
01335         # Hide make/model and Device URI for classes
01336         for widget in (self.lblPMakeModel2, self.entPMakeModel,
01337                        self.btnChangePPD, self.lblPDevice2,
01338                        self.entPDevice, self.btnSelectDevice):
01339             if printer.is_class:
01340                 widget.hide()
01341             else:
01342                 widget.show()
01343 
01344 
01345         # Policy tab
01346         # ----------
01347 
01348         try:
01349             if printer.is_shared:
01350                 if self.server_is_publishing:
01351                     self.lblNotPublished.hide_all ()
01352                 else:
01353                     self.lblNotPublished.show_all ()
01354             else:
01355                 self.lblNotPublished.hide_all ()
01356         except:
01357             nonfatalException()
01358             self.lblNotPublished.hide_all ()
01359 
01360         # Job sheets
01361         self.cmbPStartBanner.set_sensitive(editable)
01362         self.cmbPEndBanner.set_sensitive(editable)
01363 
01364         # Policies
01365         self.cmbPErrorPolicy.set_sensitive(editable)
01366         self.cmbPOperationPolicy.set_sensitive(editable)
01367 
01368         # Access control
01369         self.entPUser.set_text("")
01370 
01371         # Server side options (Job options)
01372         self.server_side_options = {}
01373         for option in self.job_options_widgets.values ():
01374             if option.name == "media" and self.ppd:
01375                 # Slightly special case because the 'system default'
01376                 # (i.e. what you get when you press Reset) depends
01377                 # on the printer's PageSize.
01378                 opt = self.ppd.findOption ("PageSize")
01379                 if opt:
01380                     option.set_default (opt.defchoice)
01381 
01382             option_editable = editable
01383             try:
01384                 value = self.printer.attributes[option.name]
01385             except KeyError:
01386                 option.reinit (None)
01387             else:
01388                 try:
01389                     if self.printer.possible_attributes.has_key (option.name):
01390                         supported = self.printer.\
01391                                     possible_attributes[option.name][1]
01392                         # Set the option widget.
01393                         # In CUPS 1.3.x the orientation-requested-default
01394                         # attribute may have the value None; this means there
01395                         # is no value set.  This suits our needs here, as None
01396                         # resets the option to the system default and makes the
01397                         # Reset button insensitive.
01398                         option.reinit (value, supported=supported)
01399                     else:
01400                         option.reinit (value)
01401 
01402                     self.server_side_options[option.name] = option
01403                 except:
01404                     option_editable = False
01405                     show_error_dialog (_("Error"),
01406                                        _("Option '%s' has value '%s' "
01407                                          "and cannot be edited.") %
01408                                        (option.name, value),
01409                                        self.parent)
01410             option.widget.set_sensitive (option_editable)
01411             if not editable:
01412                 option.button.set_sensitive (False)
01413         self.other_job_options = []
01414         self.draw_other_job_options (editable=editable)
01415         for option in self.printer.attributes.keys ():
01416             if self.server_side_options.has_key (option):
01417                 continue
01418             if option == "output-mode":
01419                 # Not settable
01420                 continue
01421             value = self.printer.attributes[option]
01422             if self.printer.possible_attributes.has_key (option):
01423                 supported = self.printer.possible_attributes[option][1]
01424             else:
01425                 if isinstance (value, bool):
01426                     supported = ["true", "false"]
01427                     value = str (value).lower ()
01428                 else:
01429                     supported = ""
01430                     value = str (value)
01431 
01432             self.add_job_option (option, value=value,
01433                                  supported=supported, is_new=False,
01434                                  editable=editable)
01435         self.entNewJobOption.set_text ('')
01436         self.entNewJobOption.set_sensitive (editable)
01437         self.btnNewJobOption.set_sensitive (False)
01438 
01439         if printer.is_class:
01440             # remove InstallOptions tab
01441             tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
01442             if tab_nr != -1:
01443                 self.ntbkPrinter.remove_page(tab_nr)
01444             self.fillClassMembers(editable)
01445         else:
01446             # real Printer
01447             self.fillPrinterOptions(name, editable)
01448 
01449         self.updateMarkerLevels()
01450         self.updateStateReasons()
01451         self.updatePrinterPropertiesTreeView()
01452 
01453         self.changed = set() # of options
01454         self.updatePrinterProperties ()
01455         self.setDataButtonState()
01456 
01457     def updatePrinterPropertiesTreeView (self):
01458         # Now update the tree view (which we use instead of the notebook tabs).
01459         store = gtk.ListStore (gobject.TYPE_STRING, gobject.TYPE_INT)
01460         self.ntbkPrinter.set_show_tabs (False)
01461         for n in range (self.ntbkPrinter.get_n_pages ()):
01462             page = self.ntbkPrinter.get_nth_page (n)
01463             label = self.ntbkPrinter.get_tab_label (page)
01464             iter = store.append (None)
01465             store.set_value (iter, 0, label.get_text ())
01466             store.set_value (iter, 1, n)
01467         sel = self.tvPrinterProperties.get_selection ()
01468         self.tvPrinterProperties.set_model (store)
01469 
01470     def updateMarkerLevels (self):
01471         printer = self.printer
01472         if not printer:
01473             # Printer has been deleted meanwhile
01474             return
01475 
01476         # Marker levels
01477         for widget in self.vboxMarkerLevels.get_children ():
01478             self.vboxMarkerLevels.remove (widget)
01479 
01480         marker_info = dict()
01481         num_markers = 0
01482         for (attr, typ) in [('marker-colors', str),
01483                             ('marker-names', str),
01484                             ('marker-types', str),
01485                             ('marker-levels', float)]:
01486             val = printer.other_attributes.get (attr, [])
01487             if typ != str and len (val) > 0:
01488                 try:
01489                     # Can the value be coerced into the right type?
01490                     typ (val[0])
01491                 except TypeError, s:
01492                     debugprint ("%s value not coercible to %s: %s" %
01493                                 (attr, typ, s))
01494                     val = map (lambda x: 0.0, val)
01495 
01496             marker_info[attr] = val
01497             if num_markers == 0 or len (val) < num_markers:
01498                 num_markers = len (val)
01499 
01500         for attr in ['marker-colors', 'marker-names',
01501                      'marker-types', 'marker-levels']:
01502             if len (marker_info[attr]) > num_markers:
01503                 debugprint ("Trimming %s from %s" %
01504                             (marker_info[attr][num_markers:], attr))
01505                 del marker_info[attr][num_markers:]
01506 
01507         markers = map (lambda color, name, type, level:
01508                            (color, name, type, level),
01509                        marker_info['marker-colors'],
01510                        marker_info['marker-names'],
01511                        marker_info['marker-types'],
01512                        marker_info['marker-levels'])
01513         debugprint (markers)
01514 
01515         can_refresh = (printer.type & cups.CUPS_PRINTER_COMMANDS) != 0
01516         if can_refresh:
01517             self.btnRefreshMarkerLevels.show ()
01518         else:
01519             self.btnRefreshMarkerLevels.hide ()
01520 
01521         if len (markers) == 0:
01522             label = gtk.Label(_("Marker levels are not reported "
01523                                 "for this printer."))
01524             label.set_line_wrap (True)
01525             label.set_alignment (0.0, 0.0)
01526             self.vboxMarkerLevels.pack_start (label, False, False, 0)
01527         else:
01528             num_markers = 0
01529             cols = len (markers)
01530             rows = 1 + (cols - 1) / 4
01531             if cols > 4:
01532                 cols = 4
01533             table = gtk.Table (rows=rows,
01534                                columns=cols,
01535                                homogeneous=True)
01536             table.set_col_spacings (6)
01537             table.set_row_spacings (12)
01538             self.vboxMarkerLevels.pack_start (table)
01539             for color, name, marker_type, level in markers:
01540                 if name == None:
01541                     name = ''
01542                 elif self.ppd != False:
01543                     localized_name = self.ppd.localizeMarkerName(name)
01544                     if localized_name != None:
01545                         name = localized_name
01546 
01547                 row = num_markers / 4
01548                 col = num_markers % 4
01549 
01550                 vbox = gtk.VBox (spacing=6)
01551                 subhbox = gtk.HBox ()
01552                 inklevel = gtkinklevel.GtkInkLevel (color, level)
01553                 inklevel.set_tooltip_text ("%d%%" % level)
01554                 subhbox.pack_start (inklevel, True, False, 0)
01555                 vbox.pack_start (subhbox, False, False, 0)
01556                 label = gtk.Label (name)
01557                 label.set_width_chars (10)
01558                 label.set_line_wrap (True)
01559                 vbox.pack_start (label, False, False, 0)
01560                 table.attach (vbox, col, col + 1, row, row + 1)
01561                 num_markers += 1
01562 
01563         self.vboxMarkerLevels.show_all ()
01564 
01565     def on_btnRefreshMarkerLevels_clicked (self, button):
01566         self.maintenance_command ("ReportLevels")
01567 
01568     def updateStateReasons (self):
01569         printer = self.printer
01570         reasons = printer.other_attributes.get ('printer-state-reasons', [])
01571         store = gtk.ListStore (str, str)
01572         any = False
01573         for reason in reasons:
01574             if reason == "none":
01575                 break
01576 
01577             any = True
01578             iter = store.append (None)
01579             r = statereason.StateReason (printer.name, reason, self._ppdcache)
01580             if r.get_reason () == "paused":
01581                 icon = gtk.STOCK_MEDIA_PAUSE
01582             else:
01583                 icon = statereason.StateReason.LEVEL_ICON[r.get_level ()]
01584             store.set_value (iter, 0, icon)
01585             (title, text) = r.get_description ()
01586             store.set_value (iter, 1, text)
01587 
01588         self.tvPrinterStateReasons.set_model (store)
01589         page = 0
01590         if any:
01591             page = 1
01592 
01593         self.ntbkPrinterStateReasons.set_current_page (page)
01594 
01595     def set_printer_state_reason_icon (self, column, cell, model, iter, *data):
01596         icon = model.get_value (iter, 0)
01597         theme = gtk.icon_theme_get_default ()
01598         try:
01599             pixbuf = theme.load_icon (icon, 22, 0)
01600             cell.set_property ("pixbuf", pixbuf)
01601         except gobject.GError, exc:
01602             pass # Couldn't load icon
01603 
01604     def set_printer_state_reason_text (self, column, cell, model, iter, *data):
01605         cell.set_property ("text", model.get_value (iter, 1))
01606 
01607     def updatePrinterProperties(self):
01608         debugprint ("update printer properties")
01609         printer = self.printer
01610         self.entPMakeModel.set_text(printer.make_and_model)
01611         state = self.printer_states.get (printer.state, _("Unknown"))
01612         reason = printer.other_attributes.get ('printer-state-message', '')
01613         if len (reason) > 0:
01614             state += ' - ' + reason
01615         self.entPState.set_text(state)
01616         if len (self.changed) == 0:
01617             debugprint ("no changes yet: full printer properties update")
01618             # State
01619             self.chkPEnabled.set_active(printer.enabled)
01620             self.chkPAccepting.set_active(not printer.rejecting)
01621             self.chkPShared.set_active(printer.is_shared)
01622 
01623             # Job sheets
01624             self.fillComboBox(self.cmbPStartBanner,
01625                               printer.job_sheets_supported,
01626                               printer.job_sheet_start,
01627                               ppdippstr.job_sheets)
01628             self.fillComboBox(self.cmbPEndBanner, printer.job_sheets_supported,
01629                               printer.job_sheet_end,
01630                               ppdippstr.job_sheets)
01631 
01632             # Policies
01633             self.fillComboBox(self.cmbPErrorPolicy,
01634                               printer.error_policy_supported,
01635                               printer.error_policy,
01636                               ppdippstr.printer_error_policy)
01637             self.fillComboBox(self.cmbPOperationPolicy,
01638                               printer.op_policy_supported,
01639                               printer.op_policy,
01640                               ppdippstr.printer_op_policy)
01641 
01642             # Access control
01643             self.rbtnPAllow.set_active(printer.default_allow)
01644             self.rbtnPDeny.set_active(not printer.default_allow)
01645             self.setPUsers(printer.except_users)
01646 
01647             # Marker levels
01648             self.updateMarkerLevels ()
01649             self.updateStateReasons ()
01650 
01651             self.updatePrinterPropertiesTreeView ()
01652 
01653     def fillPrinterOptions(self, name, editable):
01654         # remove Class membership tab
01655         tab_nr = self.ntbkPrinter.page_num(self.algnClassMembers)
01656         if tab_nr != -1:
01657             self.ntbkPrinter.remove_page(tab_nr)
01658 
01659         # clean Installable Options Tab
01660         for widget in self.vbPInstallOptions.get_children():
01661             self.vbPInstallOptions.remove(widget)
01662 
01663         # clean Options Tab
01664         for widget in self.vbPOptions.get_children():
01665             self.vbPOptions.remove(widget)
01666 
01667         # insert Options Tab
01668         if self.ntbkPrinter.page_num(self.swPOptions) == -1:
01669             self.ntbkPrinter.insert_page(
01670                 self.swPOptions, self.lblPOptions, self.static_tabs)
01671 
01672         if not self.ppd:
01673             tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
01674             if tab_nr != -1:
01675                 self.ntbkPrinter.remove_page(tab_nr)
01676             tab_nr = self.ntbkPrinter.page_num(self.swPOptions)
01677             if tab_nr != -1:
01678                 self.ntbkPrinter.remove_page(tab_nr)
01679             return
01680         ppd = self.ppd
01681         ppd.markDefaults()
01682         self.ppd_local.markDefaults()
01683 
01684         hasInstallableOptions = False
01685 
01686         # build option tabs
01687         for group in self.ppd_local.optionGroups:
01688             if group.name == "InstallableOptions":
01689                 hasInstallableOptions = True
01690                 container = self.vbPInstallOptions
01691                 tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
01692                 if tab_nr == -1:
01693                     self.ntbkPrinter.insert_page(self.swPInstallOptions,
01694                                                  gtk.Label(group.text),
01695                                                  self.static_tabs)
01696                 tab_label = self.lblPInstallOptions
01697             else:
01698                 frame = gtk.Frame("<b>%s</b>" % ppdippstr.ppd.get (group.text))
01699                 frame.get_label_widget().set_use_markup(True)
01700                 frame.set_shadow_type (gtk.SHADOW_NONE)
01701                 self.vbPOptions.pack_start (frame, False, False, 0)
01702                 container = gtk.Alignment (0.5, 0.5, 1.0, 1.0)
01703                 # We want a left padding of 12, but there is a Table with
01704                 # spacing 6, and the left-most column of it (the conflict
01705                 # icon) is normally hidden, so just use 6 here.
01706                 container.set_padding (6, 12, 6, 0)
01707                 frame.add (container)
01708                 tab_label = self.lblPOptions
01709 
01710             table = gtk.Table(1, 3, False)
01711             table.set_col_spacings(6)
01712             table.set_row_spacings(6)
01713             container.add(table)
01714 
01715             rows = 0
01716 
01717             # InputSlot and ManualFeed need special handling.  With
01718             # libcups, if ManualFeed is True, InputSlot gets unset.
01719             # Likewise, if InputSlot is set, ManualFeed becomes False.
01720             # We handle it by toggling the sensitivity of InputSlot
01721             # based on ManualFeed.
01722             self.option_inputslot = self.option_manualfeed = None
01723 
01724             for nr, option in enumerate(group.options):
01725                 if option.keyword == "PageRegion":
01726                     continue
01727                 rows += 1
01728                 table.resize (rows, 3)
01729                 o = OptionWidget(option, ppd, self, tab_label=tab_label)
01730                 table.attach(o.conflictIcon, 0, 1, nr, nr+1, 0, 0, 0, 0)
01731 
01732                 hbox = gtk.HBox()
01733                 if o.label:
01734                     a = gtk.Alignment (0.5, 0.5, 1.0, 1.0)
01735                     a.set_padding (0, 0, 0, 6)
01736                     a.add (o.label)
01737                     table.attach(a, 1, 2, nr, nr+1, gtk.FILL, 0, 0, 0)
01738                     table.attach(hbox, 2, 3, nr, nr+1, gtk.FILL, 0, 0, 0)
01739                 else:
01740                     table.attach(hbox, 1, 3, nr, nr+1, gtk.FILL, 0, 0, 0)
01741                 hbox.pack_start(o.selector, False)
01742                 self.options[option.keyword] = o
01743                 o.selector.set_sensitive(editable)
01744                 if option.keyword == "InputSlot":
01745                     self.option_inputslot = o
01746                 elif option.keyword == "ManualFeed":
01747                     self.option_manualfeed = o
01748 
01749         # remove Installable Options tab if not needed
01750         if not hasInstallableOptions:
01751             tab_nr = self.ntbkPrinter.page_num(self.swPInstallOptions)
01752             if tab_nr != -1:
01753                 self.ntbkPrinter.remove_page(tab_nr)
01754 
01755         # check for conflicts
01756         for option in self.options.itervalues():
01757             conflicts = option.checkConflicts()
01758             if conflicts:
01759                 self.conflicts.add(option)
01760 
01761         self.swPInstallOptions.show_all()
01762         self.swPOptions.show_all()
01763 
01764     # Class members
01765 
01766     def fillClassMembers(self, editable):
01767         self.btnClassAddMember.set_sensitive(editable)
01768         self.btnClassDelMember.set_sensitive(editable)
01769 
01770         # remove Options tab
01771         tab_nr = self.ntbkPrinter.page_num(self.swPOptions)
01772         if tab_nr != -1:
01773             self.ntbkPrinter.remove_page(tab_nr)
01774 
01775         # insert Member Tab
01776         if self.ntbkPrinter.page_num(self.algnClassMembers) == -1:
01777             self.ntbkPrinter.insert_page(
01778                 self.algnClassMembers, self.lblClassMembers,
01779                 self.static_tabs)
01780 
01781         model_members = self.tvClassMembers.get_model()
01782         model_not_members = self.tvClassNotMembers.get_model()
01783         model_members.clear()
01784         model_not_members.clear()
01785 
01786         names = list (self._monitor.get_printers ())
01787         names.sort ()
01788         for name in names:
01789             if name != self.printer.name:
01790                 if name in self.printer.class_members:
01791                     model_members.append((name, ))
01792                 else:
01793                     model_not_members.append((name, ))
01794 
01795     def on_btnClassAddMember_clicked(self, button):
01796         newprinter.moveClassMembers(self.tvClassNotMembers,
01797                                     self.tvClassMembers)
01798         if newprinter.getCurrentClassMembers(self.tvClassMembers) != self.printer.class_members:
01799             self.changed.add(self.tvClassMembers)
01800         else:
01801             self.changed.discard(self.tvClassMembers)
01802         self.setDataButtonState()
01803 
01804     def on_btnClassDelMember_clicked(self, button):
01805         newprinter.moveClassMembers(self.tvClassMembers,
01806                                     self.tvClassNotMembers)
01807         if newprinter.getCurrentClassMembers(self.tvClassMembers) != self.printer.class_members:
01808             self.changed.add(self.tvClassMembers)
01809         else:
01810             self.changed.discard(self.tvClassMembers)
01811         self.setDataButtonState()
01812 
01813     # change device
01814     def on_btnSelectDevice_clicked(self, button):
01815         busy (self.dialog)
01816         self.newPrinterGUI.init("device", device_uri=self.printer.device_uri,
01817                                 name=self.printer.name,
01818                                 host=self._host,
01819                                 encryption=self._encryption,
01820                                 parent=self.dialog)
01821         ready (self.dialog)
01822 
01823     # change PPD
01824     def on_btnChangePPD_clicked(self, button):
01825         busy (self.dialog)
01826         self.newPrinterGUI.init("ppd", device_uri=self.printer.device_uri,
01827                                 ppd=self.ppd,
01828                                 name=self.printer.name,
01829                                 host=self._host,
01830                                 encryption=self._encryption,
01831                                 parent=self.dialog)
01832         ready (self.dialog)
01833 
01834     # NewPrinterGUI signal handlers
01835     def on_printer_modified (self, obj, name, ppd_has_changed):
01836         debugprint ("on_printer_modified called")
01837         if self.dialog.get_property ('visible') and self.printer:
01838             try:
01839                 self.printer.getAttributes ()
01840                 if ppd_has_changed:
01841                     self.load (name)
01842                 else:
01843                     self.updatePrinterProperties ()
01844 
01845             except cups.IPPError:
01846                 pass
01847 
01848     # Monitor signal handlers
01849     def on_printer_event (self, mon, printer, eventname, event):
01850         self.on_printer_modified (None, printer, False)
01851 
01852     def on_printer_removed (self, mon, printer):
01853         if (self.dialog.get_property ('visible') and
01854             self.printer and self.printer.name == printer):
01855             self.dialog.response (gtk.RESPONSE_CANCEL)
01856 
01857         if self.printer and self.printer.name == printer:
01858             self.printer = None
01859 
01860     def on_state_reason_added (self, mon, reason):
01861         if (self.dialog.get_property ('visible') and
01862             self.printer and self.printer.name == reason.get_printer ()):
01863             try:
01864                 self.printer.getAttributes ()
01865                 self.updatePrinterProperties ()
01866             except cups.IPPError:
01867                 pass
01868 
01869     def on_state_reason_removed (self, mon, reason):
01870         if (self.dialog.get_property ('visible') and
01871             self.printer and self.printer.name == reason.get_printer ()):
01872             try:
01873                 self.printer.getAttributes ()
01874                 self.updatePrinterProperties ()
01875             except cups.IPPError:
01876                 pass
01877 
01878     def on_cups_connection_error (self, mon):
01879         # FIXME: figure out how to handle this
01880         pass
01881 
01882 if __name__ == '__main__':
01883     import locale
01884     import sys
01885 
01886     if len (sys.argv) < 2:
01887         print "Specify queue name"
01888         sys.exit (1)
01889 
01890     set_debugging (True)
01891     os.environ["SYSTEM_CONFIG_PRINTER_UI"] = "ui"
01892     locale.setlocale (locale.LC_ALL, "")
01893     ppdippstr.init ()
01894     loop = gobject.MainLoop ()
01895     def on_dialog_closed (obj):
01896         obj.destroy ()
01897         loop.quit ()
01898 
01899     properties = PrinterPropertiesDialog ()
01900     properties.connect ('dialog-closed', on_dialog_closed)
01901     properties.show (sys.argv[1])
01902 
01903     loop.run ()