~rodrigo-moya/ubuntu/oneiric/system-config-printer/fix-787694

1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1
## Copyright (C) 2007, 2008, 2009, 2010, 2011 Red Hat, Inc.
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
2
## Authors:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
3
##  Tim Waugh <twaugh@redhat.com>
4
##  Jiri Popelka <jpopelka@redhat.com>
5
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
6
## This program is free software; you can redistribute it and/or modify
7
## it under the terms of the GNU General Public License as published by
8
## the Free Software Foundation; either version 2 of the License, or
9
## (at your option) any later version.
10
11
## This program is distributed in the hope that it will be useful,
12
## but WITHOUT ANY WARRANTY; without even the implied warranty of
13
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
## GNU General Public License for more details.
15
16
## You should have received a copy of the GNU General Public License
17
## along with this program; if not, write to the Free Software
18
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20
import asyncconn
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
21
import authconn
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
22
import cups
23
import dbus
24
import dbus.glib
25
import dbus.service
26
import pynotify
27
import gettext
28
import glib
1.1.52 by Till Kamppeter
Import upstream version 1.2.0+20100408
29
import gobject
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
30
import gtk
31
import gtk.gdk
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
32
from gui import GtkGUI
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
33
import monitor
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
34
import os, shutil
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
35
import pango
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
36
import pwd
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
37
import smburi
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
38
import subprocess
39
import sys
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
40
import time
41
import urllib
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
42
from xml.sax import saxutils
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
43
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
44
from debug import *
45
import config
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
46
import statereason
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
47
import errordialogs
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
48
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
49
try:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
50
    import gnomekeyring
51
    USE_KEYRING=True
52
except ImportError:
53
    USE_KEYRING=False
54
55
from gettext import gettext as _
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
56
from statereason import StateReason
57
58
pkgdata = config.pkgdatadir
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
59
ICON="printer"
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
60
ICON_SIZE=22
1.1.54 by Till Kamppeter
Import upstream version 1.2.3+20100723
61
SEARCHING_ICON="document-print-preview"
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
62
63
# We need to call pynotify.init before we can check the server for caps
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
64
pynotify.init('System Config Printer Notification')
65
66
class PrinterURIIndex:
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
67
    def __init__ (self, names=[]):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
68
        self.printer = {}
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
69
        self.names = names
70
        self._collect_names ()
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
71
72
    def _collect_names (self, connection=None):
73
        if not self.names:
74
            return
75
76
        if not connection:
77
            try:
78
                c = cups.Connection ()
79
            except RuntimeError:
80
                return
81
82
        for name in self.names:
83
            self.add_printer (name, connection=c)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
84
85
        self.names = []
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
86
87
    def add_printer (self, printer, connection=None):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
88
        try:
1.1.52 by Till Kamppeter
Import upstream version 1.2.0+20100408
89
            self._map_printer (name=printer, connection=connection)
90
        except KeyError:
91
            return
92
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
93
    def update_from_attrs (self, printer, attrs):
94
        uris = []
95
        if attrs.has_key ('printer-uri-supported'):
96
            uri_supported = attrs['printer-uri-supported']
97
            if type (uri_supported) != list:
98
                uri_supported = [uri_supported]
99
            uris.extend (uri_supported)
100
        if attrs.has_key ('notify-printer-uri'):
101
            uris.append (attrs['notify-printer-uri'])
102
        if attrs.has_key ('printer-more-info'):
103
            uris.append (attrs['printer-more-info'])
104
105
        for uri in uris:
106
            self.printer[uri] = printer
107
108
    def remove_printer (self, printer):
109
        # Remove references to this printer in the URI map.
110
        self._collect_names ()
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
111
        uris = self.printer.keys ()
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
112
        for uri in uris:
113
            if self.printer[uri] == printer:
114
                del self.printer[uri]
115
116
    def lookup (self, uri, connection=None):
117
        self._collect_names ()
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
118
        try:
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
119
            return self.printer[uri]
120
        except KeyError:
121
            return self._map_printer (uri=uri, connection=connection)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
122
123
    def all_printer_names (self):
124
        self._collect_names ()
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
125
        return set (self.printer.values ())
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
126
127
    def lookup_cached_by_name (self, name):
128
        self._collect_names ()
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
129
        for uri, printer in self.printer.iteritems ():
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
130
            if printer == name:
131
                return uri
132
133
        raise KeyError
134
135
    def _map_printer (self, uri=None, name=None, connection=None):
136
        try:
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
137
            if connection == None:
138
                connection = cups.Connection ()
139
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
140
            r = ['printer-name', 'printer-uri-supported', 'printer-more-info']
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
141
            if uri != None:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
142
                attrs = connection.getPrinterAttributes (uri=uri,
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
143
                                                         requested_attributes=r)
144
            else:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
145
                attrs = connection.getPrinterAttributes (name,
146
                                                         requested_attributes=r)
147
        except RuntimeError:
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
148
            # cups.Connection() failed
149
            raise KeyError
150
        except cups.IPPError:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
151
            # URI not known.
152
            raise KeyError
153
154
        name = attrs['printer-name']
155
        self.update_from_attrs (name, attrs)
156
        if uri != None:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
157
            self.printer[uri] = name
158
        return name
159
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
160
161
class CancelJobsOperation(gobject.GObject):
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
162
    __gsignals__ = {
163
        'destroy':     (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
164
        'job-deleted': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
165
                        [gobject.TYPE_INT]),
166
        'ipp-error':   (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
167
                        [gobject.TYPE_INT, gobject.TYPE_PYOBJECT]),
168
        'finished':    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
169
        }
170
171
    def __init__ (self, parent, host, port, encryption, jobids, purge_job):
172
        gobject.GObject.__init__ (self)
173
        self.jobids = list (jobids)
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
174
        self.purge_job = purge_job
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
175
        self.host = host
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
176
        self.port = port
177
        self.encryption = encryption
178
        if purge_job:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
179
            if len(self.jobids) > 1:
180
                dialog_title = _("Delete Jobs")
181
                dialog_label = _("Do you really want to delete these jobs?")
182
            else:
183
                dialog_title = _("Delete Job")
184
                dialog_label = _("Do you really want to delete this job?")
185
        else:
186
            if len(self.jobids) > 1:
187
                dialog_title = _("Cancel Jobs")
188
                dialog_label = _("Do you really want to cancel these jobs?")
189
            else:
190
                dialog_title = _("Cancel Job")
191
                dialog_label = _("Do you really want to cancel this job?")
192
193
        dialog = gtk.Dialog (dialog_title, parent,
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
194
                             gtk.DIALOG_MODAL |
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
195
                             gtk.DIALOG_DESTROY_WITH_PARENT |
196
                             gtk.DIALOG_NO_SEPARATOR,
197
                             (_("Keep Printing"), gtk.RESPONSE_NO,
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
198
                              dialog_title, gtk.RESPONSE_YES))
199
        dialog.set_default_response (gtk.RESPONSE_NO)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
200
        dialog.set_border_width (6)
201
        dialog.set_resizable (False)
202
        hbox = gtk.HBox (False, 12)
203
        image = gtk.Image ()
204
        image.set_from_stock (gtk.STOCK_DIALOG_QUESTION, gtk.ICON_SIZE_DIALOG)
205
        image.set_alignment (0.0, 0.0)
206
        hbox.pack_start (image, False, False, 0)
207
        label = gtk.Label (dialog_label)
208
        label.set_line_wrap (True)
209
        label.set_alignment (0.0, 0.0)
210
        hbox.pack_start (label, False, False, 0)
211
        dialog.vbox.pack_start (hbox, False, False, 0)
212
        dialog.connect ("response", self.on_job_cancel_prompt_response)
213
        dialog.connect ("delete-event", self.on_job_cancel_prompt_delete)
214
        dialog.show_all ()
215
        self.dialog = dialog
216
        self.connection = None
217
        debugprint ("+%s" % self)
218
219
    def __del__ (self):
220
        debugprint ("-%s" % self)
221
222
    def do_destroy (self):
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
223
        if self.connection:
224
            self.connection.destroy ()
225
            self.connection = None
226
227
        if self.dialog:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
228
            self.dialog.destroy ()
229
            self.dialog = None
230
231
        debugprint ("DESTROY: %s" % self)
232
233
    def destroy (self):
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
234
        self.emit ('destroy')
235
236
    def on_job_cancel_prompt_delete (self, dialog, event):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
237
        self.on_job_cancel_prompt_response (dialog, gtk.RESPONSE_NO)
238
239
    def on_job_cancel_prompt_response (self, dialog, response):
240
        dialog.destroy ()
241
        self.dialog = None
242
243
        if response != gtk.RESPONSE_YES:
244
            self.emit ('finished')
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
245
            return
246
247
        if len(self.jobids) == 0:
248
            self.emit ('finished')
249
            return
250
251
        asyncconn.Connection (host=self.host,
252
                              port=self.port,
253
                              encryption=self.encryption,
254
                              reply_handler=self._connected,
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
255
                              error_handler=self._connect_failed)
256
257
    def _connect_failed (self, connection, exc):
258
        debugprint ("CancelJobsOperation._connect_failed %s:%s" % (connection, repr (exc)))
259
260
    def _connected (self, connection, result):
261
        self.connection = connection
262
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
263
        if self.purge_job:
264
            operation = _("deleting job")
265
        else:
266
            operation = _("canceling job")
267
268
        self.connection._begin_operation (operation)
269
        self.connection.cancelJob (self.jobids[0], self.purge_job,
270
                                   reply_handler=self.cancelJob_finish,
271
                                   error_handler=self.cancelJob_error)
272
273
    def cancelJob_error (self, connection, exc):
274
        debugprint ("cancelJob_error %s:%s" % (connection, repr (exc)))
275
        if type (exc) == cups.IPPError:
276
            (e, m) = exc.args
277
            if (e != cups.IPP_NOT_POSSIBLE and
278
                e != cups.IPP_NOT_FOUND):
279
                self.emit ('ipp-error', self.jobids[0], exc)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
280
            self.cancelJob_finish(connection, None)
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
281
        else:
282
            self.connection._end_operation ()
283
            self.connection.destroy ()
284
            self.connection = None
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
285
            self.emit ('ipp-error', self.jobids[0], exc)
286
            # Give up.
287
            self.emit ('finished')
288
            return
289
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
290
    def cancelJob_finish (self, connection, result):
291
        debugprint ("cancelJob_finish %s:%s" % (connection, repr (result)))
292
        self.emit ('job-deleted', self.jobids[0])
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
293
        del self.jobids[0]
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
294
        if not self.jobids:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
295
            # Last job canceled.
296
            self.connection._end_operation ()
297
            self.connection.destroy ()
298
            self.connection = None
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
299
            self.emit ('finished')
300
            return
301
        else:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
302
            # there are other jobs to cancel/delete
303
            connection.cancelJob (self.jobids[0], self.purge_job,
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
304
                                  reply_handler=self.cancelJob_finish,
305
                                  error_handler=self.cancelJob_error)
306
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
307
gobject.type_register (CancelJobsOperation)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
308
309
class JobViewer (GtkGUI):
310
    required_job_attributes = set(['job-k-octets',
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
311
                                   'job-name',
312
                                   'job-originating-user-name',
313
                                   'job-printer-uri',
314
                                   'job-state',
315
                                   'time-at-creation',
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
316
                                   'job-preserved'])
317
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
318
    __gsignals__ = {
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
319
        'finished':    (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [])
320
        }
