Back to index

system-config-printer  1.3.9+20120706
jobviewer.py
Go to the documentation of this file.
00001 
00002 ## Copyright (C) 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
00003 ## Authors:
00004 ##  Tim Waugh <twaugh@redhat.com>
00005 ##  Jiri Popelka <jpopelka@redhat.com>
00006 
00007 ## This program is free software; you can redistribute it and/or modify
00008 ## it under the terms of the GNU General Public License as published by
00009 ## the Free Software Foundation; either version 2 of the License, or
00010 ## (at your option) any later version.
00011 
00012 ## This program is distributed in the hope that it will be useful,
00013 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
00014 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015 ## GNU General Public License for more details.
00016 
00017 ## You should have received a copy of the GNU General Public License
00018 ## along with this program; if not, write to the Free Software
00019 ## Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00020 
00021 import asyncconn
00022 import authconn
00023 import cups
00024 import dbus
00025 import dbus.glib
00026 import dbus.service
00027 import pynotify
00028 import gettext
00029 import glib
00030 import gobject
00031 import gtk
00032 import gtk.gdk
00033 from gui import GtkGUI
00034 import monitor
00035 import os, shutil
00036 import pango
00037 import pwd
00038 import smburi
00039 import subprocess
00040 import sys
00041 import time
00042 import urllib
00043 from xml.sax import saxutils
00044 
00045 from debug import *
00046 import config
00047 import statereason
00048 import errordialogs
00049 
00050 cups.require("1.9.47")
00051 
00052 try:
00053     import gnomekeyring
00054     USE_KEYRING=True
00055 except ImportError:
00056     USE_KEYRING=False
00057 
00058 from gettext import gettext as _
00059 from statereason import StateReason
00060 
00061 pkgdata = config.pkgdatadir
00062 ICON="printer"
00063 ICON_SIZE=22
00064 SEARCHING_ICON="document-print-preview"
00065 
00066 # We need to call pynotify.init before we can check the server for caps
00067 pynotify.init('System Config Printer Notification')
00068 
00069 class PrinterURIIndex:
00070     def __init__ (self, names=[]):
00071         self.printer = {}
00072         self.names = names
00073         self._collect_names ()
00074 
00075     def _collect_names (self, connection=None):
00076         if not self.names:
00077             return
00078 
00079         if not connection:
00080             try:
00081                 c = cups.Connection ()
00082             except RuntimeError:
00083                 return
00084 
00085         for name in self.names:
00086             self.add_printer (name, connection=c)
00087 
00088         self.names = []
00089 
00090     def add_printer (self, printer, connection=None):
00091         try:
00092             self._map_printer (name=printer, connection=connection)
00093         except KeyError:
00094             return
00095 
00096     def update_from_attrs (self, printer, attrs):
00097         uris = []
00098         if attrs.has_key ('printer-uri-supported'):
00099             uri_supported = attrs['printer-uri-supported']
00100             if type (uri_supported) != list:
00101                 uri_supported = [uri_supported]
00102             uris.extend (uri_supported)
00103         if attrs.has_key ('notify-printer-uri'):
00104             uris.append (attrs['notify-printer-uri'])
00105         if attrs.has_key ('printer-more-info'):
00106             uris.append (attrs['printer-more-info'])
00107 
00108         for uri in uris:
00109             self.printer[uri] = printer
00110 
00111     def remove_printer (self, printer):
00112         # Remove references to this printer in the URI map.
00113         self._collect_names ()
00114         uris = self.printer.keys ()
00115         for uri in uris:
00116             if self.printer[uri] == printer:
00117                 del self.printer[uri]
00118 
00119     def lookup (self, uri, connection=None):
00120         self._collect_names ()
00121         try:
00122             return self.printer[uri]
00123         except KeyError:
00124             return self._map_printer (uri=uri, connection=connection)
00125 
00126     def all_printer_names (self):
00127         self._collect_names ()
00128         return set (self.printer.values ())
00129 
00130     def lookup_cached_by_name (self, name):
00131         self._collect_names ()
00132         for uri, printer in self.printer.iteritems ():
00133             if printer == name:
00134                 return uri
00135 
00136         raise KeyError
00137 
00138     def _map_printer (self, uri=None, name=None, connection=None):
00139         try:
00140             if connection == None:
00141                 connection = cups.Connection ()
00142 
00143             r = ['printer-name', 'printer-uri-supported', 'printer-more-info']
00144             if uri != None:
00145                 attrs = connection.getPrinterAttributes (uri=uri,
00146                                                          requested_attributes=r)
00147             else:
00148                 attrs = connection.getPrinterAttributes (name,
00149                                                          requested_attributes=r)
00150         except RuntimeError:
00151             # cups.Connection() failed
00152             raise KeyError
00153         except cups.IPPError:
00154             # URI not known.
00155             raise KeyError
00156 
00157         name = attrs['printer-name']
00158         self.update_from_attrs (name, attrs)
00159         if uri != None:
00160             self.printer[uri] = name
00161         return name
00162 
00163 
00164 class CancelJobsOperation(gobject.GObject):
00165     __gsignals__ = {
00166         'destroy':     (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
00167         'job-deleted': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00168                         [gobject.TYPE_INT]),
00169         'ipp-error':   (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
00170                         [gobject.TYPE_INT, gobject.TYPE_PYOBJECT]),
00171         'finished':    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
00172         }
00173 
00174     def __init__ (self, parent, host, port, encryption, jobids, purge_job):
00175         gobject.GObject.__init__ (self)
00176         self.jobids = list (jobids)
00177         self.purge_job = purge_job
00178         self.host = host
00179         self.port = port
00180         self.encryption = encryption
00181         if purge_job:
00182             if len(self.jobids) > 1:
00183                 dialog_title = _("Delete Jobs")
00184                 dialog_label = _("Do you really want to delete these jobs?")
00185             else:
00186                 dialog_title = _("Delete Job")
00187                 dialog_label = _("Do you really want to delete this job?")
00188         else:
00189             if len(self.jobids) > 1:
00190                 dialog_title = _("Cancel Jobs")
00191                 dialog_label = _("Do you really want to cancel these jobs?")
00192             else:
00193                 dialog_title = _("Cancel Job")
00194                 dialog_label = _("Do you really want to cancel this job?")
00195 
00196         dialog = gtk.Dialog (dialog_title, parent,
00197                              gtk.DIALOG_MODAL |
00198                              gtk.DIALOG_DESTROY_WITH_PARENT |
00199                              gtk.DIALOG_NO_SEPARATOR,
00200                              (_("Keep Printing"), gtk.RESPONSE_NO,
00201                               dialog_title, gtk.RESPONSE_YES))
00202         dialog.set_default_response (gtk.RESPONSE_NO)
00203         dialog.set_border_width (6)
00204         dialog.set_resizable (False)
00205         hbox = gtk.HBox (False, 12)
00206         image = gtk.Image ()
00207         image.set_from_stock (gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG)
00208         image.set_alignment (0.0, 0.0)
00209         hbox.pack_start (image, False, False, 0)
00210         label = gtk.Label (dialog_label)
00211         label.set_line_wrap (True)
00212         label.set_alignment (0.0, 0.0)
00213         hbox.pack_start (label, False, False, 0)
00214         dialog.vbox.pack_start (hbox, False, False, 0)
00215         dialog.connect ("response", self.on_job_cancel_prompt_response)
00216         dialog.connect ("delete-event", self.on_job_cancel_prompt_delete)
00217         dialog.show_all ()
00218         self.dialog = dialog
00219         self.connection = None
00220         debugprint ("+%s" % self)
00221 
00222     def __del__ (self):
00223         debugprint ("-%s" % self)
00224 
00225     def do_destroy (self):
00226         if self.connection:
00227             self.connection.destroy ()
00228             self.connection = None
00229 
00230         if self.dialog:
00231             self.dialog.destroy ()
00232             self.dialog = None
00233 
00234         debugprint ("DESTROY: %s" % self)
00235 
00236     def destroy (self):
00237         self.emit ('destroy')
00238 
00239     def on_job_cancel_prompt_delete (self, dialog, event):
00240         self.on_job_cancel_prompt_response (dialog, gtk.RESPONSE_NO)
00241 
00242     def on_job_cancel_prompt_response (self, dialog, response):
00243         dialog.destroy ()
00244         self.dialog = None
00245 
00246         if response != gtk.RESPONSE_YES:
00247             self.emit ('finished')
00248             return
00249 
00250         if len(self.jobids) == 0:
00251             self.emit ('finished')
00252             return
00253 
00254         asyncconn.Connection (host=self.host,
00255                               port=self.port,
00256                               encryption=self.encryption,
00257                               reply_handler=self._connected,
00258                               error_handler=self._connect_failed)
00259 
00260     def _connect_failed (self, connection, exc):
00261         debugprint ("CancelJobsOperation._connect_failed %s:%s" % (connection, repr (exc)))
00262 
00263     def _connected (self, connection, result):
00264         self.connection = connection
00265 
00266         if self.purge_job:
00267             operation = _("deleting job")
00268         else:
00269             operation = _("canceling job")
00270 
00271         self.connection._begin_operation (operation)
00272         self.connection.cancelJob (self.jobids[0], self.purge_job,
00273                                    reply_handler=self.cancelJob_finish,
00274                                    error_handler=self.cancelJob_error)
00275 
00276     def cancelJob_error (self, connection, exc):
00277         debugprint ("cancelJob_error %s:%s" % (connection, repr (exc)))
00278         if type (exc) == cups.IPPError:
00279             (e, m) = exc.args
00280             if (e != cups.IPP_NOT_POSSIBLE and
00281                 e != cups.IPP_NOT_FOUND):
00282                 self.emit ('ipp-error', self.jobids[0], exc)
00283             self.cancelJob_finish(connection, None)
00284         else:
00285             self.connection._end_operation ()
00286             self.connection.destroy ()
00287             self.connection = None
00288             self.emit ('ipp-error', self.jobids[0], exc)
00289             # Give up.
00290             self.emit ('finished')
00291             return
00292 
00293     def cancelJob_finish (self, connection, result):
00294         debugprint ("cancelJob_finish %s:%s" % (connection, repr (result)))
00295         self.emit ('job-deleted', self.jobids[0])
00296         del self.jobids[0]
00297         if not self.jobids:
00298             # Last job canceled.
00299             self.connection._end_operation ()
00300             self.connection.destroy ()
00301             self.connection = None
00302             self.emit ('finished')
00303             return
00304         else:
00305             # there are other jobs to cancel/delete
00306             connection.cancelJob (self.jobids[0], self.purge_job,
00307                                   reply_handler=self.cancelJob_finish,
00308                                   error_handler=self.cancelJob_error)
00309 
00310 gobject.type_register (CancelJobsOperation)
00311 
00312 class JobViewer (GtkGUI):
00313     required_job_attributes = set(['job-k-octets',
00314                                    'job-name',
00315                                    'job-originating-user-name',
00316                                    'job-printer-uri',
00317                                    'job-state',
00318                                    'time-at-creation',
00319                                    'job-preserved'])
00320 
00321     __gsignals__ = {
00322         'finished':    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
00323         }
00324 
00325     def __init__(self, bus=None, loop=None,
00326                  applet=False, suppress_icon_hide=False,
00327                  my_jobs=True, specific_dests=None,
00328                  parent=None):
00329         gobject.GObject.__init__ (self)
00330         self.loop = loop
00331         self.applet = applet
00332         self.suppress_icon_hide = suppress_icon_hide
00333         self.my_jobs = my_jobs
00334         self.specific_dests = specific_dests
00335         notify_caps = pynotify.get_server_caps ()
00336         self.notify_has_actions = "actions" in notify_caps
00337         self.notify_has_persistence = "persistence" in notify_caps
00338 
00339         self.jobs = {}
00340         self.jobiters = {}
00341         self.jobids = []
00342         self.jobs_attrs = {} # dict of jobid->(GtkListStore, page_index)
00343         self.active_jobs = set() # of job IDs
00344         self.stopped_job_prompts = set() # of job IDs
00345         self.printer_state_reasons = {}
00346         self.num_jobs_when_hidden = 0
00347         self.connecting_to_device = {} # dict of printer->time first seen
00348         self.state_reason_notifications = {}
00349         self.auth_info_dialogs = {} # by job ID
00350         self.job_creation_times_timer = None
00351         self.new_printer_notifications = {}
00352         self.completed_job_notifications = {}
00353         self.authenticated_jobs = set() # of job IDs
00354         self.ops = []
00355 
00356         self.getWidgets ({"JobsWindow":
00357                               ["JobsWindow",
00358                                "treeview",
00359                                "statusbar",
00360                                "toolbar"],
00361                           "statusicon_popupmenu":
00362                               ["statusicon_popupmenu"]},
00363 
00364                          domain=config.PACKAGE)
00365 
00366         job_action_group = gtk.ActionGroup ("JobActionGroup")
00367         job_action_group.add_actions ([
00368                 ("cancel-job", gtk.STOCK_CANCEL, _("_Cancel"), None,
00369                  _("Cancel selected jobs"), self.on_job_cancel_activate),
00370                 ("delete-job", gtk.STOCK_DELETE, _("_Delete"), None,
00371                  _("Delete selected jobs"), self.on_job_delete_activate),
00372                 ("hold-job", gtk.STOCK_MEDIA_PAUSE, _("_Hold"), None,
00373                  _("Hold selected jobs"), self.on_job_hold_activate),
00374                 ("release-job", gtk.STOCK_MEDIA_PLAY, _("_Release"), None,
00375                  _("Release selected jobs"), self.on_job_release_activate),
00376                 ("reprint-job", gtk.STOCK_REDO, _("Re_print"), None,
00377                  _("Reprint selected jobs"), self.on_job_reprint_activate),
00378                 ("retrieve-job", gtk.STOCK_SAVE_AS, _("Re_trieve"), None,
00379                  _("Retrieve selected jobs"), self.on_job_retrieve_activate),
00380                 ("move-job", None, _("_Move To"), None, None, None),
00381                 ("authenticate-job", None, _("_Authenticate"), None, None,
00382                  self.on_job_authenticate_activate),
00383                 ("job-attributes", None, _("_View Attributes"), None, None,
00384                  self.on_job_attributes_activate),
00385                 ("close", gtk.STOCK_CLOSE, None, "<ctrl>w",
00386                  _("Close this window"), self.on_delete_event)
00387                 ])
00388         self.job_ui_manager = gtk.UIManager ()
00389         self.job_ui_manager.insert_action_group (job_action_group, -1)
00390         self.job_ui_manager.add_ui_from_string (
00391 """
00392 <ui>
00393  <accelerator action="cancel-job"/>
00394  <accelerator action="delete-job"/>
00395  <accelerator action="hold-job"/>
00396  <accelerator action="release-job"/>
00397  <accelerator action="reprint-job"/>
00398  <accelerator action="retrieve-job"/>
00399  <accelerator action="move-job"/>
00400  <accelerator action="authenticate-job"/>
00401  <accelerator action="job-attributes"/>
00402  <accelerator action="close"/>
00403 </ui>
00404 """
00405 )
00406         self.job_ui_manager.ensure_update ()
00407         self.JobsWindow.add_accel_group (self.job_ui_manager.get_accel_group ())
00408         self.job_context_menu = gtk.Menu ()
00409         for action_name in ["cancel-job",
00410                             "delete-job",
00411                             "hold-job",
00412                             "release-job",
00413                             "reprint-job",
00414                             "retrieve-job",
00415                             "move-job",
00416                             None,
00417                             "authenticate-job",
00418                             "job-attributes"]:
00419             if not action_name:
00420                 item = gtk.SeparatorMenuItem ()
00421             else:
00422                 action = job_action_group.get_action (action_name)
00423                 action.set_sensitive (False)
00424                 item = action.create_menu_item ()
00425 
00426                 if action_name == 'move-job':
00427                     self.move_job_menuitem = item
00428                     printers = gtk.Menu ()
00429                     item.set_submenu (printers)
00430 
00431             item.show ()
00432             self.job_context_menu.append (item)
00433 
00434         for action_name in ["cancel-job",
00435                             "delete-job",
00436                             "hold-job",
00437                             "release-job",
00438                             "reprint-job",
00439                             "retrieve-job",
00440                             "close"]:
00441             action = job_action_group.get_action (action_name)
00442             action.set_sensitive (action_name == "close")
00443             action.set_is_important (action_name == "close")
00444             item = action.create_tool_item ()
00445             item.show ()
00446             self.toolbar.insert (item, -1)
00447 
00448         for skip, ellipsize, name, setter in \
00449                 [(False, False, _("Job"), self._set_job_job_number_text),
00450                  (True, False, _("User"), self._set_job_user_text),
00451                  (False, True, _("Document"), self._set_job_document_text),
00452                  (False, True, _("Printer"), self._set_job_printer_text),
00453                  (False, False, _("Size"), self._set_job_size_text)]:
00454             if applet and skip:
00455                 # Skip the user column when running as applet.
00456                 continue
00457 
00458             cell = gtk.CellRendererText()
00459             if ellipsize:
00460                 # Ellipsize the 'Document' and 'Printer' columns.
00461                 cell.set_property ("ellipsize", pango.ELLIPSIZE_END)
00462                 cell.set_property ("width-chars", 20)
00463             column = gtk.TreeViewColumn(name, cell)
00464             column.set_cell_data_func (cell, setter)
00465             column.set_resizable(True)
00466             self.treeview.append_column(column)
00467 
00468         cell = gtk.CellRendererText ()
00469         column = gtk.TreeViewColumn (_("Time submitted"), cell, text=1)
00470         column.set_resizable (True)
00471         self.treeview.append_column (column)
00472 
00473         column = gtk.TreeViewColumn (_("Status"))
00474         icon = gtk.CellRendererPixbuf ()
00475         column.pack_start (icon, False)
00476         text = gtk.CellRendererText ()
00477         text.set_property ("ellipsize", pango.ELLIPSIZE_END)
00478         text.set_property ("width-chars", 20)
00479         column.pack_start (text, True)
00480         column.set_cell_data_func (icon, self._set_job_status_icon)
00481         column.set_cell_data_func (text, self._set_job_status_text)
00482         self.treeview.append_column (column)
00483 
00484         self.store = gtk.TreeStore(int, str)
00485         self.store.set_sort_column_id (0, gtk.SORT_DESCENDING)
00486         self.treeview.set_model(self.store)
00487         self.treeview.set_rules_hint (True)
00488         self.selection = self.treeview.get_selection()
00489         self.selection.set_mode(gtk.SELECTION_MULTIPLE)
00490         self.selection.connect('changed', self.on_selection_changed)
00491         self.treeview.connect ('button_release_event',
00492                                self.on_treeview_button_release_event)
00493         self.treeview.connect ('popup-menu', self.on_treeview_popup_menu)
00494 
00495         self.JobsWindow.set_icon_name (ICON)
00496         self.JobsWindow.hide ()
00497 
00498         if specific_dests:
00499             the_dests = reduce (lambda x, y: x + ", " + y, specific_dests)
00500 
00501         if my_jobs:
00502             if specific_dests:
00503                 title = _("my jobs on %s") % the_dests
00504             else:
00505                 title = _("my jobs")
00506         else:
00507             if specific_dests:
00508                 title = "%s" % the_dests
00509             else:
00510                 title = _("all jobs")
00511         self.JobsWindow.set_title (_("Document Print Status (%s)") % title)
00512 
00513         if parent:
00514             self.JobsWindow.set_transient_for (parent)
00515 
00516         def load_icon(theme, icon):
00517             try:
00518                 pixbuf = theme.load_icon (icon, ICON_SIZE, 0)
00519             except gobject.GError:
00520                 debugprint ("No %s icon available" % icon)
00521                 # Just create an empty pixbuf.
00522                 pixbuf = gtk.gdk.Pixbuf (gtk.gdk.COLORSPACE_RGB,
00523                                          True, 8, ICON_SIZE, ICON_SIZE)
00524                 pixbuf.fill (0)
00525             return pixbuf
00526 
00527         theme = gtk.icon_theme_get_default ()
00528         self.icon_jobs = load_icon (theme, ICON)
00529         self.icon_jobs_processing = load_icon (theme, "printer-printing")
00530         self.icon_no_jobs = self.icon_jobs.copy ()
00531         self.icon_no_jobs.fill (0)
00532         self.icon_jobs.composite (self.icon_no_jobs,
00533                                   0, 0,
00534                                   self.icon_no_jobs.get_width(),
00535                                   self.icon_no_jobs.get_height(),
00536                                   0, 0,
00537                                   1.0, 1.0,
00538                                   gtk.gdk.INTERP_BILINEAR,
00539                                   127)
00540         if self.applet and not self.notify_has_persistence:
00541             self.statusicon = gtk.StatusIcon ()
00542             self.statusicon.set_from_pixbuf (self.icon_no_jobs)
00543             self.statusicon.connect ('activate', self.toggle_window_display)
00544             self.statusicon.connect ('popup-menu', self.on_icon_popupmenu)
00545             self.statusicon.set_visible (False)
00546 
00547         # D-Bus
00548         if bus == None:
00549             bus = dbus.SystemBus ()
00550 
00551         self.set_process_pending (True)
00552         self.host = cups.getServer ()
00553         self.port = cups.getPort ()
00554         self.encryption = cups.getEncryption ()
00555         self.monitor = monitor.Monitor (bus=bus, my_jobs=my_jobs,
00556                                         host=self.host, port=self.port,
00557                                         encryption=self.encryption)
00558         self.monitor.connect ('refresh', self.on_refresh)
00559         self.monitor.connect ('job-added', self.job_added)
00560         self.monitor.connect ('job-event', self.job_event)
00561         self.monitor.connect ('job-removed', self.job_removed)
00562         self.monitor.connect ('state-reason-added', self.state_reason_added)
00563         self.monitor.connect ('state-reason-removed', self.state_reason_removed)
00564         self.monitor.connect ('still-connecting', self.still_connecting)
00565         self.monitor.connect ('now-connected', self.now_connected)
00566         self.monitor.connect ('printer-added', self.printer_added)
00567         self.monitor.connect ('printer-event', self.printer_event)
00568         self.monitor.connect ('printer-removed', self.printer_removed)
00569         self.monitor.refresh ()
00570 
00571         self.my_monitor = None
00572         if not my_jobs:
00573             self.my_monitor = monitor.Monitor(bus=bus, my_jobs=True,
00574                                               host=self.host, port=self.port,
00575                                               encryption=self.encryption)
00576             self.my_monitor.connect ('job-added', self.job_added)
00577             self.my_monitor.connect ('job-event', self.job_event)
00578             self.my_monitor.refresh ()
00579 
00580         if not self.applet:
00581             self.JobsWindow.show ()
00582 
00583         self.JobsAttributesWindow = gtk.Window()
00584         self.JobsAttributesWindow.set_title (_("Job attributes"))
00585         self.JobsAttributesWindow.set_position(gtk.WIN_POS_MOUSE)
00586         self.JobsAttributesWindow.set_default_size(600, 600)
00587         self.JobsAttributesWindow.set_transient_for (self.JobsWindow)
00588         self.JobsAttributesWindow.connect("delete_event",
00589                                           self.job_attributes_on_delete_event)
00590         self.JobsAttributesWindow.add_accel_group (self.job_ui_manager.get_accel_group ())
00591         attrs_action_group = gtk.ActionGroup ("AttrsActionGroup")
00592         attrs_action_group.add_actions ([
00593                 ("close", gtk.STOCK_CLOSE, None, "<ctrl>w",
00594                  _("Close this window"), self.job_attributes_on_delete_event)
00595                 ])
00596         self.attrs_ui_manager = gtk.UIManager ()
00597         self.attrs_ui_manager.insert_action_group (attrs_action_group, -1)
00598         self.attrs_ui_manager.add_ui_from_string (
00599 """
00600 <ui>
00601  <accelerator action="close"/>
00602 </ui>
00603 """
00604 )
00605         self.attrs_ui_manager.ensure_update ()
00606         self.JobsAttributesWindow.add_accel_group (self.attrs_ui_manager.get_accel_group ())
00607         vbox = gtk.VBox ()
00608         self.JobsAttributesWindow.add (vbox)
00609         toolbar = gtk.Toolbar ()
00610         action = self.attrs_ui_manager.get_action ("/close")
00611         item = action.create_tool_item ()
00612         item.set_is_important (True)
00613         toolbar.insert (item, 0)
00614         vbox.pack_start (toolbar, False, False, 0)
00615         self.notebook = gtk.Notebook()
00616         vbox.pack_start (self.notebook)
00617 
00618     def cleanup (self):
00619         self.monitor.cleanup ()
00620         if self.my_monitor:
00621             self.my_monitor.cleanup ()
00622 
00623         self.JobsWindow.hide ()
00624 
00625         # Close any open notifications.
00626         for l in [self.new_printer_notifications.values (),
00627                   self.state_reason_notifications.values ()]:
00628             for notification in l:
00629                 if notification.get_data ('closed') != True:
00630                     try:
00631                         notification.close ()
00632                     except glib.GError:
00633                         # Can fail if the notification wasn't even shown
00634                         # yet (as in bug #571603).
00635                         pass
00636                     notification.set_data ('closed', True)
00637 
00638         if self.job_creation_times_timer != None:
00639             gobject.source_remove (self.job_creation_times_timer)
00640             self.job_creation_times_timer = None
00641 
00642         for op in self.ops:
00643             op.destroy ()
00644 
00645         if self.applet and not self.notify_has_persistence:
00646             self.statusicon.set_visible (False)
00647 
00648         self.emit ('finished')
00649 
00650     def set_process_pending (self, whether):
00651         self.process_pending_events = whether
00652 
00653     def on_delete_event(self, *args):
00654         if self.applet or not self.loop:
00655             self.JobsWindow.hide ()
00656             self.JobsWindow.set_data ('visible', False)
00657             if not self.applet:
00658                 # Being run from main app, not applet
00659                 self.cleanup ()
00660         else:
00661             self.loop.quit ()
00662         return True
00663 
00664     def job_attributes_on_delete_event(self, widget, event=None):
00665         for page in range(self.notebook.get_n_pages()):
00666             self.notebook.remove_page(-1)
00667         self.jobs_attrs = {}
00668         self.JobsAttributesWindow.hide_all()
00669         return True
00670 
00671     def show_IPP_Error(self, exception, message):
00672         return errordialogs.show_IPP_Error (exception, message, self.JobsWindow)
00673 
00674     def toggle_window_display(self, icon, force_show=False):
00675         visible = self.JobsWindow.get_data('visible')
00676         if force_show:
00677             visible = False
00678 
00679         if self.notify_has_persistence:
00680             if visible:
00681                 self.JobsWindow.hide ()
00682             else:
00683                 self.JobsWindow.show ()
00684         else:
00685             if visible:
00686                 w = self.JobsWindow.window
00687                 aw = self.JobsAttributesWindow.window
00688                 (s, area, o) = self.statusicon.get_geometry ()
00689                 w.set_skip_taskbar_hint (True)
00690                 if aw != None:
00691                     aw.set_skip_taskbar_hint (True)
00692 
00693                 w.property_change ("_NET_WM_ICON_GEOMETRY",
00694                                    "CARDINAL", 32,
00695                                    gtk.gdk.PROP_MODE_REPLACE,
00696                                    list (area))
00697                 self.JobsWindow.iconify ()
00698             else:
00699                 self.JobsWindow.present ()
00700                 self.JobsWindow.window.set_skip_taskbar_hint (False)
00701                 aw = self.JobsAttributesWindow.window
00702                 if aw != None:
00703                     aw.set_skip_taskbar_hint (False)
00704 
00705         self.JobsWindow.set_data ('visible', not visible)
00706 
00707     def on_show_completed_jobs_clicked(self, toggletoolbutton):
00708         if toggletoolbutton.get_active():
00709             which_jobs = "all"
00710         else:
00711             which_jobs = "not-completed"
00712         self.monitor.refresh(which_jobs=which_jobs, refresh_all=False)
00713         if self.my_monitor:
00714             self.my_monitor.refresh(which_jobs=which_jobs, refresh_all=False)
00715 
00716     def update_job_creation_times(self):
00717         now = time.time ()
00718         need_update = False
00719         for job, data in self.jobs.iteritems():
00720             t = _("Unknown")
00721             if data.has_key ('time-at-creation'):
00722                 created = data['time-at-creation']
00723                 ago = now - created
00724                 need_update = True
00725                 if ago < 2 * 60:
00726                     t = _("a minute ago")
00727                 elif ago < 60 * 60:
00728                     mins = int (ago / 60)
00729                     t = _("%d minutes ago") % mins
00730                 elif ago < 24 * 60 * 60:
00731                     hours = int (ago / (60 * 60))
00732                     if hours == 1:
00733                         t = _("an hour ago")
00734                     else:
00735                         t = _("%d hours ago") % hours
00736                 elif ago < 7 * 24 * 60 * 60:
00737                     days = int (ago / (24 * 60 * 60))
00738                     if days == 1:
00739                         t = _("yesterday")
00740                     else:
00741                         t = _("%d days ago") % days
00742                 elif ago < 6 * 7 * 24 * 60 * 60:
00743                     weeks = int (ago / (7 * 24 * 60 * 60))
00744                     if weeks == 1:
00745                         t = _("last week")
00746                     else:
00747                         t = _("%d weeks ago") % weeks
00748                 else:
00749                     need_update = False
00750                     t = time.strftime ("%B %Y", time.localtime (created))
00751 
00752             if self.jobiters.has_key (job):
00753                 iter = self.jobiters[job]
00754                 self.store.set_value (iter, 1, t)
00755 
00756         if need_update and not self.job_creation_times_timer:
00757             def update_times_with_locking ():
00758                 gtk.gdk.threads_enter ()
00759                 ret = self.update_job_creation_times ()
00760                 gtk.gdk.threads_leave ()
00761                 return ret
00762 
00763             t = gobject.timeout_add_seconds (60, update_times_with_locking)
00764             self.job_creation_times_timer = t
00765 
00766         if not need_update:
00767             if self.job_creation_times_timer:
00768                 gobject.source_remove (self.job_creation_times_timer)
00769                 self.job_creation_times_timer = None
00770 
00771         # Return code controls whether the timeout will recur.
00772         return need_update
00773 
00774     def print_error_dialog_response(self, dialog, response, jobid):
00775         dialog.hide ()
00776         dialog.destroy ()
00777         self.stopped_job_prompts.remove (jobid)
00778         if response == gtk.RESPONSE_NO:
00779             # Diagnose
00780             if not self.__dict__.has_key ('troubleshooter'):
00781                 import troubleshoot
00782                 troubleshooter = troubleshoot.run (self.on_troubleshoot_quit)
00783                 self.troubleshooter = troubleshooter
00784 
00785     def on_troubleshoot_quit(self, troubleshooter):
00786         del self.troubleshooter
00787 
00788     def add_job (self, job, data, connection=None):
00789         self.update_job (job, data, connection=connection)
00790 
00791         # There may have been an error fetching additional attributes,
00792         # in which case we need to give up.
00793         if not self.jobs.has_key (job):
00794             return
00795 
00796         store = self.store
00797         iter = self.store.append (None)
00798         store.set_value (iter, 0, job)
00799         debugprint ("Job %d added" % job)
00800         self.jobiters[job] = iter
00801 
00802         range = self.treeview.get_visible_range ()
00803         if range != None:
00804             (start, end) = range
00805             if (self.store.get_sort_column_id () == (0,
00806                                                      gtk.SORT_DESCENDING) and
00807                 start == (1,)):
00808                 # This job was added job above the visible range, and
00809                 # we are sorting by descending job ID.  Scroll to it.
00810                 self.treeview.scroll_to_cell ((0,), None, False, 0.0, 0.0)
00811 
00812         if not self.job_creation_times_timer:
00813             def start_updating_job_creation_times():
00814                 gtk.gdk.threads_enter ()
00815                 self.update_job_creation_times ()
00816                 gtk.gdk.threads_leave ()
00817                 return False
00818 
00819             gobject.timeout_add (500, start_updating_job_creation_times)
00820 
00821     def update_monitor (self):
00822         self.monitor.update ()
00823         if self.my_monitor:
00824             self.my_monitor.update ()
00825 
00826     def update_job (self, job, data, connection=None):
00827         # Fetch required attributes for this job if they are missing.
00828         r = self.required_job_attributes - set (data.keys ())
00829 
00830         # If we are showing attributes of this job at this moment, update them.
00831         if job in self.jobs_attrs:
00832             self.update_job_attributes_viewer(job)
00833 
00834         if r:
00835             attrs = None
00836             try:
00837                 if connection == None:
00838                     connection = cups.Connection (host=self.host,
00839                                                   port=self.port,
00840                                                   encryption=self.encryption)
00841 
00842                 debugprint ("requesting %s" % r)
00843                 r = list (r)
00844                 attrs = connection.getJobAttributes (job,
00845                                                      requested_attributes=r)
00846             except RuntimeError:
00847                 pass
00848             except AttributeError:
00849                 pass
00850             except cups.IPPError:
00851                 # someone else may have purged the job
00852                 return
00853 
00854             if attrs:
00855                 data.update (attrs)
00856 
00857         self.jobs[job] = data
00858 
00859         job_requires_auth = False
00860         try:
00861             jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
00862             s = int (jstate)
00863 
00864             if s in [cups.IPP_JOB_HELD, cups.IPP_JOB_STOPPED]:
00865                 jattrs = ['job-state', 'job-hold-until']
00866                 pattrs = ['auth-info-required', 'device-uri']
00867                 uri = data.get ('job-printer-uri')
00868                 c = authconn.Connection (self.JobsWindow,
00869                                          host=self.host,
00870                                          port=self.port,
00871                                          encryption=self.encryption)
00872                 attrs = c.getPrinterAttributes (uri = uri,
00873                                                 requested_attributes=pattrs)
00874 
00875                 try:
00876                     auth_info_required = attrs['auth-info-required']
00877                 except KeyError:
00878                     debugprint ("No auth-info-required attribute; "
00879                                 "guessing instead")
00880                     auth_info_required = ['username', 'password']
00881 
00882                 if not isinstance (auth_info_required, list):
00883                     auth_info_required = [auth_info_required]
00884                     attrs['auth-info-required'] = auth_info_required
00885 
00886                 data.update (attrs)
00887 
00888                 attrs = c.getJobAttributes (job,
00889                                             requested_attributes=jattrs)
00890                 data.update (attrs)
00891                 jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
00892                 s = int (jstate)
00893         except ValueError:
00894             pass
00895         except RuntimeError:
00896             pass
00897         except cups.IPPError, (e, m):
00898             pass
00899 
00900         # Invalidate the cached status description and redraw the treeview.
00901         try:
00902             del data['_status_text']
00903         except KeyError:
00904             pass
00905         self.treeview.queue_draw ()
00906 
00907         # Check whether authentication is required.
00908         if self.applet:
00909             job_requires_auth = (s == cups.IPP_JOB_HELD and
00910                                  data.get ('job-hold-until', 'none') ==
00911                                  'auth-info-required')
00912 
00913             if (job_requires_auth and
00914                 not self.auth_info_dialogs.has_key (job)):
00915                 try:
00916                     cups.require ("1.9.37")
00917                 except:
00918                     debugprint ("Authentication required but "
00919                                 "authenticateJob() not available")
00920                     return
00921 
00922                 # Find out which auth-info is required.
00923                 try_keyring = USE_KEYRING
00924                 keyring_attrs = dict()
00925                 auth_info = None
00926                 if try_keyring and 'password' in auth_info_required:
00927                     auth_info_required = data.get ('auth-info-required', [])
00928                     device_uri = data.get ("device-uri")
00929                     (scheme, rest) = urllib.splittype (device_uri)
00930                     if scheme == 'smb':
00931                         uri = smburi.SMBURI (uri=device_uri)
00932                         (group, server, share,
00933                          user, password) = uri.separate ()
00934                         keyring_attrs["domain"] = str (group)
00935                     else:
00936                         (serverport, rest) = urllib.splithost (rest)
00937                         (server, port) = urllib.splitnport (serverport)
00938                     keyring_attrs.update ({ "server": str (server.lower ()),
00939                                             "protocol": str (scheme)})
00940 
00941                 if job in self.authenticated_jobs:
00942                     # We've already tried to authenticate this job before.
00943                     try_keyring = False
00944 
00945                 if try_keyring and 'password' in auth_info_required:
00946                     type = gnomekeyring.ITEM_NETWORK_PASSWORD
00947                     try:
00948                         items = gnomekeyring.find_items_sync (type,
00949                                                               keyring_attrs)
00950                         auth_info = map (lambda x: '', auth_info_required)
00951                         ind = auth_info_required.index ('username')
00952                         auth_info[ind] = items[0].attributes.get ('user', '')
00953                         ind = auth_info_required.index ('password')
00954                         auth_info[ind] = items[0].secret
00955                     except gnomekeyring.NoMatchError:
00956                         debugprint ("gnomekeyring: no match for %s" %
00957                                     keyring_attrs)
00958                     except gnomekeyring.DeniedError:
00959                         debugprint ("gnomekeyring: denied for %s" %
00960                                     keyring_attrs)
00961 
00962                 if try_keyring and c == None:
00963                     try:
00964                         c = authconn.Connection (self.JobsWindow,
00965                                                  host=self.host,
00966                                                  port=self.port,
00967                                                  encryption=self.encryption)
00968                     except RuntimeError:
00969                         try_keyring = False
00970 
00971                 if try_keyring and auth_info != None:
00972                     try:
00973                         c._begin_operation (_("authenticating job"))
00974                         c.authenticateJob (job, auth_info)
00975                         c._end_operation ()
00976                         self.update_monitor ()
00977                         debugprint ("Automatically authenticated job %d" % job)
00978                         self.authenticated_jobs.add (job)
00979                         return
00980                     except cups.IPPError, (e, m):
00981                         c._end_operation ()
00982                         nonfatalException ()
00983                         return
00984                     except:
00985                         c._end_operation ()
00986                         nonfatalException ()
00987 
00988                 username = pwd.getpwuid (os.getuid ())[0]
00989                 keyring_attrs["user"] = str (username)
00990                 self.display_auth_info_dialog (job, keyring_attrs)
00991         self.update_sensitivity ()
00992 
00993     def display_auth_info_dialog (self, job, keyring_attrs=None):
00994         data = self.jobs[job]
00995         auth_info_required = data['auth-info-required']
00996         dialog = authconn.AuthDialog (auth_info_required=auth_info_required,
00997                                       allow_remember=USE_KEYRING)
00998         dialog.set_data ('keyring-attrs', keyring_attrs)
00999         dialog.set_data ('auth-info-required', auth_info_required)
01000         dialog.set_position (gtk.WIN_POS_CENTER)
01001 
01002         # Pre-fill 'username' field.
01003         auth_info = map (lambda x: '', auth_info_required)
01004         username = pwd.getpwuid (os.getuid ())[0]
01005         if 'username' in auth_info_required:
01006             try:
01007                 ind = auth_info_required.index ('username')
01008                 auth_info[ind] = username
01009                 dialog.set_auth_info (auth_info)
01010             except:
01011                 nonfatalException ()
01012 
01013         # Focus on the first empty field.
01014         index = 0
01015         for field in auth_info_required:
01016             if auth_info[index] == '':
01017                 dialog.field_grab_focus (field)
01018                 break
01019             index += 1
01020 
01021         dialog.set_prompt (_("Authentication required for "
01022                              "printing document `%s' (job %d)") %
01023                            (data.get('job-name', _("Unknown")), job))
01024         self.auth_info_dialogs[job] = dialog
01025         dialog.connect ('response', self.auth_info_dialog_response)
01026         dialog.connect ('delete-event', self.auth_info_dialog_delete)
01027         dialog.set_data ('job-id', job)
01028         dialog.show_all ()
01029         dialog.set_keep_above (True)
01030         dialog.show_now ()
01031 
01032     def auth_info_dialog_delete (self, dialog, event):
01033         self.auth_info_dialog_response (dialog, gtk.RESPONSE_CANCEL)
01034 
01035     def auth_info_dialog_response (self, dialog, response):
01036         jobid = dialog.get_data ('job-id')
01037         del self.auth_info_dialogs[jobid]
01038 
01039         if response != gtk.RESPONSE_OK:
01040             dialog.destroy ()
01041             return
01042 
01043         auth_info = dialog.get_auth_info ()
01044         try:
01045             c = authconn.Connection (self.JobsWindow,
01046                                      host=self.host,
01047                                      port=self.port,
01048                                      encryption=self.encryption)
01049         except RuntimeError:
01050             debugprint ("Error connecting to CUPS for authentication")
01051             return
01052 
01053         remember = False
01054         c._begin_operation (_("authenticating job"))
01055         try:
01056             c.authenticateJob (jobid, auth_info)
01057             remember = dialog.get_remember_password ()
01058             self.authenticated_jobs.add (jobid)
01059             self.update_monitor ()
01060         except cups.IPPError, (e, m):
01061             self.show_IPP_Error (e, m)
01062 
01063         c._end_operation ()
01064 
01065         if remember:
01066             try:
01067                 keyring = gnomekeyring.get_default_keyring_sync ()
01068                 type = gnomekeyring.ITEM_NETWORK_PASSWORD
01069                 attrs = dialog.get_data ("keyring-attrs")
01070                 auth_info_required = dialog.get_data ('auth-info-required')
01071                 if attrs != None and auth_info_required != None:
01072                     try:
01073                         ind = auth_info_required.index ('username')
01074                         attrs['user'] = auth_info[ind]
01075                     except IndexError:
01076                         pass
01077 
01078                     name = "%s@%s (%s)" % (attrs.get ("user"),
01079                                            attrs.get ("server"),
01080                                            attrs.get ("protocol"))
01081                     ind = auth_info_required.index ('password')
01082                     secret = auth_info[ind]
01083                     id = gnomekeyring.item_create_sync (keyring, type, name,
01084                                                         attrs, secret, True)
01085                     debugprint ("keyring: created id %d for %s" % (id, name))
01086             except:
01087                 nonfatalException ()
01088 
01089         dialog.destroy ()
01090 
01091     def set_statusicon_visibility (self):
01092         if not self.applet:
01093             return
01094 
01095         if self.suppress_icon_hide:
01096             # Avoid hiding the icon if we've been woken up to notify
01097             # about a new printer.
01098             self.suppress_icon_hide = False
01099             return
01100 
01101         open_notifications = len (self.new_printer_notifications.keys ())
01102         open_notifications += len (self.completed_job_notifications.keys ())
01103         for reason, notification in self.state_reason_notifications.iteritems():
01104             if notification.get_data ('closed') != True:
01105                 open_notifications += 1
01106         num_jobs = len (self.active_jobs)
01107 
01108         debugprint ("open notifications: %d" % open_notifications)
01109         debugprint ("num_jobs: %d" % num_jobs)
01110         debugprint ("num_jobs_when_hidden: %d" % self.num_jobs_when_hidden)
01111 
01112         if self.notify_has_persistence:
01113             return
01114 
01115         # Don't handle tooltips during the mainloop recursion at the
01116         # end of this function as it seems to cause havoc (bug #664044,
01117         # bug #739745).
01118         self.statusicon.set_has_tooltip (False)
01119 
01120         self.statusicon.set_visible (open_notifications > 0 or
01121                                      num_jobs > self.num_jobs_when_hidden)
01122 
01123         # Let the icon show/hide itself before continuing.
01124         while self.process_pending_events and gtk.events_pending ():
01125             gtk.main_iteration ()
01126 
01127     def on_treeview_popup_menu (self, treeview):
01128         event = gtk.gdk.Event (gtk.gdk.NOTHING)
01129         self.show_treeview_popup_menu (treeview, event, 0)
01130 
01131     def on_treeview_button_release_event(self, treeview, event):
01132         if event.button == 3:
01133             self.show_treeview_popup_menu (treeview, event, event.button)
01134 
01135     def update_sensitivity (self, selection = None):
01136         if (selection is None):
01137             selection = self.treeview.get_selection () 
01138         (model, pathlist) = selection.get_selected_rows()
01139         cancel = self.job_ui_manager.get_action ("/cancel-job")
01140         delete = self.job_ui_manager.get_action ("/delete-job")
01141         hold = self.job_ui_manager.get_action ("/hold-job")
01142         release = self.job_ui_manager.get_action ("/release-job")
01143         reprint = self.job_ui_manager.get_action ("/reprint-job")
01144         retrieve = self.job_ui_manager.get_action ("/retrieve-job")
01145         authenticate = self.job_ui_manager.get_action ("/authenticate-job")
01146         attributes = self.job_ui_manager.get_action ("/job-attributes")
01147         move = self.job_ui_manager.get_action ("/move-job")
01148         if len (pathlist) == 0:
01149             for widget in [cancel, delete, hold, release, reprint, retrieve,
01150                            move, authenticate, attributes]:
01151                 widget.set_sensitive (False)
01152             return
01153 
01154         cancel_sensitive = True
01155         hold_sensitive = True
01156         release_sensitive = True
01157         reprint_sensitive = True
01158         authenticate_sensitive = True
01159         move_sensitive = False
01160         other_printers = self.printer_uri_index.all_printer_names ()
01161         job_printers = dict()
01162 
01163         self.jobids = []
01164         for path in pathlist:
01165             iter = self.store.get_iter (path)
01166             jobid = self.store.get_value (iter, 0)
01167             self.jobids.append(jobid)
01168             job = self.jobs[jobid]
01169 
01170             if job.has_key ('job-state'):
01171                 s = job['job-state']
01172                 if s >= cups.IPP_JOB_CANCELED:
01173                     cancel_sensitive = False
01174                 if s != cups.IPP_JOB_PENDING:
01175                     hold_sensitive = False
01176                 if s != cups.IPP_JOB_HELD:
01177                     release_sensitive = False
01178                 if (not job.get('job-preserved', False)):
01179                     reprint_sensitive = False
01180 
01181             if (job.get ('job-state',
01182                          cups.IPP_JOB_CANCELED) != cups.IPP_JOB_HELD or
01183                 job.get ('job-hold-until', 'none') != 'auth-info-required'):
01184                 authenticate_sensitive = False
01185 
01186             uri = job.get ('job-printer-uri', None)
01187             if uri:
01188                 try:
01189                     printer = self.printer_uri_index.lookup (uri)
01190                 except KeyError:
01191                     printer = uri
01192                 job_printers[printer] = uri
01193 
01194         if len (job_printers.keys ()) == 1:
01195             try:
01196                 other_printers.remove (job_printers.keys ()[0])
01197             except KeyError:
01198                 pass
01199 
01200         if len (other_printers) > 0:
01201             printers_menu = gtk.Menu ()
01202             other_printers = list (other_printers)
01203             other_printers.sort ()
01204             for printer in other_printers:
01205                 try:
01206                     uri = self.printer_uri_index.lookup_cached_by_name (printer)
01207                 except KeyError:
01208                     uri = None
01209                 menuitem = gtk.MenuItem (printer, False)
01210                 menuitem.set_sensitive (uri != None)
01211                 menuitem.show ()
01212                 menuitem.connect ('activate', self.on_job_move_activate, uri)
01213                 printers_menu.append (menuitem)
01214 
01215             self.move_job_menuitem.set_submenu (printers_menu)
01216             move_sensitive = True
01217 
01218         cancel.set_sensitive(cancel_sensitive)
01219         delete.set_sensitive(not cancel_sensitive)
01220         hold.set_sensitive(hold_sensitive)
01221         release.set_sensitive(release_sensitive)
01222         reprint.set_sensitive(reprint_sensitive)
01223         retrieve.set_sensitive(reprint_sensitive)
01224         move.set_sensitive (move_sensitive)
01225         authenticate.set_sensitive(authenticate_sensitive)
01226         attributes.set_sensitive(True)
01227 
01228     def on_selection_changed (self, selection):
01229         self.update_sensitivity (selection)
01230 
01231     def show_treeview_popup_menu (self, treeview, event, event_button):
01232         # Right-clicked.
01233         self.job_context_menu.popup (None, None, None, event_button,
01234                                      event.get_time ())
01235 
01236     def on_icon_popupmenu(self, icon, button, time):
01237         self.statusicon_popupmenu.popup (None, None, None, button, time)
01238 
01239     def on_icon_hide_activate(self, menuitem):
01240         self.num_jobs_when_hidden = len (self.jobs.keys ())
01241         self.set_statusicon_visibility ()
01242 
01243     def on_icon_configure_printers_activate(self, menuitem):
01244         if self.loop:
01245             env = {}
01246             for name, value in os.environ.iteritems ():
01247                 if name == "SYSTEM_CONFIG_PRINTER_UI":
01248                     continue
01249                 env[name] = value
01250             p = subprocess.Popen ([ "system-config-printer" ],
01251                                   close_fds=True, env=env)
01252             gobject.timeout_add_seconds (10, self.poll_subprocess, p)
01253 
01254     def poll_subprocess(self, process):
01255         returncode = process.poll ()
01256         return returncode == None
01257 
01258     def on_icon_quit_activate (self, menuitem):
01259         self.cleanup ()
01260         if self.loop:
01261             self.loop.quit ()
01262 
01263     def on_job_cancel_activate(self, menuitem):
01264         self.on_job_cancel_activate2(False)
01265 
01266     def on_job_delete_activate(self, menuitem):
01267         self.on_job_cancel_activate2(True)
01268 
01269     def on_job_cancel_activate2(self, purge_job):
01270         if self.jobids:
01271             op = CancelJobsOperation (self.JobsWindow, self.host, self.port,
01272                                       self.encryption, self.jobids, purge_job)
01273             self.ops.append (op)
01274             op.connect ('finished', self.on_canceljobs_finished)
01275             op.connect ('ipp-error', self.on_canceljobs_error)
01276 
01277     def on_canceljobs_finished (self, canceljobsoperation):
01278         canceljobsoperation.destroy ()
01279         i = self.ops.index (canceljobsoperation)
01280         del self.ops[i]
01281         self.update_monitor ()
01282 
01283     def on_canceljobs_error (self, canceljobsoperation, jobid, exc):
01284         self.update_monitor ()
01285         if type (exc) == cups.IPPError:
01286             (e, m) = exc.args
01287             if (e != cups.IPP_NOT_POSSIBLE and
01288                 e != cups.IPP_NOT_FOUND):
01289                 self.show_IPP_Error (e, m)
01290 
01291             return
01292 
01293         raise exc
01294 
01295     def on_job_hold_activate(self, menuitem):
01296         try:
01297             c = authconn.Connection (self.JobsWindow,
01298                                      host=self.host,
01299                                      port=self.port,
01300                                      encryption=self.encryption)
01301         except RuntimeError:
01302             return
01303 
01304         for jobid in self.jobids:
01305             c._begin_operation (_("holding job"))
01306             try:
01307                 c.setJobHoldUntil (jobid, "indefinite")
01308             except cups.IPPError, (e, m):
01309                 if (e != cups.IPP_NOT_POSSIBLE and
01310                     e != cups.IPP_NOT_FOUND):
01311                     self.show_IPP_Error (e, m)
01312                 self.update_monitor ()
01313                 c._end_operation ()
01314                 return
01315             c._end_operation ()
01316 
01317         del c
01318         self.update_monitor ()
01319 
01320     def on_job_release_activate(self, menuitem):
01321         try:
01322             c = authconn.Connection (self.JobsWindow,
01323                                      host=self.host,
01324                                      port=self.port,
01325                                      encryption=self.encryption)
01326         except RuntimeError:
01327             return
01328 
01329         for jobid in self.jobids:
01330             c._begin_operation (_("releasing job"))
01331             try:
01332                 c.setJobHoldUntil (jobid, "no-hold")
01333             except cups.IPPError, (e, m):
01334                 if (e != cups.IPP_NOT_POSSIBLE and
01335                     e != cups.IPP_NOT_FOUND):
01336                     self.show_IPP_Error (e, m)
01337                 self.update_monitor ()
01338                 c._end_operation ()
01339                 return
01340             c._end_operation ()
01341 
01342         del c
01343         self.update_monitor ()
01344 
01345     def on_job_reprint_activate(self, menuitem):
01346         try:
01347             c = authconn.Connection (self.JobsWindow,
01348                                      host=self.host,
01349                                      port=self.port,
01350                                      encryption=self.encryption)
01351             for jobid in self.jobids:
01352                 c.restartJob (jobid)
01353             del c
01354         except cups.IPPError, (e, m):
01355             self.show_IPP_Error (e, m)
01356             self.update_monitor ()
01357             return
01358         except RuntimeError:
01359             return
01360 
01361         self.update_monitor ()
01362 
01363     def on_job_retrieve_activate(self, menuitem):
01364         try:
01365             c = authconn.Connection (self.JobsWindow,
01366                                      host=self.host,
01367                                      port=self.port,
01368                                      encryption=self.encryption)
01369         except RuntimeError:
01370             return
01371 
01372         for jobid in self.jobids:
01373             try:
01374                 attrs=c.getJobAttributes(jobid)
01375                 printer_uri=attrs['job-printer-uri']
01376                 document_count=attrs['document-count']
01377                 for document_number in range(1, document_count+1):
01378                     document=c.getDocument(printer_uri, jobid, document_number)
01379                     tempfile = document.get('file')
01380                     name = document.get('document-name')
01381                     format = document.get('document-format', '')
01382 
01383                     # if there's no document-name retrieved
01384                     if name == None:
01385                         # give the default filename some meaningful name
01386                         name = _("retrieved")+str(document_number)
01387                         # add extension according to format
01388                         if format == 'application/postscript':
01389                             name = name + ".ps"
01390                         elif format.find('application/vnd.') != -1:
01391                             name = name + format.replace('application/vnd', '')
01392                         elif format.find('application/') != -1:
01393                             name = name + format.replace('application/', '.')
01394 
01395                     if tempfile != None:
01396                         dialog = gtk.FileChooserDialog (_("Save File"),
01397                                                         self.JobsWindow,
01398                                                   gtk.FILE_CHOOSER_ACTION_SAVE,
01399                                         (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
01400                                          gtk.STOCK_SAVE, gtk.RESPONSE_OK))
01401                         dialog.set_current_name(name)
01402                         dialog.set_do_overwrite_confirmation(True)
01403 
01404                         response = dialog.run()
01405                         if response == gtk.RESPONSE_OK:
01406                             file_to_save = dialog.get_filename()
01407                             try:
01408                                 shutil.copyfile(tempfile, file_to_save)
01409                             except (IOError, shutil.Error):
01410                                 debugprint("Unable to save file "+file_to_save)
01411                         elif response == gtk.RESPONSE_CANCEL:
01412                             pass
01413                         dialog.destroy()
01414                         os.unlink(tempfile)
01415                     else:
01416                         debugprint("Unable to retrieve file from job file")
01417                         return
01418 
01419             except cups.IPPError, (e, m):
01420                 self.show_IPP_Error (e, m)
01421                 self.update_monitor ()
01422                 return
01423 
01424         del c
01425         self.update_monitor ()
01426 
01427     def on_job_move_activate(self, menuitem, job_printer_uri):
01428         try:
01429             c = authconn.Connection (self.JobsWindow,
01430                                      host=self.host,
01431                                      port=self.port,
01432                                      encryption=self.encryption)
01433             for jobid in self.jobids:
01434                 c.moveJob (job_id=jobid, job_printer_uri=job_printer_uri)
01435             del c
01436         except cups.IPPError, (e, m):
01437             self.show_IPP_Error (e, m)
01438             self.update_monitor ()
01439             return
01440         except RuntimeError:
01441             return
01442 
01443         self.update_monitor ()
01444 
01445     def on_job_authenticate_activate(self, menuitem):
01446         for jobid in self.jobids:
01447             self.display_auth_info_dialog (jobid)
01448 
01449     def on_refresh_clicked(self, toolbutton):
01450         self.monitor.refresh ()
01451         if self.my_monitor:
01452             self.my_monitor.refresh ()
01453 
01454         self.update_job_creation_times ()
01455 
01456     def on_job_attributes_activate(self, menuitem):
01457         """ For every selected job create notebook page with attributes. """
01458         try:
01459             c = cups.Connection (host=self.host,
01460                                  port=self.port,
01461                                  encryption=self.encryption)
01462         except RuntimeError:
01463             return False
01464 
01465         for jobid in self.jobids:
01466             if jobid not in self.jobs_attrs:
01467                 # add new notebook page with scrollable treeview
01468                 scrolledwindow = gtk.ScrolledWindow()
01469                 label = gtk.Label(str(jobid)) # notebook page has label with jobid
01470                 page_index = self.notebook.append_page(scrolledwindow, label)
01471                 attr_treeview = gtk.TreeView()
01472                 scrolledwindow.add(attr_treeview)
01473                 cell = gtk.CellRendererText ()
01474                 attr_treeview.insert_column_with_attributes(0, _("Name"),
01475                                                             cell, text=0)
01476                 cell = gtk.CellRendererText ()
01477                 attr_treeview.insert_column_with_attributes(1, _("Value"),
01478                                                             cell, text=1)
01479                 attr_store = gtk.ListStore(gobject.TYPE_STRING,
01480                                            gobject.TYPE_STRING)
01481                 attr_treeview.set_model(attr_store)
01482                 attr_treeview.get_selection().set_mode(gtk.SELECTION_NONE)
01483                 attr_store.set_sort_column_id (0, gtk.SORT_ASCENDING)
01484                 self.jobs_attrs[jobid] = (attr_store, page_index)
01485                 self.update_job_attributes_viewer (jobid, conn=c)
01486 
01487         self.JobsAttributesWindow.show_all ()
01488 
01489     def update_job_attributes_viewer(self, jobid, conn=None):
01490         """ Update attributes store with new values. """
01491         if conn != None:
01492             c = conn
01493         else:
01494             try:
01495                 c = cups.Connection (host=self.host,
01496                                      port=self.port,
01497                                      encryption=self.encryption)
01498             except RuntimeError:
01499                 return False
01500 
01501         if jobid in self.jobs_attrs:
01502             (attr_store, page) = self.jobs_attrs[jobid]
01503             try:
01504                 attrs = c.getJobAttributes(jobid)       # new attributes
01505             except AttributeError:
01506                 return
01507             except cups.IPPError:
01508                 # someone else may have purged the job,
01509                 # remove jobs notebook page
01510                 self.notebook.remove_page(page)
01511                 del self.jobs_attrs[jobid]
01512                 return
01513 
01514             attr_store.clear()                          # remove old attributes
01515             for name, value in attrs.iteritems():
01516                 if name in ['job-id', 'job-printer-up-time']:
01517                     continue
01518                 attr_store.append([name, str(value)])
01519 
01520     def job_is_active (self, jobdata):
01521         state = jobdata.get ('job-state', cups.IPP_JOB_CANCELED)
01522         if state >= cups.IPP_JOB_CANCELED:
01523             return False
01524 
01525         return True
01526 
01527     ## Icon manipulation
01528     def add_state_reason_emblem (self, pixbuf, printer=None):
01529         worst_reason = None
01530         if printer == None and self.worst_reason != None:
01531             # Check that it's valid.
01532             printer = self.worst_reason.get_printer ()
01533             found = False
01534             for reason in self.printer_state_reasons.get (printer, []):
01535                 if reason == self.worst_reason:
01536                     worst_reason = self.worst_reason
01537                     break
01538             if worst_reason == None:
01539                 self.worst_reason = None
01540 
01541         if printer != None:
01542             for reason in self.printer_state_reasons.get (printer, []):
01543                 if worst_reason == None:
01544                     worst_reason = reason
01545                 elif reason > worst_reason:
01546                     worst_reason = reason
01547 
01548         if worst_reason != None:
01549             level = worst_reason.get_level ()
01550             if level > StateReason.REPORT:
01551                 # Add an emblem to the icon.
01552                 icon = StateReason.LEVEL_ICON[level]
01553                 pixbuf = pixbuf.copy ()
01554                 try:
01555                     theme = gtk.icon_theme_get_default ()
01556                     emblem = theme.load_icon (icon, 22, 0)
01557                     emblem.composite (pixbuf,
01558                                       pixbuf.get_width () / 2,
01559                                       pixbuf.get_height () / 2,
01560                                       emblem.get_width () / 2,
01561                                       emblem.get_height () / 2,
01562                                       pixbuf.get_width () / 2,
01563                                       pixbuf.get_height () / 2,
01564                                       0.5, 0.5,
01565                                       gtk.gdk.INTERP_BILINEAR, 255)
01566                 except gobject.GError:
01567                     debugprint ("No %s icon available" % icon)
01568 
01569         return pixbuf
01570 
01571     def get_icon_pixbuf (self, have_jobs=None):
01572         if not self.applet:
01573             return
01574 
01575         if have_jobs == None:
01576             have_jobs = len (self.jobs.keys ()) > 0
01577 
01578         if have_jobs:
01579             pixbuf = self.icon_jobs
01580             for jobid, jobdata in self.jobs.iteritems ():
01581                 jstate = jobdata.get ('job-state', cups.IPP_JOB_PENDING)
01582                 if jstate == cups.IPP_JOB_PROCESSING:
01583                     pixbuf = self.icon_jobs_processing
01584                     break
01585         else:
01586             pixbuf = self.icon_no_jobs
01587 
01588         try:
01589             pixbuf = self.add_state_reason_emblem (pixbuf)
01590         except:
01591             nonfatalException ()
01592 
01593         return pixbuf
01594 
01595     def set_statusicon_tooltip (self, tooltip=None):
01596         if not self.applet:
01597             return
01598 
01599         if tooltip == None:
01600             num_jobs = len (self.jobs)
01601             if num_jobs == 0:
01602                 tooltip = _("No documents queued")
01603             elif num_jobs == 1:
01604                 tooltip = _("1 document queued")
01605             else:
01606                 tooltip = _("%d documents queued") % num_jobs
01607 
01608         self.statusicon.set_tooltip (tooltip)
01609 
01610     def update_status (self, have_jobs=None):
01611         # Found out which printer state reasons apply to our active jobs.
01612         upset_printers = set()
01613         for printer, reasons in self.printer_state_reasons.iteritems ():
01614             if len (reasons) > 0:
01615                 upset_printers.add (printer)
01616         debugprint ("Upset printers: %s" % upset_printers)
01617 
01618         my_upset_printers = set()
01619         if len (upset_printers):
01620             my_upset_printers = set()
01621             for jobid in self.active_jobs:
01622                 # 'job-printer-name' is set by job_added/job_event
01623                 printer = self.jobs[jobid]['job-printer-name']
01624                 if printer in upset_printers:
01625                     my_upset_printers.add (printer)
01626             debugprint ("My upset printers: %s" % my_upset_printers)
01627 
01628         my_reasons = []
01629         for printer in my_upset_printers:
01630             my_reasons.extend (self.printer_state_reasons[printer])
01631 
01632         # Find out which is the most problematic.
01633         self.worst_reason = None
01634         if len (my_reasons) > 0:
01635             worst_reason = my_reasons[0]
01636             for reason in my_reasons:
01637                 if reason > worst_reason:
01638                     worst_reason = reason
01639             self.worst_reason = worst_reason
01640             debugprint ("Worst reason: %s" % worst_reason)
01641 
01642         self.statusbar.pop (0)
01643         if self.worst_reason != None:
01644             (title, tooltip) = self.worst_reason.get_description ()
01645             self.statusbar.push (0, tooltip)
01646         else:
01647             tooltip = None
01648             status_message = ""
01649             processing = 0
01650             pending = 0
01651             for jobid in self.active_jobs:
01652                 try:
01653                     job_state = self.jobs[jobid]['job-state']
01654                 except KeyError:
01655                     continue
01656                 if job_state == cups.IPP_JOB_PROCESSING:
01657                     processing = processing + 1
01658                 elif job_state == cups.IPP_JOB_PENDING:
01659                     pending = pending + 1
01660             if ((processing > 0) or (pending > 0)):
01661                 status_message = _("processing / pending:   %d / %d") % (processing, pending)
01662                 self.statusbar.push(0, status_message)
01663 
01664         if self.applet and not self.notify_has_persistence:
01665             pixbuf = self.get_icon_pixbuf (have_jobs=have_jobs)
01666             self.statusicon.set_from_pixbuf (pixbuf)
01667             self.set_statusicon_visibility ()
01668             self.set_statusicon_tooltip (tooltip=tooltip)
01669 
01670     ## Notifications
01671     def notify_printer_state_reason_if_important (self, reason):
01672         level = reason.get_level ()
01673         if level < StateReason.WARNING:
01674             # Not important enough to justify a notification.
01675             return
01676 
01677         blacklist = [
01678             # Some printers report 'other-warning' for no apparent
01679             # reason, e.g.  Canon iR 3170C, Epson AL-CX11NF.
01680             # See bug #520815.
01681             "other",
01682 
01683             # This seems to be some sort of 'magic' state reason that
01684             # is for internal use only.
01685             "com.apple.print.recoverable",
01686             ]
01687 
01688         if reason.get_reason () in blacklist:
01689             return
01690 
01691         self.notify_printer_state_reason (reason)
01692 
01693     def notify_printer_state_reason (self, reason):
01694         tuple = reason.get_tuple ()
01695         if self.state_reason_notifications.has_key (tuple):
01696             debugprint ("Already sent notification for %s" % repr (reason))
01697             return
01698 
01699         level = reason.get_level ()
01700         if (level == StateReason.ERROR or
01701             reason.get_reason () == "connecting-to-device"):
01702             urgency = pynotify.URGENCY_NORMAL
01703         else:
01704             urgency = pynotify.URGENCY_LOW
01705 
01706         (title, text) = reason.get_description ()
01707         notification = pynotify.Notification (title, text, 'printer')
01708         reason.user_notified = True
01709         notification.set_urgency (urgency)
01710         if self.notify_has_actions:
01711             notification.set_timeout (pynotify.EXPIRES_NEVER)
01712         notification.connect ('closed',
01713                               self.on_state_reason_notification_closed)
01714         self.state_reason_notifications[reason.get_tuple ()] = notification
01715         self.set_statusicon_visibility ()
01716         try:
01717             notification.show ()
01718         except gobject.GError:
01719             nonfatalException ()
01720 
01721     def on_state_reason_notification_closed (self, notification, reason=None):
01722         debugprint ("Notification %s closed" % repr (notification))
01723         notification.set_data ('closed', True)
01724         self.set_statusicon_visibility ()
01725         return
01726 
01727     def notify_completed_job (self, jobid):
01728         job = self.jobs.get (jobid, {})
01729         document = job.get ('job-name', _("Unknown"))
01730         printer_uri = job.get ('job-printer-uri')
01731         if printer_uri != None:
01732             # Determine if this printer is remote.  There's no need to
01733             # show a notification if the printer is connected to this
01734             # machine.
01735 
01736             # Find out the device URI.  We might already have
01737             # determined this if authentication was required.
01738             device_uri = job.get ('device-uri')
01739 
01740             if device_uri == None:
01741                 pattrs = ['device-uri']
01742                 c = authconn.Connection (self.JobsWindow,
01743                                          host=self.host,
01744                                          port=self.port,
01745                                          encryption=self.encryption)
01746                 try:
01747                     attrs = c.getPrinterAttributes (uri=printer_uri,
01748                                                     requested_attributes=pattrs)
01749                 except cups.IPPError:
01750                     return
01751 
01752                 device_uri = attrs.get ('device-uri')
01753 
01754             if device_uri != None:
01755                 (scheme, rest) = urllib.splittype (device_uri)
01756                 if scheme not in ['socket', 'ipp', 'http', 'smb']:
01757                     return
01758 
01759         printer = job.get ('job-printer-name', _("Unknown"))
01760         notification = pynotify.Notification (_("Document printed"),
01761                                               _("Document `%s' has been sent "
01762                                                 "to `%s' for printing.") %
01763                                               (document, printer),
01764                                               'printer')
01765         notification.set_urgency (pynotify.URGENCY_LOW)
01766         notification.connect ('closed',
01767                               self.on_completed_job_notification_closed)
01768         notification.set_data ('jobid', jobid)
01769         self.completed_job_notifications[jobid] = notification
01770         self.set_statusicon_visibility ()
01771         try:
01772             notification.show ()
01773         except gobject.GError:
01774             nonfatalException ()
01775 
01776     def on_completed_job_notification_closed (self, notification, reason=None):
01777         jobid = notification.get_data ('jobid')
01778         del self.completed_job_notifications[jobid]
01779         self.set_statusicon_visibility ()
01780 
01781     ## Monitor signal handlers
01782     def on_refresh (self, mon):
01783         self.store.clear ()
01784         self.jobs = {}
01785         self.active_jobs = set()
01786         self.jobiters = {}
01787         self.printer_uri_index = PrinterURIIndex ()
01788 
01789     def job_added (self, mon, jobid, eventname, event, jobdata):
01790         uri = jobdata.get ('job-printer-uri', '')
01791         try:
01792             printer = self.printer_uri_index.lookup (uri)
01793         except KeyError:
01794             printer = uri
01795 
01796         if self.specific_dests and printer not in self.specific_dests:
01797             return
01798 
01799         jobdata['job-printer-name'] = printer
01800 
01801         # We may be showing this job already, perhaps because we are showing
01802         # completed jobs and one was reprinted.
01803         if not self.jobiters.has_key (jobid):
01804             self.add_job (jobid, jobdata)
01805         elif mon == self.my_monitor:
01806             # Copy over any missing attributes such as user and title.
01807             for attr, value in jobdata.iteritems ():
01808                 if not self.jobs[jobid].has_key (attr):
01809                     self.jobs[jobid][attr] = value
01810                     debugprint ("Add %s=%s (my job)" % (attr, value))
01811 
01812         # If we failed to get required attributes for the job, bail.
01813         if not self.jobiters.has_key (jobid):
01814             return
01815 
01816         if self.job_is_active (jobdata):
01817             self.active_jobs.add (jobid)
01818         elif jobid in self.active_jobs:
01819             self.active_jobs.remove (jobid)
01820 
01821         self.update_status (have_jobs=True)
01822         if self.applet:
01823             if not self.job_is_active (jobdata):
01824                 return
01825 
01826             for reason in self.printer_state_reasons.get (printer, []):
01827                 if not reason.user_notified:
01828                     self.notify_printer_state_reason_if_important (reason)
01829 
01830     def job_event (self, mon, jobid, eventname, event, jobdata):
01831         uri = jobdata.get ('job-printer-uri', '')
01832         try:
01833             printer = self.printer_uri_index.lookup (uri)
01834         except KeyError:
01835             printer = uri
01836 
01837         if self.specific_dests and printer not in self.specific_dests:
01838             return
01839 
01840         jobdata['job-printer-name'] = printer
01841 
01842         if self.job_is_active (jobdata):
01843             self.active_jobs.add (jobid)
01844         elif jobid in self.active_jobs:
01845             self.active_jobs.remove (jobid)
01846 
01847         self.update_job (jobid, jobdata)
01848         self.update_status ()
01849 
01850         # Check that the job still exists, as update_status re-enters
01851         # the main loop in order to paint/hide the tray icon.  Really
01852         # that should probably be deferred to the idle handler, but
01853         # for the moment just deal with the fact that the job might
01854         # have gone (bug #640904).
01855         if not self.jobs.has_key (jobid):
01856             return
01857 
01858         jobdata = self.jobs[jobid]
01859 
01860         # If the job has finished, let the user know.
01861         if self.applet and (eventname == 'job-completed' or
01862                             (eventname == 'job-state-changed' and
01863                              event['job-state'] == cups.IPP_JOB_COMPLETED)):
01864             reasons = event['job-state-reasons']
01865             if type (reasons) != list:
01866                 reasons = [reasons]
01867 
01868             canceled = False
01869             for reason in reasons:
01870                 if reason.startswith ("job-canceled"):
01871                     canceled = True
01872                     break
01873 
01874             if not canceled:
01875                 self.notify_completed_job (jobid)
01876 
01877         # Look out for stopped jobs.
01878         if (self.applet and
01879             (eventname == 'job-stopped' or
01880              (eventname == 'job-state-changed' and
01881               event['job-state'] in [cups.IPP_JOB_STOPPED,
01882                                      cups.IPP_JOB_PENDING])) and
01883             not jobid in self.stopped_job_prompts):
01884             # Why has the job stopped?  It might be due to a job error
01885             # of some sort, or it might be that the backend requires
01886             # authentication.  If the latter, the job will be held not
01887             # stopped, and the job-hold-until attribute will be
01888             # 'auth-info-required'.  This was already checked for in
01889             # update_job.
01890             may_be_problem = True
01891             jstate = jobdata['job-state']
01892             if (jstate == cups.IPP_JOB_PROCESSING or
01893                 (jstate == cups.IPP_JOB_HELD and
01894                  jobdata['job-hold-until'] == 'auth-info-required')):
01895                 # update_job already dealt with this.
01896                 may_be_problem = False
01897             else:
01898                 # Other than that, unfortunately the only
01899                 # clue we get is the notify-text, which is not
01900                 # translated into our native language.  We'd better
01901                 # try parsing it.  In CUPS-1.3.6 the possible strings
01902                 # are:
01903                 #
01904                 # "Job stopped due to filter errors; please consult
01905                 # the error_log file for details."
01906                 #
01907                 # "Job stopped due to backend errors; please consult
01908                 # the error_log file for details."
01909                 #
01910                 # "Job held due to backend errors; please consult the
01911                 # error_log file for details."
01912                 #
01913                 # "Authentication is required for job %d."
01914                 # [This case is handled in the update_job method.]
01915                 #
01916                 # "Job stopped due to printer being paused"
01917                 # [This should be ignored, as the job was doing just
01918                 # fine until the printer was stopped for other reasons.]
01919                 notify_text = event['notify-text']
01920                 document = jobdata['job-name']
01921                 if notify_text.find ("backend errors") != -1:
01922                     message = _("There was a problem sending document `%s' "
01923                                 "(job %d) to the printer.") % (document, jobid)
01924                 elif notify_text.find ("filter errors") != -1:
01925                     message = _("There was a problem processing document `%s' "
01926                                 "(job %d).") % (document, jobid)
01927                 elif (notify_text.find ("being paused") != -1 or
01928                       jstate != cups.IPP_JOB_STOPPED):
01929                     may_be_problem = False
01930                 else:
01931                     # Give up and use the provided message untranslated.
01932                     message = _("There was a problem printing document `%s' "
01933                                 "(job %d): `%s'.") % (document, jobid,
01934                                                       notify_text)
01935 
01936             if may_be_problem:
01937                 debugprint ("Problem detected")
01938                 self.toggle_window_display (None, force_show=True)
01939                 dialog = gtk.Dialog (_("Print Error"), self.JobsWindow, 0,
01940                                      (_("_Diagnose"), gtk.RESPONSE_NO,
01941                                         gtk.STOCK_OK, gtk.RESPONSE_OK))
01942                 dialog.set_default_response (gtk.RESPONSE_OK)
01943                 dialog.set_border_width (6)
01944                 dialog.set_resizable (False)
01945                 dialog.set_icon_name (ICON)
01946                 hbox = gtk.HBox (False, 12)
01947                 hbox.set_border_width (6)
01948                 image = gtk.Image ()
01949                 image.set_from_stock (gtk.STOCK_DIALOG_ERROR,
01950                                       gtk.ICON_SIZE_DIALOG)
01951                 hbox.pack_start (image, False, False, 0)
01952                 vbox = gtk.VBox (False, 12)
01953 
01954                 markup = ('<span weight="bold" size="larger">' +
01955                           _("Print Error") + '</span>\n\n' +
01956                           saxutils.escape (message))
01957                 try:
01958                     if event['printer-state'] == cups.IPP_PRINTER_STOPPED:
01959                         name = event['printer-name']
01960                         markup += ' '
01961                         markup += (_("The printer called `%s' has "
01962                                      "been disabled.") % name)
01963                 except KeyError:
01964                     pass
01965 
01966                 label = gtk.Label (markup)
01967                 label.set_use_markup (True)
01968                 label.set_line_wrap (True)
01969                 label.set_alignment (0, 0)
01970                 vbox.pack_start (label, False, False, 0)
01971                 hbox.pack_start (vbox, False, False, 0)
01972                 dialog.vbox.pack_start (hbox)
01973                 dialog.connect ('response',
01974                                 self.print_error_dialog_response, jobid)
01975                 self.stopped_job_prompts.add (jobid)
01976                 dialog.show_all ()
01977 
01978     def job_removed (self, mon, jobid, eventname, event):
01979         # If the job has finished, let the user know.
01980         if self.applet and (eventname == 'job-completed' or
01981                             (eventname == 'job-state-changed' and
01982                              event['job-state'] == cups.IPP_JOB_COMPLETED)):
01983             reasons = event['job-state-reasons']
01984             debugprint (reasons)
01985             if type (reasons) != list:
01986                 reasons = [reasons]
01987 
01988             canceled = False
01989             for reason in reasons:
01990                 if reason.startswith ("job-canceled"):
01991                     canceled = True
01992                     break
01993 
01994             if not canceled:
01995                 self.notify_completed_job (jobid)
01996 
01997         if self.jobiters.has_key (jobid):
01998             self.store.remove (self.jobiters[jobid])
01999             del self.jobiters[jobid]
02000             del self.jobs[jobid]
02001 
02002         if jobid in self.active_jobs:
02003             self.active_jobs.remove (jobid)
02004 
02005         if self.jobs_attrs.has_key (jobid):
02006             del self.jobs_attrs[jobid]
02007 
02008         self.update_status ()
02009 
02010     def state_reason_added (self, mon, reason):
02011         (title, text) = reason.get_description ()
02012         printer = reason.get_printer ()
02013 
02014         try:
02015             l = self.printer_state_reasons[printer]
02016         except KeyError:
02017             l = []
02018             self.printer_state_reasons[printer] = l
02019 
02020         reason.user_notified = False
02021         l.append (reason)
02022         self.update_status ()
02023         self.treeview.queue_draw ()
02024 
02025         if not self.applet:
02026             return
02027 
02028         # Find out if the user has jobs queued for that printer.
02029         for job, data in self.jobs.iteritems ():
02030             if not self.job_is_active (data):
02031                 continue
02032             if data['job-printer-name'] == printer:
02033                 # Yes!  Notify them of the state reason, if necessary.
02034                 self.notify_printer_state_reason_if_important (reason)
02035                 break
02036 
02037     def state_reason_removed (self, mon, reason):
02038         printer = reason.get_printer ()
02039         try:
02040             reasons = self.printer_state_reasons[printer]
02041         except KeyError:
02042             debugprint ("Printer not found")
02043             return
02044 
02045         try:
02046             i = reasons.index (reason)
02047         except IndexError:
02048             debugprint ("Reason not found")
02049             return
02050 
02051         del reasons[i]
02052 
02053         self.update_status ()
02054         self.treeview.queue_draw ()
02055 
02056         if not self.applet:
02057             return
02058 
02059         tuple = reason.get_tuple ()
02060         try:
02061             notification = self.state_reason_notifications[tuple]
02062             if notification.get_data ('closed') != True:
02063                 try:
02064                     notification.close ()
02065                 except glib.GError:
02066                     # Can fail if the notification wasn't even shown
02067                     # yet (as in bug #545733).
02068                     pass
02069 
02070             del self.state_reason_notifications[tuple]
02071             self.set_statusicon_visibility ()
02072         except KeyError:
02073             pass
02074 
02075     def still_connecting (self, mon, reason):
02076         if not self.applet:
02077             return
02078 
02079         self.notify_printer_state_reason (reason)
02080 
02081     def now_connected (self, mon, printer):
02082         if not self.applet:
02083             return
02084 
02085         # Find the connecting-to-device state reason.
02086         try:
02087             reasons = self.printer_state_reasons[printer]
02088             reason = None
02089             for r in reasons:
02090                 if r.get_reason () == "connecting-to-device":
02091                     reason = r
02092                     break
02093         except KeyError:
02094             debugprint ("Couldn't find state reason (no reasons)!")
02095 
02096         if reason != None:
02097             tuple = reason.get_tuple ()
02098         else:
02099             debugprint ("Couldn't find state reason in list!")
02100             tuple = None
02101             for (level,
02102                  p,
02103                  r) in self.state_reason_notifications.keys ():
02104                 if p == printer and r == "connecting-to-device":
02105                     debugprint ("Found from notifications list")
02106                     tuple = (level, p, r)
02107                     break
02108 
02109             if tuple == None:
02110                 debugprint ("Unexpected now_connected signal "
02111                             "(reason not in notifications list)")
02112                 return
02113 
02114         try:
02115             notification = self.state_reason_notifications[tuple]
02116         except KeyError:
02117             debugprint ("Unexpected now_connected signal")
02118             return
02119 
02120         if notification.get_data ('closed') != True:
02121             try:
02122                 notification.close ()
02123             except glib.GError:
02124                 # Can fail if the notification wasn't even shown
02125                 pass
02126             notification.set_data ('closed', True)
02127 
02128     def printer_added (self, mon, printer):
02129         self.printer_uri_index.add_printer (printer)
02130 
02131     def printer_event (self, mon, printer, eventname, event):
02132         self.printer_uri_index.update_from_attrs (printer, event)
02133 
02134     def printer_removed (self, mon, printer):
02135         self.printer_uri_index.remove_printer (printer)
02136 
02137     ### Cell data functions
02138     def _set_job_job_number_text (self, column, cell, model, iter, *data):
02139         cell.set_property("text", str (model.get_value (iter, 0)))
02140 
02141     def _set_job_user_text (self, column, cell, model, iter, *data):
02142         jobid = model.get_value (iter, 0)
02143         job = self.jobs[jobid]
02144         cell.set_property("text", job.get ('job-originating-user-name',
02145                                            _("Unknown")))
02146 
02147     def _set_job_document_text (self, column, cell, model, iter, *data):
02148         jobid = model.get_value (iter, 0)
02149         job = self.jobs[jobid]
02150         cell.set_property("text", job.get('job-name', _("Unknown")))
02151 
02152     def _set_job_printer_text (self, column, cell, model, iter, *data):
02153         jobid = model.get_value (iter, 0)
02154         reasons = self.jobs[jobid].get('job-state-reasons')
02155         if reasons == 'printer-stopped':
02156             reason = ' - ' + _("disabled")
02157         else:
02158             reason = ''
02159         cell.set_property("text", self.jobs[jobid]['job-printer-name']+reason)
02160 
02161     def _set_job_size_text (self, column, cell, model, iter, *data):
02162         jobid = model.get_value (iter, 0)
02163         job = self.jobs[jobid]
02164         size = _("Unknown")
02165         if job.has_key ('job-k-octets'):
02166             size = str (job['job-k-octets']) + 'k'
02167         cell.set_property("text", size)
02168 
02169     def _find_job_state_text (self, job):
02170         data = self.jobs[job]
02171         jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
02172         s = int (jstate)
02173         job_requires_auth = (s == cups.IPP_JOB_HELD and
02174                              data.get ('job-hold-until', 'none') ==
02175                              'auth-info-required')
02176         state = None
02177         if job_requires_auth:
02178             state = _("Held for authentication")
02179         elif s == cups.IPP_JOB_HELD:
02180             state = _("Held")
02181             until = data.get ('job-hold-until')
02182             if until != None:
02183                 try:
02184                     colon1 = until.find (':')
02185                     if colon1 != -1:
02186                         now = time.gmtime ()
02187                         hh = int (until[:colon1])
02188                         colon2 = until[colon1 + 1:].find (':')
02189                         if colon2 != -1:
02190                             colon2 += colon1 + 1
02191                             mm = int (until[colon1 + 1:colon2])
02192                             ss = int (until[colon2 + 1:])
02193                         else:
02194                             mm = int (until[colon1 + 1:])
02195                             ss = 0
02196 
02197                         day = now.tm_mday
02198                         if (hh < now.tm_hour or
02199                             (hh == now.tm_hour and
02200                              (mm < now.tm_min or
02201                               (mm == now.tm_min and ss < now.tm_sec)))):
02202                             day += 1
02203 
02204                         hold = (now.tm_year, now.tm_mon, day,
02205                                 hh, mm, ss, 0, 0, -1)
02206                         old_tz = os.environ.get("TZ")
02207                         os.environ["TZ"] = "UTC"
02208                         simpletime = time.mktime (hold)
02209 
02210                         if old_tz == None:
02211                             del os.environ["TZ"]
02212                         else:
02213                             os.environ["TZ"] = old_tz
02214 
02215                         local = time.localtime (simpletime)
02216                         state = _("Held until %s") % time.strftime ("%X", local)
02217                 except ValueError:
02218                     pass
02219             if until == "day-time":
02220                 state = _("Held until day-time")
02221             elif until == "evening":
02222                 state = _("Held until evening")
02223             elif until == "night":
02224                 state = _("Held until night-time")
02225             elif until == "second-shift":
02226                 state = _("Held until second shift")
02227             elif until == "third-shift":
02228                 state = _("Held until third shift")
02229             elif until == "weekend":
02230                 state = _("Held until weekend")
02231         else:
02232             try:
02233                 state = { cups.IPP_JOB_PENDING: _("Pending"),
02234                           cups.IPP_JOB_PROCESSING: _("Processing"),
02235                           cups.IPP_JOB_STOPPED: _("Stopped"),
02236                           cups.IPP_JOB_CANCELED: _("Canceled"),
02237                           cups.IPP_JOB_ABORTED: _("Aborted"),
02238                           cups.IPP_JOB_COMPLETED: _("Completed") }[s]
02239             except IndexError:
02240                 pass
02241 
02242         if state == None:
02243             state = _("Unknown")
02244 
02245         return state
02246 
02247     def _set_job_status_icon (self, column, cell, model, iter, *data):
02248         jobid = model.get_value (iter, 0)
02249         data = self.jobs[jobid]
02250         jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
02251         s = int (jstate)
02252         if s == cups.IPP_JOB_PROCESSING:
02253             icon = self.icon_jobs_processing
02254         else:
02255             icon = self.icon_jobs
02256 
02257         if s == cups.IPP_JOB_HELD:
02258             theme = gtk.icon_theme_get_default ()
02259             emblem = theme.load_icon (gtk.STOCK_MEDIA_PAUSE, 22 / 2, 0)
02260             copy = icon.copy ()
02261             emblem.composite (copy, 0, 0,
02262                               copy.get_width (),
02263                               copy.get_height (),
02264                               copy.get_width () / 2 - 1,
02265                               copy.get_height () / 2 - 1,
02266                               1.0, 1.0,
02267                               gtk.gdk.INTERP_NEAREST, 255)
02268             icon = copy
02269         else:
02270             # Check state reasons.
02271             printer = data['job-printer-name']
02272             icon = self.add_state_reason_emblem (icon, printer=printer)
02273 
02274         cell.set_property ("pixbuf", icon)
02275 
02276     def _set_job_status_text (self, column, cell, model, iter, *data):
02277         jobid = model.get_value (iter, 0)
02278         data = self.jobs[jobid]
02279         try:
02280             text = data['_status_text']
02281         except KeyError:
02282             text = self._find_job_state_text (jobid)
02283             data['_status_text'] = text
02284 
02285         printer = data['job-printer-name']
02286         reasons = self.printer_state_reasons.get (printer, [])
02287         if len (reasons) > 0:
02288             worst_reason = reasons[0]
02289             for reason in reasons[1:]:
02290                 if reason > worst_reason:
02291                     worst_reason = reason
02292             (title, unused) = worst_reason.get_description ()
02293             text += " - " + title
02294 
02295         cell.set_property ("text", text)
02296 
02297 gobject.type_register (JobViewer)