321
322
    def __init__(self, bus=None, loop=None,
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
323
                 applet=False, suppress_icon_hide=False,
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
324
                 my_jobs=True, specific_dests=None,
325
                 parent=None):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
326
        gobject.GObject.__init__ (self)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
327
        self.loop = loop
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
328
        self.applet = applet
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
329
        self.suppress_icon_hide = suppress_icon_hide
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
330
        self.my_jobs = my_jobs
331
        self.specific_dests = specific_dests
332
        notify_caps = pynotify.get_server_caps ()
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
333
        self.notify_has_actions = "actions" in notify_caps
334
        self.notify_has_persistence = "persistence" in notify_caps
335
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
336
        self.jobs = {}
337
        self.jobiters = {}
338
        self.jobids = []
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
339
        self.jobs_attrs = {} # dict of jobid->(GtkListStore, page_index)
340
        self.active_jobs = set() # of job IDs
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
341
        self.stopped_job_prompts = set() # of job IDs
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
342
        self.printer_state_reasons = {}
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
343
        self.num_jobs_when_hidden = 0
344
        self.connecting_to_device = {} # dict of printer->time first seen
345
        self.state_reason_notifications = {}
346
        self.auth_info_dialogs = {} # by job ID
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
347
        self.job_creation_times_timer = None
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
348
        self.new_printer_notifications = {}
349
        self.completed_job_notifications = {}
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
350
        self.authenticated_jobs = set() # of job IDs
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
351
        self.ops = []
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
352
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
353
        self.getWidgets ({"JobsWindow":
354
                              ["JobsWindow",
355
                               "treeview",
356
                               "statusbar",
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
357
                               "toolbar"],
358
                          "statusicon_popupmenu":
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
359
                              ["statusicon_popupmenu"]},
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
360
361
                         domain=config.PACKAGE)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
362
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
363
        job_action_group = gtk.ActionGroup ("JobActionGroup")
364
        job_action_group.add_actions ([
365
                ("cancel-job", gtk.STOCK_CANCEL, _("_Cancel"), None,
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
366
                 _("Cancel selected jobs"), self.on_job_cancel_activate),
367
                ("delete-job", gtk.STOCK_DELETE, _("_Delete"), None,
368
                 _("Delete selected jobs"), self.on_job_delete_activate),
369
                ("hold-job", gtk.STOCK_MEDIA_PAUSE, _("_Hold"), None,
370
                 _("Hold selected jobs"), self.on_job_hold_activate),
371
                ("release-job", gtk.STOCK_MEDIA_PLAY, _("_Release"), None,
372
                 _("Release selected jobs"), self.on_job_release_activate),
373
                ("reprint-job", gtk.STOCK_REDO, _("Re_print"), None,
374
                 _("Reprint selected jobs"), self.on_job_reprint_activate),
375
                ("retrieve-job", gtk.STOCK_SAVE_AS, _("Re_trieve"), None,
376
                 _("Retrieve selected jobs"), self.on_job_retrieve_activate),
377
                ("move-job", None, _("_Move To"), None, None, None),
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
378
                ("authenticate-job", None, _("_Authenticate"), None, None,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
379
                 self.on_job_authenticate_activate),
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
380
                ("job-attributes", None, _("_View Attributes"), None, None,
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
381
                 self.on_job_attributes_activate),
382
                ("close", gtk.STOCK_CLOSE, None, "<ctrl>w",
383
                 _("Close this window"), self.on_delete_event)
384
                ])
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
385
        self.job_ui_manager = gtk.UIManager ()
386
        self.job_ui_manager.insert_action_group (job_action_group, -1)
387
        self.job_ui_manager.add_ui_from_string (
388
"""
389
<ui>
390
 <accelerator action="cancel-job"/>
391
 <accelerator action="delete-job"/>
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
392
 <accelerator action="hold-job"/>
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
393
 <accelerator action="release-job"/>
394
 <accelerator action="reprint-job"/>
395
 <accelerator action="retrieve-job"/>
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
396
 <accelerator action="move-job"/>
397
 <accelerator action="authenticate-job"/>
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
398
 <accelerator action="job-attributes"/>
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
399
 <accelerator action="close"/>
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
400
</ui>
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
401
"""
402
)
403
        self.job_ui_manager.ensure_update ()
404
        self.JobsWindow.add_accel_group (self.job_ui_manager.get_accel_group ())
405
        self.job_context_menu = gtk.Menu ()
406
        for action_name in ["cancel-job",
407
                            "delete-job",
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
408
                            "hold-job",
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
409
                            "release-job",
410
                            "reprint-job",
411
                            "retrieve-job",
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
412
                            "move-job",
413
                            None,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
414
                            "authenticate-job",
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
415
                            "job-attributes"]:
416
            if not action_name:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
417
                item = gtk.SeparatorMenuItem ()
418
            else:
419
                action = job_action_group.get_action (action_name)
420
                action.set_sensitive (False)
421
                item = action.create_menu_item ()
422
423
                if action_name == 'move-job':
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
424
                    self.move_job_menuitem = item
425
                    printers = gtk.Menu ()
426
                    item.set_submenu (printers)
427
428
            item.show ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
429
            self.job_context_menu.append (item)
430
431
        for action_name in ["cancel-job",
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
432
                            "delete-job",
433
                            "hold-job",
434
                            "release-job",
435
                            "reprint-job",
436
                            "retrieve-job",
437
                            "close"]:
438
            action = job_action_group.get_action (action_name)
439
            action.set_sensitive (action_name == "close")
440
            action.set_is_important (action_name == "close")
441
            item = action.create_tool_item ()
442
            item.show ()
443
            self.toolbar.insert (item, -1)
444
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
445
        for skip, ellipsize, name, setter in \
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
446
                [(False, False, _("Job"), self._set_job_job_number_text),
447
                 (True, False, _("User"), self._set_job_user_text),
448
                 (False, True, _("Document"), self._set_job_document_text),
449
                 (False, True, _("Printer"), self._set_job_printer_text),
450
                 (False, False, _("Size"), self._set_job_size_text)]:
451
            if applet and skip:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
452
                # Skip the user column when running as applet.
453
                continue
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
454
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
455
            cell = gtk.CellRendererText()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
456
            if ellipsize:
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
457
                # Ellipsize the 'Document' and 'Printer' columns.
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
458
                cell.set_property ("ellipsize", pango.ELLIPSIZE_END)
459
                cell.set_property ("width-chars", 20)
460
            column = gtk.TreeViewColumn(name, cell)
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
461
            column.set_cell_data_func (cell, setter)
462
            column.set_resizable(True)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
463
            self.treeview.append_column(column)
464
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
465
        cell = gtk.CellRendererText ()
466
        column = gtk.TreeViewColumn (_("Time submitted"), cell, text=1)
467
        column.set_resizable (True)
468
        self.treeview.append_column (column)
469
470
        column = gtk.TreeViewColumn (_("Status"))
471
        icon = gtk.CellRendererPixbuf ()
472
        column.pack_start (icon, False)
473
        text = gtk.CellRendererText ()
474
        text.set_property ("ellipsize", pango.ELLIPSIZE_END)
475
        text.set_property ("width-chars", 20)
476
        column.pack_start (text, True)
477
        column.set_cell_data_func (icon, self._set_job_status_icon)
478
        column.set_cell_data_func (text, self._set_job_status_text)
479
        self.treeview.append_column (column)
480
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
481
        self.store = gtk.TreeStore(int, str)
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
482
        self.store.set_sort_column_id (0, gtk.SORT_DESCENDING)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
483
        self.treeview.set_model(self.store)
484
        self.treeview.set_rules_hint (True)
485
        self.selection = self.treeview.get_selection()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
486
        self.selection.set_mode(gtk.SELECTION_MULTIPLE)
487
        self.selection.connect('changed', self.on_selection_changed)
488
        self.treeview.connect ('button_release_event',
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
489
                               self.on_treeview_button_release_event)
490
        self.treeview.connect ('popup-menu', self.on_treeview_popup_menu)
491
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
492
        self.JobsWindow.set_icon_name (ICON)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
493
        self.JobsWindow.hide ()
494
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
495
        if specific_dests:
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
496
            the_dests = reduce (lambda x, y: x + ", " + y, specific_dests)
497
498
        if my_jobs:
499
            if specific_dests:
500
                title = _("my jobs on %s") % the_dests
501
            else:
502
                title = _("my jobs")
503
        else:
504
            if specific_dests:
505
                title = "%s" % the_dests
506
            else:
507
                title = _("all jobs")
508
        self.JobsWindow.set_title (_("Document Print Status (%s)") % title)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
509
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
510
        if parent:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
511
            self.JobsWindow.set_transient_for (parent)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
512
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
513
        def load_icon(theme, icon):
1.1.54 by Till Kamppeter
Import upstream version 1.2.3+20100723
514
            try:
515
                pixbuf = theme.load_icon (icon, ICON_SIZE, 0)
516
            except gobject.GError:
517
                debugprint ("No %s icon available" % icon)
518
                # Just create an empty pixbuf.
519
                pixbuf = gtk.gdk.Pixbuf (gtk.gdk.COLORSPACE_RGB,
520
                                         True, 8, ICON_SIZE, ICON_SIZE)
521
                pixbuf.fill (0)
522
            return pixbuf
523
524
        theme = gtk.icon_theme_get_default ()
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
525
        self.icon_jobs = load_icon (theme, ICON)
1.1.54 by Till Kamppeter
Import upstream version 1.2.3+20100723
526
        self.icon_jobs_processing = load_icon (theme, "printer-printing")
527
        self.icon_no_jobs = self.icon_jobs.copy ()
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
528
        self.icon_no_jobs.fill (0)
529
        self.icon_jobs.composite (self.icon_no_jobs,
530
                                  0, 0,
531
                                  self.icon_no_jobs.get_width(),
532
                                  self.icon_no_jobs.get_height(),
533
                                  0, 0,
534
                                  1.0, 1.0,
535
                                  gtk.gdk.INTERP_BILINEAR,
536
                                  127)
537
        if self.applet and not self.notify_has_persistence:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
538
            self.statusicon = gtk.StatusIcon ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
539
            self.statusicon.set_from_pixbuf (self.icon_no_jobs)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
540
            self.statusicon.connect ('activate', self.toggle_window_display)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
541
            self.statusicon.connect ('popup-menu', self.on_icon_popupmenu)
542
            self.statusicon.set_visible (False)
543
544
        # D-Bus
545
        if bus == None:
546
            bus = dbus.SystemBus ()
547
548
        self.set_process_pending (True)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
549
        self.host = cups.getServer ()
550
        self.port = cups.getPort ()
551
        self.encryption = cups.getEncryption ()
552
        self.monitor = monitor.Monitor (bus=bus, my_jobs=my_jobs,
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
553
                                        host=self.host, port=self.port,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
554
                                        encryption=self.encryption)
555
        self.monitor.connect ('refresh', self.on_refresh)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
556
        self.monitor.connect ('job-added', self.job_added)
557
        self.monitor.connect ('job-event', self.job_event)
558
        self.monitor.connect ('job-removed', self.job_removed)
559
        self.monitor.connect ('state-reason-added', self.state_reason_added)
560
        self.monitor.connect ('state-reason-removed', self.state_reason_removed)
561
        self.monitor.connect ('still-connecting', self.still_connecting)
562
        self.monitor.connect ('now-connected', self.now_connected)
563
        self.monitor.connect ('printer-added', self.printer_added)
564
        self.monitor.connect ('printer-event', self.printer_event)
565
        self.monitor.connect ('printer-removed', self.printer_removed)
566
        self.monitor.refresh ()
567
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
568
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
569
            self.JobsWindow.show ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
570
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
571
        self.JobsAttributesWindow = gtk.Window()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
572
        self.JobsAttributesWindow.set_title (_("Job attributes"))
573
        self.JobsAttributesWindow.set_position(gtk.WIN_POS_MOUSE)
574
        self.JobsAttributesWindow.set_default_size(600, 600)
575
        self.JobsAttributesWindow.set_transient_for (self.JobsWindow)
576
        self.JobsAttributesWindow.connect("delete_event",
577
                                          self.job_attributes_on_delete_event)
578
        self.JobsAttributesWindow.add_accel_group (self.job_ui_manager.get_accel_group ())
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
579
        attrs_action_group = gtk.ActionGroup ("AttrsActionGroup")
580
        attrs_action_group.add_actions ([
581
                ("close", gtk.STOCK_CLOSE, None, "<ctrl>w",
582
                 _("Close this window"), self.job_attributes_on_delete_event)
583
                ])
584
        self.attrs_ui_manager = gtk.UIManager ()
585
        self.attrs_ui_manager.insert_action_group (attrs_action_group, -1)
586
        self.attrs_ui_manager.add_ui_from_string (
587
"""
588
<ui>
589
 <accelerator action="close"/>
590
</ui>
591
"""
592
)
593
        self.attrs_ui_manager.ensure_update ()
594
        self.JobsAttributesWindow.add_accel_group (self.attrs_ui_manager.get_accel_group ())
595
        vbox = gtk.VBox ()
596
        self.JobsAttributesWindow.add (vbox)
597
        toolbar = gtk.Toolbar ()
598
        action = self.attrs_ui_manager.get_action ("/close")
599
        item = action.create_tool_item ()
600
        item.set_is_important (True)
601
        toolbar.insert (item, 0)
602
        vbox.pack_start (toolbar, False, False, 0)
603
        self.notebook = gtk.Notebook()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
604
        vbox.pack_start (self.notebook)
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
605
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
606
    def cleanup (self):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
607
        self.monitor.cleanup ()
608
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
609
        self.JobsWindow.hide ()
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
610
611
        # Close any open notifications.
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
612
        for l in [self.new_printer_notifications.values (),
613
                  self.state_reason_notifications.values ()]:
614
            for notification in l:
615
                if notification.get_data ('closed') != True:
616
                    try:
1.1.52 by Till Kamppeter
Import upstream version 1.2.0+20100408
617
                        notification.close ()
618
                    except glib.GError:
619
                        # Can fail if the notification wasn't even shown
620
                        # yet (as in bug #571603).
621
                        pass
622
                    notification.set_data ('closed', True)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
623
624
        if self.job_creation_times_timer != None:
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
625
            gobject.source_remove (self.job_creation_times_timer)
626
            self.job_creation_times_timer = None
627
628
        for op in self.ops:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
629
            op.destroy ()
630
631
        if self.applet and not self.notify_has_persistence:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
632
            self.statusicon.set_visible (False)
633
634
        self.emit ('finished')
635
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
636
    def set_process_pending (self, whether):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
637
        self.process_pending_events = whether
638
639
    def on_delete_event(self, *args):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
640
        if self.applet or not self.loop:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
641
            self.JobsWindow.hide ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
642
            self.JobsWindow.set_data ('visible', False)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
643
            if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
644
                # Being run from main app, not applet
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
645
                self.cleanup ()
646
        else:
647
            self.loop.quit ()
648
        return True
649
650
    def job_attributes_on_delete_event(self, widget, event=None):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
651
        for page in range(self.notebook.get_n_pages()):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
652
            self.notebook.remove_page(-1)
653
        self.jobs_attrs = {}
654
        self.JobsAttributesWindow.hide_all()
655
        return True
656
657
    def show_IPP_Error(self, exception, message):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
658
        return errordialogs.show_IPP_Error (exception, message, self.JobsWindow)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
659
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
660
    def toggle_window_display(self, icon, force_show=False):
661
        visible = self.JobsWindow.get_data('visible')
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
662
        if force_show:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
663
            visible = False
664
665
        if self.notify_has_persistence:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
666
            if visible:
667
                self.JobsWindow.hide ()
668
            else:
669
                self.JobsWindow.show ()
670
        else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
671
            if visible:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
672
                w = self.JobsWindow.window
673
                aw = self.JobsAttributesWindow.window
674
                (s, area, o) = self.statusicon.get_geometry ()
675
                w.set_skip_taskbar_hint (True)
676
                if aw != None:
677
                    aw.set_skip_taskbar_hint (True)
678
679
                w.property_change ("_NET_WM_ICON_GEOMETRY",
680
                                   "CARDINAL", 32,
681
                                   gtk.gdk.PROP_MODE_REPLACE,
682
                                   list (area))
683
                self.JobsWindow.iconify ()
684
            else:
685
                self.JobsWindow.present ()
686
                self.JobsWindow.window.set_skip_taskbar_hint (False)
687
                aw = self.JobsAttributesWindow.window
688
                if aw != None:
689
                    aw.set_skip_taskbar_hint (False)
690
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
691
        self.JobsWindow.set_data ('visible', not visible)
692
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
693
    def on_show_completed_jobs_clicked(self, toggletoolbutton):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
694
        if toggletoolbutton.get_active():
695
            which_jobs = "all"
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
696
        else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
697
            which_jobs = "not-completed"
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
698
        self.monitor.refresh(which_jobs=which_jobs, refresh_all=False)
699
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
700
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
701
    def update_job_creation_times(self):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
702
        now = time.time ()
703
        need_update = False
704
        for job, data in self.jobs.iteritems():
705
            t = _("Unknown")
706
            if data.has_key ('time-at-creation'):
707
                created = data['time-at-creation']
708
                ago = now - created
709
                need_update = True
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
710
                if ago < 2 * 60:
711
                    t = _("a minute ago")
712
                elif ago < 60 * 60:
713
                    mins = int (ago / 60)
714
                    t = _("%d minutes ago") % mins
715
                elif ago < 24 * 60 * 60:
716
                    hours = int (ago / (60 * 60))
717
                    if hours == 1:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
718
                        t = _("an hour ago")
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
719
                    else:
720
                        t = _("%d hours ago") % hours
721
                elif ago < 7 * 24 * 60 * 60:
722
                    days = int (ago / (24 * 60 * 60))
723
                    if days == 1:
724
                        t = _("yesterday")
725
                    else:
726
                        t = _("%d days ago") % days
727
                elif ago < 6 * 7 * 24 * 60 * 60:
728
                    weeks = int (ago / (7 * 24 * 60 * 60))
729
                    if weeks == 1:
730
                        t = _("last week")
731
                    else:
732
                        t = _("%d weeks ago") % weeks
733
                else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
734
                    need_update = False
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
735
                    t = time.strftime ("%B %Y", time.localtime (created))
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
736
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
737
            if self.jobiters.has_key (job):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
738
                iter = self.jobiters[job]
739
                self.store.set_value (iter, 1, t)
740
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
741
        if need_update and not self.job_creation_times_timer:
742
            def update_times_with_locking ():
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
743
                gtk.gdk.threads_enter ()
744
                ret = self.update_job_creation_times ()
745
                gtk.gdk.threads_leave ()
746
                return ret
747
748
            t = gobject.timeout_add_seconds (60, update_times_with_locking)
749
            self.job_creation_times_timer = t
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
750
751
        if not need_update:
752
            if self.job_creation_times_timer:
753
                gobject.source_remove (self.job_creation_times_timer)
754
                self.job_creation_times_timer = None
755
756
        # Return code controls whether the timeout will recur.
757
        return need_update
758
759
    def print_error_dialog_response(self, dialog, response, jobid):
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
760
        dialog.hide ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
761
        dialog.destroy ()
762
        self.stopped_job_prompts.remove (jobid)
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
763
        if response == gtk.RESPONSE_NO:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
764
            # Diagnose
765
            if not self.__dict__.has_key ('troubleshooter'):
766
                import troubleshoot
767
                troubleshooter = troubleshoot.run (self.on_troubleshoot_quit)
768
                self.troubleshooter = troubleshooter
769
770
    def on_troubleshoot_quit(self, troubleshooter):
771
        del self.troubleshooter
772
773
    def add_job (self, job, data, connection=None):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
774
        self.update_job (job, data, connection=connection)
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
775
776
        store = self.store
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
777
        iter = self.store.append (None)
778
        store.set_value (iter, 0, job)
779
        debugprint ("Job %d added" % job)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
780
        self.jobiters[job] = iter
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
781
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
782
        range = self.treeview.get_visible_range ()
783
        if range != None:
784
            (start, end) = range
785
            if (self.store.get_sort_column_id () == (0,
786
                                                     gtk.SORT_DESCENDING) and
787
                start == (1,)):
788
                # This job was added job above the visible range, and
789
                # we are sorting by descending job ID.  Scroll to it.
790
                self.treeview.scroll_to_cell ((0,), None, False, 0.0, 0.0)
791
792
        if not self.job_creation_times_timer:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
793
            def start_updating_job_creation_times():
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
794
                gtk.gdk.threads_enter ()
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
795
                self.update_job_creation_times ()
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
796
                gtk.gdk.threads_leave ()
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
797
                return False
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
798
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
799
            gobject.timeout_add (500, start_updating_job_creation_times)
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
800
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
801
    def update_monitor (self):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
802
        self.monitor.update ()
803
804
    def update_job (self, job, data, connection=None):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
805
        # Fetch required attributes for this job if they are missing.
806
        r = self.required_job_attributes - set (data.keys ())
807
808
        # If we are showing attributes of this job at this moment, update them.
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
809
        if job in self.jobs_attrs:
810
            self.update_job_attributes_viewer(job)
811
812
        if r:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
813
            attrs = None
814
            try:
815
                if connection == None:
816
                    connection = cups.Connection (host=self.host,
817
                                                  port=self.port,
818
                                                  encryption=self.encryption)
819
820
                debugprint ("requesting %s" % r)
821
                r = list (r)
822
                attrs = connection.getJobAttributes (job,
823
                                                     requested_attributes=r)
824
            except RuntimeError:
825
                pass
826
            except AttributeError:
827
                pass
828
            except cups.IPPError:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
829
                # someone else may have purged the job
830
                return
831
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
832
            if attrs:
833
                data.update (attrs)
834
835
        self.jobs[job] = data
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
836
837
        job_requires_auth = False
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
838
        try:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
839
            jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
840
            s = int (jstate)
841
842
            if s in [cups.IPP_JOB_HELD, cups.IPP_JOB_STOPPED]:
843
                jattrs = ['job-state', 'job-hold-until']
844
                pattrs = ['auth-info-required', 'device-uri']
845
                uri = data.get ('job-printer-uri')
846
                c = authconn.Connection (self.JobsWindow,
847
                                         host=self.host,
848
                                         port=self.port,
849
                                         encryption=self.encryption)
850
                attrs = c.getPrinterAttributes (uri = uri,
851
                                                requested_attributes=pattrs)
852
853
                try:
854
                    auth_info_required = attrs['auth-info-required']
855
                except KeyError:
856
                    debugprint ("No auth-info-required attribute; "
857
                                "guessing instead")
858
                    auth_info_required = ['username', 'password']
859
860
                if not isinstance (auth_info_required, list):
861
                    auth_info_required = [auth_info_required]
862
                    attrs['auth-info-required'] = auth_info_required
863
864
                data.update (attrs)
865
866
                attrs = c.getJobAttributes (job,
867
                                            requested_attributes=jattrs)
868
                data.update (attrs)
869
                jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
870
                s = int (jstate)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
871
        except ValueError:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
872
            pass
873
        except RuntimeError:
874
            pass
875
        except cups.IPPError, (e, m):
876
            pass
877
878
        # Invalidate the cached status description and redraw the treeview.
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
879
        try:
880
            del data['_status_text']
881
        except KeyError:
882
            pass
883
        self.treeview.queue_draw ()
884
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
885
        # Check whether authentication is required.
886
        if self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
887
            job_requires_auth = (s == cups.IPP_JOB_HELD and
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
888
                                 data.get ('job-hold-until', 'none') ==
889
                                 'auth-info-required')
890
891
            if (job_requires_auth and
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
892
                not self.auth_info_dialogs.has_key (job)):
893
                try:
894
                    cups.require ("1.9.37")
895
                except:
896
                    debugprint ("Authentication required but "
897
                                "authenticateJob() not available")
898
                    return
899
900
                # Find out which auth-info is required.
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
901
                try_keyring = USE_KEYRING
902
                keyring_attrs = dict()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
903
                auth_info = None
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
904
                if try_keyring and 'password' in auth_info_required:
905
                    auth_info_required = data.get ('auth-info-required', [])
906
                    device_uri = data.get ("device-uri")
907
                    (scheme, rest) = urllib.splittype (device_uri)
908
                    if scheme == 'smb':
909
                        uri = smburi.SMBURI (uri=device_uri)
910
                        (group, server, share,
911
                         user, password) = uri.separate ()
912
                        keyring_attrs["domain"] = str (group)
913
                    else:
914
                        (serverport, rest) = urllib.splithost (rest)
915
                        (server, port) = urllib.splitnport (serverport)
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
916
                    keyring_attrs.update ({ "server": str (server.lower ()),
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
917
                                            "protocol": str (scheme)})
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
918
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
919
                if job in self.authenticated_jobs:
920
                    # We've already tried to authenticate this job before.
921
                    try_keyring = False
922
923
                if try_keyring and 'password' in auth_info_required:
924
                    type = gnomekeyring.ITEM_NETWORK_PASSWORD
925
                    try:
926
                        items = gnomekeyring.find_items_sync (type,
927
                                                              keyring_attrs)
928
                        auth_info = map (lambda x: '', auth_info_required)
929
                        ind = auth_info_required.index ('username')
930
                        auth_info[ind] = items[0].attributes.get ('user', '')
931
                        ind = auth_info_required.index ('password')
932
                        auth_info[ind] = items[0].secret
933
                    except gnomekeyring.NoMatchError:
934
                        debugprint ("gnomekeyring: no match for %s" %
935
                                    keyring_attrs)
936
                    except gnomekeyring.DeniedError:
937
                        debugprint ("gnomekeyring: denied for %s" %
938
                                    keyring_attrs)
939
940
                if try_keyring and c == None:
941
                    try:
942
                        c = authconn.Connection (self.JobsWindow,
943
                                                 host=self.host,
944
                                                 port=self.port,
945
                                                 encryption=self.encryption)
946
                    except RuntimeError:
947
                        try_keyring = False
948
949
                if try_keyring and auth_info != None:
950
                    try:
951
                        c._begin_operation (_("authenticating job"))
952
                        c.authenticateJob (job, auth_info)
953
                        c._end_operation ()
954
                        self.monitor.update ()
955
                        debugprint ("Automatically authenticated job %d" % job)
956
                        self.authenticated_jobs.add (job)
957
                        return
958
                    except cups.IPPError, (e, m):
959
                        c._end_operation ()
960
                        nonfatalException ()
961
                        return
962
                    except:
963
                        c._end_operation ()
964
                        nonfatalException ()
965
966
                username = pwd.getpwuid (os.getuid ())[0]
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
967
                keyring_attrs["user"] = str (username)
968
                self.display_auth_info_dialog (job, keyring_attrs)
969
        self.update_sensitivity ()
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
970
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
971
    def display_auth_info_dialog (self, job, keyring_attrs=None):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
972
        data = self.jobs[job]
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
973
        auth_info_required = data['auth-info-required']
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
974
        dialog = authconn.AuthDialog (auth_info_required=auth_info_required,
975
                                      allow_remember=USE_KEYRING)
976
        dialog.set_data ('keyring-attrs', keyring_attrs)
977
        dialog.set_data ('auth-info-required', auth_info_required)
978
        dialog.set_position (gtk.WIN_POS_CENTER)
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
979
980
        # Pre-fill 'username' field.
981
        auth_info = map (lambda x: '', auth_info_required)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
982
        username = pwd.getpwuid (os.getuid ())[0]
983
        if 'username' in auth_info_required:
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
984
            try:
985
                ind = auth_info_required.index ('username')
986
                auth_info[ind] = username
987
                dialog.set_auth_info (auth_info)
988
            except:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
989
                nonfatalException ()
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
990
991
        # Focus on the first empty field.
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
992
        index = 0
993
        for field in auth_info_required:
994
            if auth_info[index] == '':
995
                dialog.field_grab_focus (field)
996
                break
997
            index += 1
998
999
        dialog.set_prompt (_("Authentication required for "
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
1000
                             "printing document `%s' (job %d)") %
1001
                           (data.get('job-name', _("Unknown")), job))
1002
        self.auth_info_dialogs[job] = dialog
1003
        dialog.connect ('response', self.auth_info_dialog_response)
1004
        dialog.connect ('delete-event', self.auth_info_dialog_delete)
1005
        dialog.set_data ('job-id', job)
1006
        dialog.show_all ()
1007
        dialog.set_keep_above (True)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1008
        dialog.show_now ()
1009
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
1010
    def auth_info_dialog_delete (self, dialog, event):
1011
        self.auth_info_dialog_response (dialog, gtk.RESPONSE_CANCEL)
1012
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1013
    def auth_info_dialog_response (self, dialog, response):
1014
        jobid = dialog.get_data ('job-id')
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
1015
        del self.auth_info_dialogs[jobid]
1016
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1017
        if response != gtk.RESPONSE_OK:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1018
            dialog.destroy ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1019
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1020
1021
        auth_info = dialog.get_auth_info ()
1022
        try:
1023
            c = authconn.Connection (self.JobsWindow,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1024
                                     host=self.host,
1025
                                     port=self.port,
1026
                                     encryption=self.encryption)
1027
        except RuntimeError:
1028
            debugprint ("Error connecting to CUPS for authentication")
1029
            return
1030
1031
        remember = False
1032
        c._begin_operation (_("authenticating job"))
1033
        try:
1034
            c.authenticateJob (jobid, auth_info)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1035
            remember = dialog.get_remember_password ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1036
            self.authenticated_jobs.add (jobid)
1037
            self.monitor.update ()
1038
        except cups.IPPError, (e, m):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1039
            self.show_IPP_Error (e, m)
1040
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1041
        c._end_operation ()
1042
1043
        if remember:
1044
            try:
1045
                keyring = gnomekeyring.get_default_keyring_sync ()
1046
                type = gnomekeyring.ITEM_NETWORK_PASSWORD
1047
                attrs = dialog.get_data ("keyring-attrs")
1048
                auth_info_required = dialog.get_data ('auth-info-required')
1049
                if attrs != None and auth_info_required != None:
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
1050
                    try:
1051
                        ind = auth_info_required.index ('username')
1052
                        attrs['user'] = auth_info[ind]
1053
                    except IndexError:
1054
                        pass
1055
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1056
                    name = "%s@%s (%s)" % (attrs.get ("user"),
1057
                                           attrs.get ("server"),
1058
                                           attrs.get ("protocol"))
1059
                    ind = auth_info_required.index ('password')
1060
                    secret = auth_info[ind]
1061
                    id = gnomekeyring.item_create_sync (keyring, type, name,
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1062
                                                        attrs, secret, True)
1063
                    debugprint ("keyring: created id %d for %s" % (id, name))
1064
            except:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1065
                nonfatalException ()
1066
1067
        dialog.destroy ()
1068
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1069
    def set_statusicon_visibility (self):
1070
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1071
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1072
1073
        if self.suppress_icon_hide:
1074
            # Avoid hiding the icon if we've been woken up to notify
1075
            # about a new printer.
1076
            self.suppress_icon_hide = False
1077
            return
1078
1079
        open_notifications = len (self.new_printer_notifications.keys ())
1080
        open_notifications += len (self.completed_job_notifications.keys ())
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1081
        for reason, notification in self.state_reason_notifications.iteritems():
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1082
            if notification.get_data ('closed') != True:
1083
                open_notifications += 1
1084
        num_jobs = len (self.active_jobs)
1.1.43 by Till Kamppeter
Import upstream version 1.1.3+git20090217
1085
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1086
        debugprint ("open notifications: %d" % open_notifications)
1087
        debugprint ("num_jobs: %d" % num_jobs)
1088
        debugprint ("num_jobs_when_hidden: %d" % self.num_jobs_when_hidden)
1089
1090
        if self.notify_has_persistence:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1091
            return
1092
1093
        self.statusicon.set_visible (open_notifications > 0 or
1094
                                     num_jobs > self.num_jobs_when_hidden)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1095
1096
        # Let the icon show/hide itself before continuing.
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1097
        while self.process_pending_events and gtk.events_pending ():
1098
            gtk.main_iteration ()
1099
1100
    def on_treeview_popup_menu (self, treeview):
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1101
        event = gtk.gdk.Event (gtk.gdk.NOTHING)
1102
        self.show_treeview_popup_menu (treeview, event, 0)
1103
1104
    def on_treeview_button_release_event(self, treeview, event):
1105
        if event.button == 3:
1106
            self.show_treeview_popup_menu (treeview, event, event.button)
1107
1108
    def update_sensitivity (self, selection = None):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1109
        if (selection is None):
1110
            selection = self.treeview.get_selection () 
1111
        (model, pathlist) = selection.get_selected_rows()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1112
        cancel = self.job_ui_manager.get_action ("/cancel-job")
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1113
        delete = self.job_ui_manager.get_action ("/delete-job")
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1114
        hold = self.job_ui_manager.get_action ("/hold-job")
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1115
        release = self.job_ui_manager.get_action ("/release-job")
1116
        reprint = self.job_ui_manager.get_action ("/reprint-job")
1117
        retrieve = self.job_ui_manager.get_action ("/retrieve-job")
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1118
        authenticate = self.job_ui_manager.get_action ("/authenticate-job")
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1119
        attributes = self.job_ui_manager.get_action ("/job-attributes")
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1120
        move = self.job_ui_manager.get_action ("/move-job")
1121
        if len (pathlist) == 0:
1122
            for widget in [cancel, delete, hold, release, reprint, retrieve,
1123
                           move, authenticate, attributes]:
1124
                widget.set_sensitive (False)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1125
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1126
1127
        cancel_sensitive = True
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1128
        hold_sensitive = True
1129
        release_sensitive = True
1130
        reprint_sensitive = True
1131
        authenticate_sensitive = True
1132
        move_sensitive = False
1133
        other_printers = self.printer_uri_index.all_printer_names ()
1134
        job_printers = dict()
1135
1136
        self.jobids = []
1137
        for path in pathlist:
1138
            iter = self.store.get_iter (path)
1139
            jobid = self.store.get_value (iter, 0)
1140
            self.jobids.append(jobid)
1141
            job = self.jobs[jobid]
1142
1143
            if job.has_key ('job-state'):
1144
                s = job['job-state']
1145
                if s >= cups.IPP_JOB_CANCELED:
1146
                    cancel_sensitive = False
1147
                if s != cups.IPP_JOB_PENDING:
1148
                    hold_sensitive = False
1149
                if s != cups.IPP_JOB_HELD:
1150
                    release_sensitive = False
1151
                if (not job.get('job-preserved', False)):
1152
                    reprint_sensitive = False
1153
1154
            if (job.get ('job-state',
1155
                         cups.IPP_JOB_CANCELED) != cups.IPP_JOB_HELD or
1156
                job.get ('job-hold-until', 'none') != 'auth-info-required'):
1157
                authenticate_sensitive = False
1158
1159
            uri = job.get ('job-printer-uri', None)
1160
            if uri:
1161
                try:
1.1.54 by Till Kamppeter
Import upstream version 1.2.3+20100723
1162
                    printer = self.printer_uri_index.lookup (uri)
1163
                except KeyError:
1164
                    printer = uri
1165
                job_printers[printer] = uri
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1166
1167
        if len (job_printers.keys ()) == 1:
1168
            try:
1169
                other_printers.remove (job_printers.keys ()[0])
1170
            except KeyError:
1171
                pass
1172
1173
        if len (other_printers) > 0:
1174
            printers_menu = gtk.Menu ()
1175
            other_printers = list (other_printers)
1176
            other_printers.sort ()
1177
            for printer in other_printers:
1178
                try:
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
1179
                    uri = self.printer_uri_index.lookup_cached_by_name (printer)
1180
                except KeyError:
1181
                    uri = None
1182
                menuitem = gtk.MenuItem (printer, False)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1183
                menuitem.set_sensitive (uri != None)
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
1184
                menuitem.show ()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1185
                menuitem.connect ('activate', self.on_job_move_activate, uri)
1186
                printers_menu.append (menuitem)
1187
1188
            self.move_job_menuitem.set_submenu (printers_menu)
1189
            move_sensitive = True
1190
1191
        cancel.set_sensitive(cancel_sensitive)
1192
        delete.set_sensitive(not cancel_sensitive)
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
1193
        hold.set_sensitive(hold_sensitive)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1194
        release.set_sensitive(release_sensitive)
1195
        reprint.set_sensitive(reprint_sensitive)
1196
        retrieve.set_sensitive(reprint_sensitive)
1197
        move.set_sensitive (move_sensitive)
1198
        authenticate.set_sensitive(authenticate_sensitive)
1199
        attributes.set_sensitive(True)
1200
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1201
    def on_selection_changed (self, selection):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1202
        self.update_sensitivity (selection)
1203
1204
    def show_treeview_popup_menu (self, treeview, event, event_button):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1205
        # Right-clicked.
1206
        self.job_context_menu.popup (None, None, None, event_button,
1207
                                     event.get_time ())
1208
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1209
    def on_icon_popupmenu(self, icon, button, time):
1210
        self.statusicon_popupmenu.popup (None, None, None, button, time)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1211
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1212
    def on_icon_hide_activate(self, menuitem):
1213
        self.num_jobs_when_hidden = len (self.jobs.keys ())
1214
        self.set_statusicon_visibility ()
1215
1216
    def on_icon_configure_printers_activate(self, menuitem):
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1217
        if self.loop:
1218
            env = {}
1219
            for name, value in os.environ.iteritems ():
1220
                if name == "SYSTEM_CONFIG_PRINTER_UI":
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1221
                    continue
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1222
                env[name] = value
1223
            p = subprocess.Popen ([ "system-config-printer" ],
1224
                                  close_fds=True, env=env)
1225
            gobject.timeout_add_seconds (10, self.poll_subprocess, p)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1226
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1227
    def poll_subprocess(self, process):
1228
        returncode = process.poll ()
1229
        return returncode == None
1230
1231
    def on_icon_quit_activate (self, menuitem):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1232
        self.cleanup ()
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1233
        if self.loop:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1234
            self.loop.quit ()
1235
1236
    def on_job_cancel_activate(self, menuitem):
1237
        self.on_job_cancel_activate2(False)
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1238
1239
    def on_job_delete_activate(self, menuitem):
1240
        self.on_job_cancel_activate2(True)
1241
1242
    def on_job_cancel_activate2(self, purge_job):
1243
        if self.jobids:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1244
            op = CancelJobsOperation (self.JobsWindow, self.host, self.port,
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1245
                                      self.encryption, self.jobids, purge_job)
1246
            self.ops.append (op)
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1247
            op.connect ('finished', self.on_canceljobs_finished)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1248
            op.connect ('ipp-error', self.on_canceljobs_error)
1249
1250
    def on_canceljobs_finished (self, canceljobsoperation):
1251
        canceljobsoperation.destroy ()
1252
        i = self.ops.index (canceljobsoperation)
1253
        del self.ops[i]
1254
        self.update_monitor ()
1255
1256
    def on_canceljobs_error (self, canceljobsoperation, jobid, exc):
1257
        self.update_monitor ()
1258
        if type (exc) == cups.IPPError:
1259
            (e, m) = exc.args
1260
            if (e != cups.IPP_NOT_POSSIBLE and
1261
                e != cups.IPP_NOT_FOUND):
1262
                self.show_IPP_Error (e, m)
1263
1264
            return
1265
1266
        raise exc
1267
1.1.39 by Till Kamppeter
Import upstream version 1.0.4+git20080730
1268
    def on_job_hold_activate(self, menuitem):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1269
        try:
1270
            c = authconn.Connection (self.JobsWindow,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1271
                                     host=self.host,
1272
                                     port=self.port,
1273
                                     encryption=self.encryption)
1274
        except RuntimeError:
1275
            return
1276
1277
        for jobid in self.jobids:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1278
            c._begin_operation (_("holding job"))
1279
            try:
1280
                c.setJobHoldUntil (jobid, "indefinite")
1281
            except cups.IPPError, (e, m):
1282
                if (e != cups.IPP_NOT_POSSIBLE and
1283
                    e != cups.IPP_NOT_FOUND):
1284
                    self.show_IPP_Error (e, m)
1285
                self.monitor.update ()
1286
                c._end_operation ()
1287
                return
1288
            c._end_operation ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1289
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1290
        del c
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1291
        self.monitor.update ()
1292
1.1.39 by Till Kamppeter
Import upstream version 1.0.4+git20080730
1293
    def on_job_release_activate(self, menuitem):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1294
        try:
1295
            c = authconn.Connection (self.JobsWindow,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1296
                                     host=self.host,
1297
                                     port=self.port,
1298
                                     encryption=self.encryption)
1299
        except RuntimeError:
1300
            return
1301
1302
        for jobid in self.jobids:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1303
            c._begin_operation (_("releasing job"))
1304
            try:
1305
                c.setJobHoldUntil (jobid, "no-hold")
1306
            except cups.IPPError, (e, m):
1307
                if (e != cups.IPP_NOT_POSSIBLE and
1308
                    e != cups.IPP_NOT_FOUND):
1309
                    self.show_IPP_Error (e, m)
1310
                self.monitor.update ()
1311
                c._end_operation ()
1312
                return
1313
            c._end_operation ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1314
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1315
        del c
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1316
        self.monitor.update ()
1317
1.1.39 by Till Kamppeter
Import upstream version 1.0.4+git20080730
1318
    def on_job_reprint_activate(self, menuitem):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1319
        try:
1320
            c = authconn.Connection (self.JobsWindow,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1321
                                     host=self.host,
1322
                                     port=self.port,
1323
                                     encryption=self.encryption)
1324
            for jobid in self.jobids:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1325
                c.restartJob (jobid)
1326
            del c
1327
        except cups.IPPError, (e, m):
1328
            self.show_IPP_Error (e, m)
1329
            self.monitor.update ()
1330
            return
1331
        except RuntimeError:
1332
            return
1333
1334
        self.monitor.update ()
1335
1336
    def on_job_retrieve_activate(self, menuitem):
1337
        try:
1338
            c = authconn.Connection (self.JobsWindow,
1339
                                     host=self.host,
1340
                                     port=self.port,
1341
                                     encryption=self.encryption)
1342
        except RuntimeError:
1343
            return
1344
1345
        for jobid in self.jobids:
1346
            try:
1347
                attrs=c.getJobAttributes(jobid)
1348
                printer_uri=attrs['job-printer-uri']
1349
                document_count=attrs['document-count']
1350
                for document_number in range(1, document_count+1):
1351
                    document=c.getDocument(printer_uri, jobid, document_number)
1352
                    tempfile = document.get('file')
1353
                    name = document.get('document-name')
1354
                    format = document.get('document-format', '')
1355
1356
                    # if there's no document-name retrieved
1357
                    if name == None:
1358
                        # give the default filename some meaningful name
1359
                        name = _("retrieved")+str(document_number)
1360
                        # add extension according to format
1361
                        if format == 'application/postscript':
1362
                            name = name + ".ps"
1363
                        elif format.find('application/vnd.') != -1:
1364
                            name = name + format.replace('application/vnd', '')
1365
                        elif format.find('application/') != -1:
1366
                            name = name + format.replace('application/', '.')
1367
1368
                    if tempfile != None:
1369
                        dialog = gtk.FileChooserDialog (_("Save File"),
1370
                                                        self.JobsWindow,
1371
                                                  gtk.FILE_CHOOSER_ACTION_SAVE,
1372
                                        (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
1373
                                         gtk.STOCK_SAVE, gtk.RESPONSE_OK))
1374
                        dialog.set_current_name(name)
1375
                        dialog.set_do_overwrite_confirmation(True)
1376
1377
                        response = dialog.run()
1378
                        if response == gtk.RESPONSE_OK:
1379
                            file_to_save = dialog.get_filename()
1380
                            try:
1381
                                shutil.copyfile(tempfile, file_to_save)
1382
                            except (IOError, shutil.Error):
1383
                                debugprint("Unable to save file "+file_to_save)
1384
                        elif response == gtk.RESPONSE_CANCEL:
1385
                            pass
1386
                        dialog.destroy()
1387
                        os.unlink(tempfile)
1388
                    else:
1389
                        debugprint("Unable to retrieve file from job file")
1390
                        return
1391
1392
            except cups.IPPError, (e, m):
1393
                self.show_IPP_Error (e, m)
1394
                self.monitor.update ()
1395
                return
1396
1397
        del c
1398
        self.monitor.update ()
1399
1400
    def on_job_move_activate(self, menuitem, job_printer_uri):
1401
        try:
1402
            c = authconn.Connection (self.JobsWindow,
1403
                                     host=self.host,
1404
                                     port=self.port,
1405
                                     encryption=self.encryption)
1406
            for jobid in self.jobids:
1407
                c.moveJob (job_id=jobid, job_printer_uri=job_printer_uri)
1408
            del c
1409
        except cups.IPPError, (e, m):
1410
            self.show_IPP_Error (e, m)
1411
            self.monitor.update ()
1412
            return
1413
        except RuntimeError:
1414
            return
1415
        except AttributeError:
1416
            # Requires pycups >= 1.9.47
1417
            debugprint ("Move requires pycups >= 1.9.47")
1418
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1419
1420
        self.monitor.update ()
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1421
1422
    def on_job_authenticate_activate(self, menuitem):
1423
        for jobid in self.jobids:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1424
            self.display_auth_info_dialog (jobid)
1425
1.1.39 by Till Kamppeter
Import upstream version 1.0.4+git20080730
1426
    def on_refresh_clicked(self, toolbutton):
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1427
        self.monitor.refresh ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1428
        self.update_job_creation_times ()
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1429
1430
    def on_job_attributes_activate(self, menuitem):
1431
        """ For every selected job create notebook page with attributes. """
1432
        try:
1433
            c = cups.Connection (host=self.host,
1434
                                 port=self.port,
1435
                                 encryption=self.encryption)
1436
        except RuntimeError:
1437
            return False
1438
1439
        for jobid in self.jobids:
1440
            if jobid not in self.jobs_attrs:
1441
                # add new notebook page with scrollable treeview
1442
                scrolledwindow = gtk.ScrolledWindow()
1443
                label = gtk.Label(jobid) # notebook page has label with jobid
1444
                page_index = self.notebook.append_page(scrolledwindow, label)
1445
                attr_treeview = gtk.TreeView()
1446
                scrolledwindow.add(attr_treeview)
1447
                cell = gtk.CellRendererText ()
1448
                attr_treeview.insert_column_with_attributes(0, _("Name"),
1449
                                                            cell, text=0)
1450
                cell = gtk.CellRendererText ()
1451
                attr_treeview.insert_column_with_attributes(1, _("Value"),
1452
                                                            cell, text=1)
1453
                attr_store = gtk.ListStore(gobject.TYPE_STRING,
1454
                                           gobject.TYPE_STRING)
1455
                attr_treeview.set_model(attr_store)
1456
                attr_treeview.get_selection().set_mode(gtk.SELECTION_NONE)
1457
                attr_store.set_sort_column_id (0, gtk.SORT_ASCENDING)
1458
                self.jobs_attrs[jobid] = (attr_store, page_index)
1459
                self.update_job_attributes_viewer (jobid, conn=c)
1460
1461
        self.JobsAttributesWindow.show_all ()
1462
1463
    def update_job_attributes_viewer(self, jobid, conn=None):
1464
        """ Update attributes store with new values. """
1465
        if conn != None:
1466
            c = conn
1467
        else:
1468
            try:
1469
                c = cups.Connection (host=self.host,
1470
                                     port=self.port,
1471
                                     encryption=self.encryption)
1472
            except RuntimeError:
1473
                return False
1474
1475
        if jobid in self.jobs_attrs:
1476
            (attr_store, page) = self.jobs_attrs[jobid]
1477
            try:
1478
                attrs = c.getJobAttributes(jobid)       # new attributes
1479
            except AttributeError:
1480
                return
1481
            except cups.IPPError:
1482
                # someone else may have purged the job,
1483
                # remove jobs notebook page
1484
                self.notebook.remove_page(page)
1485
                del self.jobs_attrs[jobid]
1486
                return
1487
1488
            attr_store.clear()                          # remove old attributes
1489
            for name, value in attrs.iteritems():
1490
                if name in ['job-id', 'job-printer-up-time']:
1491
                    continue
1492
                attr_store.append([name, value])
1493
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1494
    def job_is_active (self, jobdata):
1495
        state = jobdata.get ('job-state', cups.IPP_JOB_CANCELED)
1496
        if state >= cups.IPP_JOB_CANCELED:
1497
            return False
1498
1499
        return True
1500
1501
    ## Icon manipulation
1502
    def add_state_reason_emblem (self, pixbuf, printer=None):
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1503
        worst_reason = None
1504
        if printer == None and self.worst_reason != None:
1505
            # Check that it's valid.
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1506
            printer = self.worst_reason.get_printer ()
1507
            found = False
1508
            for reason in self.printer_state_reasons.get (printer, []):
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1509
                if reason == self.worst_reason:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1510
                    worst_reason = self.worst_reason
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1511
                    break
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1512
            if worst_reason == None:
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1513
                self.worst_reason = None
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1514
1515
        if printer != None:
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1516
            for reason in self.printer_state_reasons.get (printer, []):
1517
                if worst_reason == None:
1518
                    worst_reason = reason
1519
                elif reason > worst_reason:
1520
                    worst_reason = reason
1521
1522
        if worst_reason != None:
1523
            level = worst_reason.get_level ()
1524
            if level > StateReason.REPORT:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1525
                # Add an emblem to the icon.
1526
                icon = StateReason.LEVEL_ICON[level]
1527
                pixbuf = pixbuf.copy ()
1528
                try:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1529
                    theme = gtk.icon_theme_get_default ()
1530
                    emblem = theme.load_icon (icon, 22, 0)
1531
                    emblem.composite (pixbuf,
1532
                                      pixbuf.get_width () / 2,
1533
                                      pixbuf.get_height () / 2,
1534
                                      emblem.get_width () / 2,
1535
                                      emblem.get_height () / 2,
1536
                                      pixbuf.get_width () / 2,
1537
                                      pixbuf.get_height () / 2,
1538
                                      0.5, 0.5,
1539
                                      gtk.gdk.INTERP_BILINEAR, 255)
1540
                except gobject.GError:
1541
                    debugprint ("No %s icon available" % icon)
1542
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1543
        return pixbuf
1544
1545
    def get_icon_pixbuf (self, have_jobs=None):
1546
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1547
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1548
1549
        if have_jobs == None:
1550
            have_jobs = len (self.jobs.keys ()) > 0
1551
1552
        if have_jobs:
1553
            pixbuf = self.icon_jobs
1554
            for jobid, jobdata in self.jobs.iteritems ():
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1555
                jstate = jobdata.get ('job-state', cups.IPP_JOB_PENDING)
1556
                if jstate == cups.IPP_JOB_PROCESSING:
1557
                    pixbuf = self.icon_jobs_processing
1558
                    break
1559
        else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1560
            pixbuf = self.icon_no_jobs
1561
1562
        try:
1563
            pixbuf = self.add_state_reason_emblem (pixbuf)
1564
        except:
1565
            nonfatalException ()
1566
1567
        return pixbuf
1568
1569
    def set_statusicon_tooltip (self, tooltip=None):
1570
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1571
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1572
1573
        if tooltip == None:
1574
            num_jobs = len (self.jobs)
1575
            if num_jobs == 0:
1576
                tooltip = _("No documents queued")
1577
            elif num_jobs == 1:
1578
                tooltip = _("1 document queued")
1579
            else:
1580
                tooltip = _("%d documents queued") % num_jobs
1581
1582
        self.statusicon.set_tooltip (tooltip)
1583
1584
    def update_status (self, have_jobs=None):
1585
        # Found out which printer state reasons apply to our active jobs.
1586
        upset_printers = set()
1587
        for printer, reasons in self.printer_state_reasons.iteritems ():
1588
            if len (reasons) > 0:
1589
                upset_printers.add (printer)
1590
        debugprint ("Upset printers: %s" % upset_printers)
1591
1592
        my_upset_printers = set()
1593
        if len (upset_printers):
1594
            my_upset_printers = set()
1595
            for jobid in self.active_jobs:
1596
                # 'job-printer-name' is set by job_added/job_event
1597
                printer = self.jobs[jobid]['job-printer-name']
1598
                if printer in upset_printers:
1599
                    my_upset_printers.add (printer)
1600
            debugprint ("My upset printers: %s" % my_upset_printers)
1601
1602
        my_reasons = []
1603
        for printer in my_upset_printers:
1604
            my_reasons.extend (self.printer_state_reasons[printer])
1605
1606
        # Find out which is the most problematic.
1607
        self.worst_reason = None
1608
        if len (my_reasons) > 0:
1609
            worst_reason = my_reasons[0]
1610
            for reason in my_reasons:
1611
                if reason > worst_reason:
1612
                    worst_reason = reason
1613
            self.worst_reason = worst_reason
1614
            debugprint ("Worst reason: %s" % worst_reason)
1615
1616
        self.statusbar.pop (0)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1617
        if self.worst_reason != None:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1618
            (title, tooltip) = self.worst_reason.get_description ()
1619
            self.statusbar.push (0, tooltip)
1620
        else:
1621
            tooltip = None
1622
            status_message = ""
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1623
            processing = 0
1624
            pending = 0
1625
            for jobid in self.active_jobs:
1626
                try:
1627
                    job_state = self.jobs[jobid]['job-state']
1628
                except KeyError:
1629
                    continue
1630
                if job_state == cups.IPP_JOB_PROCESSING:
1631
                    processing = processing + 1
1632
                elif job_state == cups.IPP_JOB_PENDING:
1633
                    pending = pending + 1
1634
            if ((processing > 0) or (pending > 0)):
1635
                status_message = _("processing / pending:   %d / %d") % (processing, pending)
1636
                self.statusbar.push(0, status_message)
1637
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1638
        if self.applet and not self.notify_has_persistence:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1639
            pixbuf = self.get_icon_pixbuf (have_jobs=have_jobs)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1640
            self.statusicon.set_from_pixbuf (pixbuf)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1641
            self.set_statusicon_visibility ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1642
            self.set_statusicon_tooltip (tooltip=tooltip)
1643
1644
    ## Notifications
1645
    def notify_printer_state_reason_if_important (self, reason):
1646
        level = reason.get_level ()
1647
        if level < StateReason.WARNING:
1648
            # Not important enough to justify a notification.
1649
            return
1650
1651
        blacklist = [
1.1.54 by Till Kamppeter
Import upstream version 1.2.3+20100723
1652
            # Some printers report 'other-warning' for no apparent
1653
            # reason, e.g.  Canon iR 3170C, Epson AL-CX11NF.
1654
            # See bug #520815.
1655
            "other",
1656
1657
            # This seems to be some sort of 'magic' state reason that
1658
            # is for internal use only.
1659
            "com.apple.print.recoverable",
1660
            ]
1661
1662
        if reason.get_reason () in blacklist:
1663
            return
1664
1665
        self.notify_printer_state_reason (reason)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1666
1667
    def notify_printer_state_reason (self, reason):
1668
        tuple = reason.get_tuple ()
1669
        if self.state_reason_notifications.has_key (tuple):
1670
            debugprint ("Already sent notification for %s" % repr (reason))
1671
            return
1672
1673
        level = reason.get_level ()
1674
        if (level == StateReason.ERROR or
1675
            reason.get_reason () == "connecting-to-device"):
1676
            urgency = pynotify.URGENCY_NORMAL
1677
        else:
1678
            urgency = pynotify.URGENCY_LOW
1679
1680
        (title, text) = reason.get_description ()
1681
        notification = pynotify.Notification (title, text, 'printer')
1682
        reason.user_notified = True
1683
        notification.set_urgency (urgency)
1684
        if self.notify_has_actions:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1685
            notification.set_timeout (pynotify.EXPIRES_NEVER)
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
1686
        notification.connect ('closed',
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1687
                              self.on_state_reason_notification_closed)
1688
        self.state_reason_notifications[reason.get_tuple ()] = notification
1689
        self.set_statusicon_visibility ()
1690
        try:
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
1691
            notification.show ()
1692
        except gobject.GError:
1693
            nonfatalException ()
1694
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1695
    def on_state_reason_notification_closed (self, notification, reason=None):
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
1696
        debugprint ("Notification %s closed" % repr (notification))
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1697
        notification.set_data ('closed', True)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1698
        self.set_statusicon_visibility ()
1699
        return
1700
1701
    def notify_completed_job (self, jobid):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1702
        job = self.jobs.get (jobid, {})
1703
        document = job.get ('job-name', _("Unknown"))
1704
        printer_uri = job.get ('job-printer-uri')
1705
        if printer_uri != None:
1706
            # Determine if this printer is remote.  There's no need to
1707
            # show a notification if the printer is connected to this
1708
            # machine.
1709
1710
            # Find out the device URI.  We might already have
1711
            # determined this if authentication was required.
1712
            device_uri = job.get ('device-uri')
1713
1714
            if device_uri == None:
1715
                pattrs = ['device-uri']
1716
                c = authconn.Connection (self.JobsWindow,
1717
                                         host=self.host,
1718
                                         port=self.port,
1719
                                         encryption=self.encryption)
1720
                try:
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1721
                    attrs = c.getPrinterAttributes (uri=printer_uri,
1722
                                                    requested_attributes=pattrs)
1723
                except cups.IPPError:
1724
                    return
1725
1726
                device_uri = attrs.get ('device-uri')
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1727
1728
            if device_uri != None:
1729
                (scheme, rest) = urllib.splittype (device_uri)
1730
                if scheme not in ['socket', 'ipp', 'http', 'smb']:
1731
                    return
1732
1733
        printer = job.get ('job-printer-name', _("Unknown"))
1734
        notification = pynotify.Notification (_("Document printed"),
1735
                                              _("Document `%s' has been sent "
1736
                                                "to `%s' for printing.") %
1737
                                              (document, printer),
1738
                                              'printer')
1739
        notification.set_urgency (pynotify.URGENCY_LOW)
1740
        notification.connect ('closed',
1741
                              self.on_completed_job_notification_closed)
1742
        notification.set_data ('jobid', jobid)
1743
        self.completed_job_notifications[jobid] = notification
1744
        self.set_statusicon_visibility ()
1745
        try:
1746
            notification.show ()
1747
        except gobject.GError:
1748
            nonfatalException ()
1749
1750
    def on_completed_job_notification_closed (self, notification, reason=None):
1751
        jobid = notification.get_data ('jobid')
1752
        del self.completed_job_notifications[jobid]
1753
        self.set_statusicon_visibility ()
1754
1755
    ## Monitor signal handlers
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1756
    def on_refresh (self, mon):
1757
        self.store.clear ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1758
        self.jobs = {}
1.1.41 by Till Kamppeter
Import upstream version 1.0.5+git20080819
1759
        self.active_jobs = set()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1760
        self.jobiters = {}
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1761
        self.printer_uri_index = PrinterURIIndex ()
1762
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1763
    def job_added (self, mon, jobid, eventname, event, jobdata):
1764
        uri = jobdata.get ('job-printer-uri', '')
1765
        try:
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
1766
            printer = self.printer_uri_index.lookup (uri)
1767
        except KeyError:
1768
            printer = uri
1769
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1770
        if self.specific_dests and printer not in self.specific_dests:
1771
            return
1772
1773
        jobdata['job-printer-name'] = printer
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1774
1775
        # We may be showing this job already, perhaps because we are showing
1776
        # completed jobs and one was reprinted.
1777
        if not self.jobiters.has_key (jobid):
1778
            self.add_job (jobid, jobdata)
1779
1780
        if self.job_is_active (jobdata):
1.1.43 by Till Kamppeter
Import upstream version 1.1.3+git20090217
1781
            self.active_jobs.add (jobid)
1782
        elif jobid in self.active_jobs:
1783
            self.active_jobs.remove (jobid)
1784
1785
        self.update_status (have_jobs=True)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1786
        if self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1787
            if not self.job_is_active (jobdata):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1788
                return
1789
1790
            for reason in self.printer_state_reasons.get (printer, []):
1791
                if not reason.user_notified:
1792
                    self.notify_printer_state_reason_if_important (reason)
1793
1794
    def job_event (self, mon, jobid, eventname, event, jobdata):
1795
        uri = jobdata.get ('job-printer-uri', '')
1796
        try:
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
1797
            printer = self.printer_uri_index.lookup (uri)
1798
        except KeyError:
1799
            printer = uri
1800
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1801
        if self.specific_dests and printer not in self.specific_dests:
1802
            return
1803
1804
        jobdata['job-printer-name'] = printer
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1805
1806
        if self.job_is_active (jobdata):
1807
            self.active_jobs.add (jobid)
1808
        elif jobid in self.active_jobs:
1809
            self.active_jobs.remove (jobid)
1810
1811
        self.update_job (jobid, jobdata)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1812
        self.update_status ()
1.1.46 by Till Kamppeter
Import upstream version 1.1.10+git20090731
1813
1.1.55 by Till Kamppeter
Import upstream version 1.2.6+20110127
1814
        # Check that the job still exists, as update_status re-enters
1815
        # the main loop in order to paint/hide the tray icon.  Really
1816
        # that should probably be deferred to the idle handler, but
1817
        # for the moment just deal with the fact that the job might
1818
        # have gone (bug #640904).
1819
        if not self.jobs.has_key (jobid):
1820
            return
1821
1822
        jobdata = self.jobs[jobid]
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1823
1824
        # If the job has finished, let the user know.
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1825
        if self.applet and (eventname == 'job-completed' or
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1826
                            (eventname == 'job-state-changed' and
1827
                             event['job-state'] == cups.IPP_JOB_COMPLETED)):
1828
            reasons = event['job-state-reasons']
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1829
            if type (reasons) != list:
1830
                reasons = [reasons]
1831
1832
            canceled = False
1833
            for reason in reasons:
1834
                if reason.startswith ("job-canceled"):
1835
                    canceled = True
1836
                    break
1837
1838
            if not canceled:
1839
                self.notify_completed_job (jobid)
1840
1841
        # Look out for stopped jobs.
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1842
        if (self.applet and
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1843
            (eventname == 'job-stopped' or
1.1.45 by Till Kamppeter
Import upstream version 1.1.10+git20090725
1844
             (eventname == 'job-state-changed' and
1845
              event['job-state'] in [cups.IPP_JOB_STOPPED,
1.1.47 by Till Kamppeter
Import upstream version 1.1.11+git20090820
1846
                                     cups.IPP_JOB_PENDING])) and
1847
            not jobid in self.stopped_job_prompts):
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1848
            # Why has the job stopped?  It might be due to a job error
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1849
            # of some sort, or it might be that the backend requires
1850
            # authentication.  If the latter, the job will be held not
1851
            # stopped, and the job-hold-until attribute will be
1852
            # 'auth-info-required'.  This was already checked for in
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1853
            # update_job.
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1854
            may_be_problem = True
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1855
            jstate = jobdata['job-state']
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1856
            if (jstate == cups.IPP_JOB_PROCESSING or
1.1.47 by Till Kamppeter
Import upstream version 1.1.11+git20090820
1857
                (jstate == cups.IPP_JOB_HELD and
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1858
                 jobdata['job-hold-until'] == 'auth-info-required')):
1859
                # update_job already dealt with this.
1860
                may_be_problem = False
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1861
            else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1862
                # Other than that, unfortunately the only
1863
                # clue we get is the notify-text, which is not
1864
                # translated into our native language.  We'd better
1865
                # try parsing it.  In CUPS-1.3.6 the possible strings
1866
                # are:
1867
                #
1868
                # "Job stopped due to filter errors; please consult
1869
                # the error_log file for details."
1870
                #
1871
                # "Job stopped due to backend errors; please consult
1872
                # the error_log file for details."
1873
                #
1874
                # "Job held due to backend errors; please consult the
1875
                # error_log file for details."
1876
                #
1877
                # "Authentication is required for job %d."
1878
                # [This case is handled in the update_job method.]
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1879
                #
1880
                # "Job stopped due to printer being paused"
1881
                # [This should be ignored, as the job was doing just
1882
                # fine until the printer was stopped for other reasons.]
1883
                notify_text = event['notify-text']
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1884
                document = jobdata['job-name']
1885
                if notify_text.find ("backend errors") != -1:
1886
                    message = _("There was a problem sending document `%s' "
1887
                                "(job %d) to the printer.") % (document, jobid)
1888
                elif notify_text.find ("filter errors") != -1:
1889
                    message = _("There was a problem processing document `%s' "
1890
                                "(job %d).") % (document, jobid)
1891
                elif (notify_text.find ("being paused") != -1 or
1.1.47 by Till Kamppeter
Import upstream version 1.1.11+git20090820
1892
                      jstate != cups.IPP_JOB_STOPPED):
1893
                    may_be_problem = False
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1894
                else:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1895
                    # Give up and use the provided message untranslated.
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1896
                    message = _("There was a problem printing document `%s' "
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1897
                                "(job %d): `%s'.") % (document, jobid,
1898
                                                      notify_text)
1899
1900
            if may_be_problem:
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1901
                debugprint ("Problem detected")
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1902
                self.toggle_window_display (None, force_show=True)
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1903
                dialog = gtk.Dialog (_("Print Error"), self.JobsWindow, 0,
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
1904
                                     (_("_Diagnose"), gtk.RESPONSE_NO,
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1905
                                        gtk.STOCK_OK, gtk.RESPONSE_OK))
1906
                dialog.set_default_response (gtk.RESPONSE_OK)
1907
                dialog.set_border_width (6)
1908
                dialog.set_resizable (False)
1909
                dialog.set_icon_name (ICON)
1910
                hbox = gtk.HBox (False, 12)
1911
                hbox.set_border_width (6)
1912
                image = gtk.Image ()
1913
                image.set_from_stock (gtk.STOCK_DIALOG_ERROR,
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
1914
                                      gtk.ICON_SIZE_DIALOG)
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1915
                hbox.pack_start (image, False, False, 0)
1916
                vbox = gtk.VBox (False, 12)
1917
1918
                markup = ('<span weight="bold" size="larger">' +
1919
                          _("Print Error") + '</span>\n\n' +
1920
                          saxutils.escape (message))
1.1.53 by Till Kamppeter
Import upstream version 1.2.3+20100713
1921
                try:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1922
                    if event['printer-state'] == cups.IPP_PRINTER_STOPPED:
1923
                        name = event['printer-name']
1924
                        markup += ' '
1925
                        markup += (_("The printer called `%s' has "
1926
                                     "been disabled.") % name)
1927
                except KeyError:
1928
                    pass
1929
1930
                label = gtk.Label (markup)
1931
                label.set_use_markup (True)
1932
                label.set_line_wrap (True)
1933
                label.set_alignment (0, 0)
1934
                vbox.pack_start (label, False, False, 0)
1935
                hbox.pack_start (vbox, False, False, 0)
1936
                dialog.vbox.pack_start (hbox)
1937
                dialog.connect ('response',
1.1.35 by Till Kamppeter
Import upstream version 1.0.0+git20080609
1938
                                self.print_error_dialog_response, jobid)
1939
                self.stopped_job_prompts.add (jobid)
1940
                dialog.show_all ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1941
1942
    def job_removed (self, mon, jobid, eventname, event):
1943
        # If the job has finished, let the user know.
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1944
        if self.applet and (eventname == 'job-completed' or
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1945
                            (eventname == 'job-state-changed' and
1946
                             event['job-state'] == cups.IPP_JOB_COMPLETED)):
1947
            reasons = event['job-state-reasons']
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1948
            debugprint (reasons)
1949
            if type (reasons) != list:
1950
                reasons = [reasons]
1951
1952
            canceled = False
1953
            for reason in reasons:
1954
                if reason.startswith ("job-canceled"):
1955
                    canceled = True
1956
                    break
1957
1958
            if not canceled:
1959
                self.notify_completed_job (jobid)
1960
1961
        if self.jobiters.has_key (jobid):
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1962
            self.store.remove (self.jobiters[jobid])
1963
            del self.jobiters[jobid]
1964
            del self.jobs[jobid]
1965
1966
        if jobid in self.active_jobs:
1967
            self.active_jobs.remove (jobid)
1968
1969
        if self.jobs_attrs.has_key (jobid):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
1970
            del self.jobs_attrs[jobid]
1971
1972
        self.update_status ()
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1973
1974
    def state_reason_added (self, mon, reason):
1975
        (title, text) = reason.get_description ()
1976
        printer = reason.get_printer ()
1977
1978
        try:
1979
            l = self.printer_state_reasons[printer]
1980
        except KeyError:
1981
            l = []
1982
            self.printer_state_reasons[printer] = l
1983
1984
        reason.user_notified = False
1985
        l.append (reason)
1986
        self.update_status ()
1987
        self.treeview.queue_draw ()
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
1988
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1989
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
1990
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
1991
1992
        # Find out if the user has jobs queued for that printer.
1993
        for job, data in self.jobs.iteritems ():
1994
            if not self.job_is_active (data):
1995
                continue
1996
            if data['job-printer-name'] == printer:
1997
                # Yes!  Notify them of the state reason, if necessary.
1998
                self.notify_printer_state_reason_if_important (reason)
1999
                break
2000
2001
    def state_reason_removed (self, mon, reason):
2002
        printer = reason.get_printer ()
2003
        try:
2004
            reasons = self.printer_state_reasons[printer]
2005
        except KeyError:
2006
            debugprint ("Printer not found")
2007
            return
2008
2009
        try:
2010
            i = reasons.index (reason)
2011
        except IndexError:
2012
            debugprint ("Reason not found")
2013
            return
2014
2015
        del reasons[i]
2016
2017
        self.update_status ()
2018
        self.treeview.queue_draw ()
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
2019
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2020
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
2021
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2022
2023
        tuple = reason.get_tuple ()
2024
        try:
2025
            notification = self.state_reason_notifications[tuple]
2026
            if notification.get_data ('closed') != True:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
2027
                try:
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
2028
                    notification.close ()
2029
                except glib.GError:
2030
                    # Can fail if the notification wasn't even shown
2031
                    # yet (as in bug #545733).
2032
                    pass
2033
2034
            del self.state_reason_notifications[tuple]
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
2035
            self.set_statusicon_visibility ()
2036
        except KeyError:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2037
            pass
2038
2039
    def still_connecting (self, mon, reason):
2040
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
2041
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2042
2043
        self.notify_printer_state_reason (reason)
2044
2045
    def now_connected (self, mon, printer):
2046
        if not self.applet:
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
2047
            return
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2048
2049
        # Find the connecting-to-device state reason.
2050
        try:
2051
            reasons = self.printer_state_reasons[printer]
2052
            reason = None
2053
            for r in reasons:
2054
                if r.get_reason () == "connecting-to-device":
2055
                    reason = r
2056
                    break
2057
        except KeyError:
2058
            debugprint ("Couldn't find state reason (no reasons)!")
2059
2060
        if reason != None:
2061
            tuple = reason.get_tuple ()
2062
        else:
2063
            debugprint ("Couldn't find state reason in list!")
2064
            for (level,
2065
                 p,
2066
                 r) in self.state_reason_notifications.keys ():
2067
                if p == printer and r == "connecting-to-device":
2068
                    debugprint ("Found from notifications list")
2069
                    tuple = (level, p, r)
2070
                    break
2071
2072
        try:
2073
            notification = self.state_reason_notifications[tuple]
2074
        except KeyError:
2075
            debugprint ("Unexpected now_connected signal")
2076
            return
2077
2078
        if notification.get_data ('closed') != True:
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
2079
            try:
1.1.52 by Till Kamppeter
Import upstream version 1.2.0+20100408
2080
                notification.close ()
2081
            except glib.GError:
2082
                # Can fail if the notification wasn't even shown
2083
                pass
2084
            notification.set_data ('closed', True)
1.1.42 by Till Kamppeter
Import upstream version 1.1.2+git20090125
2085
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2086
    def printer_added (self, mon, printer):
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
2087
        self.printer_uri_index.add_printer (printer)
2088
2089
    def printer_event (self, mon, printer, eventname, event):
1.1.36 by Till Kamppeter
Import upstream version 1.0.2+git20080620
2090
        self.printer_uri_index.update_from_attrs (printer, event)
2091
2092
    def printer_removed (self, mon, printer):
2093
        self.printer_uri_index.remove_printer (printer)
2094
2095
    ### Cell data functions
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
2096
    def _set_job_job_number_text (self, column, cell, model, iter, *data):
2097
        cell.set_property("text", str (model.get_value (iter, 0)))
2098
2099
    def _set_job_user_text (self, column, cell, model, iter, *data):
2100
        jobid = model.get_value (iter, 0)
2101
        job = self.jobs[jobid]
2102
        cell.set_property("text", job.get ('job-originating-user-name',
2103
                                           _("Unknown")))
2104
2105
    def _set_job_document_text (self, column, cell, model, iter, *data):
2106
        jobid = model.get_value (iter, 0)
2107
        job = self.jobs[jobid]
2108
        cell.set_property("text", job.get('job-name', _("Unknown")))
2109
2110
    def _set_job_printer_text (self, column, cell, model, iter, *data):
2111
        jobid = model.get_value (iter, 0)
2112
        reasons = self.jobs[jobid].get('job-state-reasons')
1.1.50 by Till Kampeter
Import upstream version 1.1.16+git20100209
2113
        if reasons == 'printer-stopped':
2114
            reason = ' - ' + _("disabled")
2115
        else:
2116
            reason = ''
2117
        cell.set_property("text", self.jobs[jobid]['job-printer-name']+reason)
2118
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
2119
    def _set_job_size_text (self, column, cell, model, iter, *data):
2120
        jobid = model.get_value (iter, 0)
2121
        job = self.jobs[jobid]
2122
        size = _("Unknown")
2123
        if job.has_key ('job-k-octets'):
2124
            size = str (job['job-k-octets']) + 'k'
2125
        cell.set_property("text", size)
2126
2127
    def _find_job_state_text (self, job):
2128
        data = self.jobs[job]
2129
        jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
2130
        s = int (jstate)
2131
        job_requires_auth = (s == cups.IPP_JOB_HELD and
2132
                             data.get ('job-hold-until', 'none') ==
2133
                             'auth-info-required')
2134
        state = None
2135
        if job_requires_auth:
2136
            state = _("Held for authentication")
2137
        elif s == cups.IPP_JOB_HELD:
2138
            state = _("Held")
2139
            until = data.get ('job-hold-until')
2140
            if until != None:
2141
                try:
2142
                    colon1 = until.find (':')
2143
                    if colon1 != -1:
2144
                        now = time.gmtime ()
2145
                        hh = int (until[:colon1])
2146
                        colon2 = until[colon1 + 1:].find (':')
2147
                        if colon2 != -1:
2148
                            colon2 += colon1 + 1
2149
                            mm = int (until[colon1 + 1:colon2])
2150
                            ss = int (until[colon2 + 1:])
2151
                        else:
2152
                            mm = int (until[colon1 + 1:])
2153
                            ss = 0
2154
2155
                        day = now.tm_mday
2156
                        if (hh < now.tm_hour or
2157
                            (hh == now.tm_hour and
2158
                             (mm < now.tm_min or
2159
                              (mm == now.tm_min and ss < now.tm_sec)))):
2160
                            day += 1
2161
2162
                        hold = (now.tm_year, now.tm_mon, day,
2163
                                hh, mm, ss, 0, 0, -1)
2164
                        old_tz = os.environ.get("TZ")
2165
                        os.environ["TZ"] = "UTC"
2166
                        simpletime = time.mktime (hold)
2167
2168
                        if old_tz == None:
2169
                            del os.environ["TZ"]
2170
                        else:
2171
                            os.environ["TZ"] = old_tz
2172
2173
                        local = time.localtime (simpletime)
2174
                        state = _("Held until %s") % time.strftime ("%X", local)
2175
                except ValueError:
2176
                    pass
2177
            if until == "day-time":
2178
                state = _("Held until day-time")
2179
            elif until == "evening":
2180
                state = _("Held until evening")
2181
            elif until == "night":
2182
                state = _("Held until night-time")
2183
            elif until == "second-shift":
2184
                state = _("Held until second shift")
2185
            elif until == "third-shift":
2186
                state = _("Held until third shift")
2187
            elif until == "weekend":
2188
                state = _("Held until weekend")
2189
        else:
2190
            try:
2191
                state = { cups.IPP_JOB_PENDING: _("Pending"),
2192
                          cups.IPP_JOB_PROCESSING: _("Processing"),
2193
                          cups.IPP_JOB_STOPPED: _("Stopped"),
2194
                          cups.IPP_JOB_CANCELED: _("Canceled"),
2195
                          cups.IPP_JOB_ABORTED: _("Aborted"),
2196
                          cups.IPP_JOB_COMPLETED: _("Completed") }[s]
2197
            except IndexError:
2198
                pass
2199
2200
        if state == None:
2201
            state = _("Unknown")
2202
2203
        return state
2204
2205
    def _set_job_status_icon (self, column, cell, model, iter, *data):
2206
        jobid = model.get_value (iter, 0)
2207
        data = self.jobs[jobid]
2208
        jstate = data.get ('job-state', cups.IPP_JOB_PROCESSING)
2209
        s = int (jstate)
2210
        if s == cups.IPP_JOB_PROCESSING:
2211
            icon = self.icon_jobs_processing
2212
        else:
2213
            icon = self.icon_jobs
2214
2215
        if s == cups.IPP_JOB_HELD:
2216
            theme = gtk.icon_theme_get_default ()
2217
            emblem = theme.load_icon (gtk.STOCK_MEDIA_PAUSE, 22 / 2, 0)
2218
            copy = icon.copy ()
2219
            emblem.composite (copy, 0, 0,
2220
                              copy.get_width (),
2221
                              copy.get_height (),
2222
                              copy.get_width () / 2 - 1,
2223
                              copy.get_height () / 2 - 1,
2224
                              1.0, 1.0,
2225
                              gtk.gdk.INTERP_NEAREST, 255)
2226
            icon = copy
2227
        else:
2228
            # Check state reasons.
2229
            printer = data['job-printer-name']
2230
            icon = self.add_state_reason_emblem (icon, printer=printer)
2231
2232
        cell.set_property ("pixbuf", icon)
2233
2234
    def _set_job_status_text (self, column, cell, model, iter, *data):
2235
        jobid = model.get_value (iter, 0)
2236
        data = self.jobs[jobid]
2237
        try:
1.1.34 by Till Kamppeter
Import upstream version 0.9.90+svn2385
2238
            text = data['_status_text']
1.1.48 by Till Kamppeter
Import upstream version 1.1.11+git20090825
2239
        except KeyError:
2240
            text = self._find_job_state_text (jobid)
2241
            data['_status_text'] = text
2242
2243
        printer = data['job-printer-name']
2244
        reasons = self.printer_state_reasons.get (printer, [])
2245
        if len (reasons) > 0:
2246
            worst_reason = reasons[0]
2247
            for reason in reasons[1:]:
2248
                if reason > worst_reason:
2249
                    worst_reason = reason
2250
            (title, unused) = worst_reason.get_description ()
2251
            text += " - " + title
2252
2253
        cell.set_property ("text", text)
2254
1.1.58 by Till Kamppeter
Import upstream version 1.3.1
2255
gobject.type_register (JobViewer)
2256