Tizen 2.1 base
[platform/upstream/hplip.git] / ui / devmgr4.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2001-2009 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Authors: Don Welch, Pete Parks, Naga Samrat Chowdary Narla,
20 #
21
22 from __future__ import generators
23
24 # Std Lib
25 import sys
26 import time
27 import os
28 import gzip
29 import select
30 import struct
31 import threading
32 import Queue
33
34 # Local
35 from base.g import *
36 from base import device, utils, pml, maint, pkit
37 from prnt import cups
38 from base.codes import *
39 from ui_utils import load_pixmap
40 from installer.core_install import *
41
42 # Qt
43 from qt import *
44
45 # Main form
46 from devmgr4_base import DevMgr4_base
47
48 # Scrollviews
49 from scrollview import ScrollView
50 from scrollprintsettings import ScrollPrintSettingsView
51
52 # Alignment and ColorCal forms
53 from alignform import AlignForm
54 from aligntype6form1 import AlignType6Form1
55 from aligntype6form2 import AlignType6Form2
56 from paperedgealignform import PaperEdgeAlignForm
57 from colorcalform import ColorCalForm # Type 1 color cal
58 from coloradjform import ColorAdjForm  # Type 5 and 6 color adj
59 from colorcalform2 import ColorCalForm2 # Type 2 color cal
60 from colorcal4form import ColorCal4Form # Type 4 color cal
61 from align10form import Align10Form # Type 10 and 11 alignment
62 from align13form import Align13Form # Type 13 alignment
63
64 # Misc forms
65 from loadpaperform import LoadPaperForm
66 from settingsdialog import SettingsDialog
67 from aboutdlg import AboutDlg
68 from cleaningform import CleaningForm
69 from cleaningform2 import CleaningForm2
70 from waitform import WaitForm
71 from faxsettingsform import FaxSettingsForm
72 from nodevicesform import NoDevicesForm
73 from settingsdialog import SettingsDialog
74 from firmwaredialog import FirmwareDialog
75
76 # all in seconds
77 MIN_AUTO_REFRESH_RATE = 5
78 MAX_AUTO_REFRESH_RATE = 60
79 DEF_AUTO_REFRESH_RATE = 30
80
81
82 devices = {}    # { Device_URI : device.Device(), ... }
83 devices_lock = threading.RLock()
84
85 RESPONSE_START = 1
86 RESPONSE_DONE = 2
87
88 # ***********************************************************************************
89 #
90 # LISTVIEW/UTILITY UI CLASSES
91 #
92 # ***********************************************************************************
93
94 class IconViewToolTip(QToolTip):
95     def __init__(self, parent, tooltip_text):
96         QToolTip.__init__(self, parent.viewport())
97         self.parent = parent
98
99
100     def maybeTip(self, pos):
101         abs_coords = QPoint(pos.x() + self.parent.contentsX(),
102             pos.y() + self.parent.contentsY())
103
104         item = self.parent.findItem(abs_coords)
105
106         if item is not None and item.tooltip_text:
107             rel_coords = QRect()
108             rel_coords.setX(pos.x())
109             rel_coords.setY(pos.y())
110             i = item.rect()
111             rel_coords.setWidth(i.width())
112             rel_coords.setHeight(i.height())
113             self.tip(rel_coords, item.tooltip_text)
114
115
116
117 class FuncViewItem(QIconViewItem):
118     def __init__(self, parent, text, pixmap, tooltip_text, cmd):
119         QIconViewItem.__init__(self, parent, text, pixmap)
120         self.tooltip_text = tooltip_text
121         self.cmd = cmd
122
123         self.tooltip = IconViewToolTip(parent, tooltip_text)
124
125
126
127 class DeviceViewItem(QIconViewItem):
128     def __init__(self, parent, text, pixmap, device_uri, is_avail=True):
129         QIconViewItem.__init__(self, parent, text, pixmap)
130         self.device_uri = device_uri
131         self.is_avail = is_avail
132
133
134
135 class SuppliesListViewItem(QListViewItem):
136     def __init__(self, parent, pixmap, desc, part_no, level_pixmap, status):
137         QListViewItem.__init__(self, parent, '', desc, part_no, '', status)
138         if pixmap is not None:
139             self.setPixmap(0, pixmap)
140         if level_pixmap is not None:
141             self.setPixmap(3, level_pixmap)
142
143     def paintCell(self, p, cg, c, w, a):
144         color = QColorGroup(cg)
145         pos = self.listView().itemPos(self)
146         h = self.totalHeight()
147
148         if (pos/h) % 2:
149             color.setColor(QColorGroup.Base,  QColor(220, 228, 249))
150
151         QListViewItem.paintCell(self, p, color, c, w, a)
152
153
154
155 class PasswordDialog(QDialog):
156     def __init__(self,prompt, parent=None, name=None, modal=0, fl=0):
157         QDialog.__init__(self,parent,name,modal,fl)
158         self.prompt = prompt
159
160         if not name:
161             self.setName("PasswordDialog")
162
163         passwordDlg_baseLayout = QGridLayout(self,1,1,11,6,"passwordDlg_baseLayout")
164
165         self.promptTextLabel = QLabel(self,"promptTextLabel")
166         passwordDlg_baseLayout.addMultiCellWidget(self.promptTextLabel,0,0,0,1)
167
168         self.usernameTextLabel = QLabel(self,"usernameTextLabel")
169         passwordDlg_baseLayout.addMultiCellWidget(self.usernameTextLabel,1,1,0,1)
170
171         self.usernameLineEdit = QLineEdit(self,"usernameLineEdit")
172         self.usernameLineEdit.setEchoMode(QLineEdit.Normal)
173         passwordDlg_baseLayout.addMultiCellWidget(self.usernameLineEdit,1,1,1,2)
174
175         self.passwordTextLabel = QLabel(self,"passwordTextLabel")
176         passwordDlg_baseLayout.addMultiCellWidget(self.passwordTextLabel,2,2,0,1)
177
178         self.passwordLineEdit = QLineEdit(self,"passwordLineEdit")
179         self.passwordLineEdit.setEchoMode(QLineEdit.Password)
180         passwordDlg_baseLayout.addMultiCellWidget(self.passwordLineEdit,2,2,1,2)
181
182         self.okPushButton = QPushButton(self,"okPushButton")
183         passwordDlg_baseLayout.addWidget(self.okPushButton,3,2)
184
185         self.languageChange()
186
187         self.resize(QSize(420,163).expandedTo(self.minimumSizeHint()))
188         self.clearWState(Qt.WState_Polished)
189
190         self.connect(self.okPushButton,SIGNAL("clicked()"),self.accept)
191         self.connect(self.passwordLineEdit,SIGNAL("returnPressed()"),self.accept)
192     def getUsername(self):
193         return unicode(self.usernameLineEdit.text())
194
195     def getPassword(self):
196         return unicode(self.passwordLineEdit.text())
197
198     def languageChange(self):
199         self.setCaption(self.__tr("HP Device Manager - Enter Username/Password"))
200         self.promptTextLabel.setText(self.__tr(self.prompt))
201         self.usernameTextLabel.setText(self.__tr("Username"))
202         self.passwordTextLabel.setText(self.__tr("Password"))
203         self.okPushButton.setText(self.__tr("OK"))
204
205     def __tr(self,s,c = None):
206         return qApp.translate("PasswordDialog",s,c)
207
208
209
210 class ScrollDialog(QDialog):
211     def __init__(self, scrollview_cls, cur_device, cur_printer, service,
212         parent = None, name=None, modal=0, fl=0):
213
214         QDialog.__init__(self,parent,name,modal,fl)
215
216         if not name:
217             self.setName("ScrollDialog")
218
219         self.setSizeGripEnabled(1)
220         ScrollDialogLayout = QGridLayout(self,1,1,11,6,"ScrollDialogLayout")
221         Layout1 = QHBoxLayout(None,0,6,"Layout1")
222         Horizontal_Spacing2 = QSpacerItem(20,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
223         Layout1.addItem(Horizontal_Spacing2)
224         self.buttonOk = QPushButton(self,"buttonOk")
225         self.buttonOk.setAutoDefault(1)
226         self.buttonOk.setDefault(1)
227         Layout1.addWidget(self.buttonOk)
228         ScrollDialogLayout.addLayout(Layout1,1,0)
229
230         self.scrollview = scrollview_cls(service, self)
231         ScrollDialogLayout.addWidget(self.scrollview,0,0)
232
233         self.scrollview.onDeviceChange(cur_device)
234         self.scrollview.onPrinterChange(cur_printer)
235         self.languageChange()
236
237         self.resize(QSize(520,457).expandedTo(self.minimumSizeHint()))
238         self.clearWState(Qt.WState_Polished)
239         self.connect(self.buttonOk,SIGNAL("clicked()"),self.accept)
240
241
242     def languageChange(self):
243         self.setCaption(self.__tr("HP Device Manager"))
244         self.buttonOk.setText(self.__tr("Close"))
245         self.buttonOk.setAccel(QKeySequence(QString.null))
246
247     def __tr(self,s,c = None):
248         return qApp.translate("ScrollDialog",s,c)
249
250
251 def showPasswordUI(prompt):
252     try:
253         dlg = PasswordDialog(prompt, None)
254
255         if dlg.exec_loop() == QDialog.Accepted:
256             return (dlg.getUsername(), dlg.getPassword())
257
258     finally:
259         pass
260
261     return ("", "")
262
263
264 class StatusListViewItem(QListViewItem):
265     def __init__(self, parent, pixmap, ess, tt, event_code, job_id, username):
266         QListViewItem.__init__(self, parent, '', ess, tt, event_code, job_id, username)
267         self.setPixmap(0, pixmap)
268
269     def paintCell(self, p, cg, c, w, a):
270         color = QColorGroup(cg)
271         pos = self.listView().itemPos(self)
272         h = self.totalHeight()
273         row = pos/2
274
275         if row % 2:
276             color.setColor(QColorGroup.Base,  QColor(220, 228, 249))
277
278         QListViewItem.paintCell(self, p, color, c, w, a)
279
280
281
282 class JobListViewItem(QCheckListItem):
283     def __init__(self, parent, pixmap, desc, status, job_id):
284         QCheckListItem.__init__(self, parent, '', QCheckListItem.CheckBox)
285         self.job_id = job_id
286         self.setPixmap(1, pixmap)
287         self.setText(2, desc)
288         self.setText(3, status)
289         self.setText(4, job_id)
290
291     def paintCell(self, p, cg, c, w, a):
292         color = QColorGroup(cg)
293         pos = self.listView().itemPos(self)
294         h = self.totalHeight()
295
296         if (pos/h) % 2:
297             color.setColor(QColorGroup.Base,  QColor(220, 228, 249))
298
299         QCheckListItem.paintCell(self, p, color, c, w, a)
300
301
302
303 class JobInfoDialog(QDialog):
304     def __init__(self, text, parent=None, name=None, modal=0, fl=0):
305         QDialog.__init__(self, parent, name, modal, fl)
306
307         if not name:
308             self.setName("JobInfoDialog")
309
310         Form1Layout = QGridLayout(self,1,1,11,6,"Form1Layout")
311         spacer6 = QSpacerItem(371,20,QSizePolicy.Expanding,QSizePolicy.Minimum)
312         Form1Layout.addItem(spacer6,1,0)
313         self.pushButton4 = QPushButton(self,"pushButton4")
314         Form1Layout.addWidget(self.pushButton4,1,1)
315         self.textEdit = QTextEdit(self,"textEdit")
316         Form1Layout.addMultiCellWidget(self.textEdit,0,0,0,1)
317
318         self.languageChange()
319
320         self.resize(QSize(571,542).expandedTo(self.minimumSizeHint()))
321         self.clearWState(Qt.WState_Polished)
322
323         self.connect(self.pushButton4,SIGNAL("clicked()"),self.close)
324
325         self.textEdit.setText(text)
326
327
328     def languageChange(self):
329         self.setCaption(self.__tr("HP Device Manager - Job Log"))
330         self.pushButton4.setText(self.__tr("Close"))
331
332
333     def __tr(self,s,c = None):
334         return qApp.translate("JobInfoDialog",s,c)
335
336
337 # ***********************************************************************************
338 #
339 # DEVICE UPDATE THREAD
340 #
341 # ***********************************************************************************
342
343
344 class UpdateThread(QThread):
345     def __init__(self, response_queue=None, request_queue=None):
346         self.response_queue = response_queue # update queue -> main window
347         self.request_queue = request_queue # main window -> update queue
348
349         QThread.__init__(self)
350
351     def run(self):
352         while True:
353             dev = self.request_queue.get(True)
354
355             if dev is None:
356                 log.debug("Update thread: exit")
357                 break
358
359             log.debug("Update thread start: %s" % dev.device_uri)
360
361             try:
362                 #print "THREAD LOCK ACQUIRE"
363                 devices_lock.acquire()
364                 #print "THREAD LOCK ACQUIRE - OK"
365                 self.response_queue.put((RESPONSE_START, dev.device_uri))
366                 log.debug(log.bold("Update: %s %s %s" % ("*"*20, dev.device_uri, "*"*20)))
367
368                 if dev.supported:
369                     try:
370                         dev.open()
371                     except Error, e:
372                         log.warn(e.msg)
373
374                     time.sleep(0.1)
375
376                     if dev.device_state == DEVICE_STATE_NOT_FOUND:
377                         dev.error_state = ERROR_STATE_ERROR
378                     else:
379                         try:
380                             dev.queryDevice()
381
382                         except Error, e:
383                             log.error("Query device error (%s)." % e.msg)
384                             dev.error_state = ERROR_STATE_ERROR
385
386             finally:
387                 dev.close()
388                 #print "THREAD LOCK RELEASE"
389                 devices_lock.release()
390
391             log.debug("Device state = %d" % dev.device_state)
392             log.debug("Status code = %d" % dev.status_code)
393             log.debug("Error state = %d" % dev.error_state)
394
395             log.debug("Update thread end: %s" % dev.device_uri)
396
397             self.response_queue.put((RESPONSE_DONE, dev.device_uri))
398
399
400 # ***********************************************************************************
401 #
402 # MAINWINDOW
403 #
404 # ***********************************************************************************
405
406 class DevMgr4(DevMgr4_base):
407     def __init__(self, read_pipe=None, toolbox_version='0.0',
408                  initial_device_uri=None, disable_dbus=False,
409                  parent=None, name=None, fl = 0):
410
411
412         # Distro insformation
413         core =  CoreInstall(MODE_CHECK)
414 #        core.init()
415         self.Is_autoInstaller_distro = core.is_auto_installer_support()
416         self.Latest_ver= user_conf.get('upgrade', 'latest_available_version')
417         installed_version=sys_conf.get('hplip','version')
418         if utils.Is_HPLIP_older_version(installed_version, self.Latest_ver):
419             DevMgr4_base.__init__(self, parent, name, fl,self.Latest_ver,self.Is_autoInstaller_distro)
420         else:
421             self.Latest_ver = ""
422             DevMgr4_base.__init__(self, parent, name, fl,self.Latest_ver,self.Is_autoInstaller_distro)
423         log.debug("Initializing toolbox UI (Qt3)...")
424         log.debug("HPLIP Version: %s" % prop.installed_version)
425
426         self.disable_dbus = disable_dbus
427         self.toolbox_version = toolbox_version
428         self.cur_device_uri = user_conf.get('last_used', 'device_uri')
429         self.device_vars = {}
430         self.num_devices = 0
431         self.cur_device = None
432         self.rescanning = False
433         self.initial_device_uri = initial_device_uri
434
435         # dbus setup
436         if not self.disable_dbus:
437             self.dbus_avail, self.service, session_bus = device.init_dbus()
438
439             if not self.dbus_avail:
440                 self.FailureUI("<b>Error</b><p>hp-systray must be running to get device status. hp-systray requires dbus support. Device status will not be available.")
441             else:
442                 log.debug("dbus enabled")
443
444         else:
445             log.debug("dbus disabled")
446             self.dbus_avail, self.service = False, None
447
448
449         # Update thread setup
450         self.request_queue = Queue.Queue()
451         self.response_queue = Queue.Queue()
452         self.update_thread = UpdateThread(self.response_queue, self.request_queue)
453         self.update_thread.start()
454
455         # Pipe from toolbox/dbus setup
456         self.fmt = "80s80sI32sI80sf"
457         self.fmt_size = struct.calcsize(self.fmt)
458
459         if read_pipe is not None and not disable_dbus:
460             log.debug("Setting up read_pipe")
461             self.notifier = QSocketNotifier(read_pipe, QSocketNotifier.Read)
462             QObject.connect(self.notifier, SIGNAL("activated(int)"), self.notifier_activated)
463
464         # Application icon
465         self.setIcon(load_pixmap('hp_logo', '128x128'))
466
467         # User settings
468         self.user_settings = utils.UserSettings()
469         self.cmd_fab = self.user_settings.cmd_fab
470         log.debug("FAB command: %s" % self.cmd_fab)
471
472         if not self.user_settings.auto_refresh:
473             self.autoRefresh.toggle()
474
475         # Other initialization
476         self.InitPixmaps()
477         self.InitMisc()
478         self.InitUI()
479
480         cups.setPasswordCallback(showPasswordUI)
481
482         if not prop.doc_build:
483             self.helpContentsAction.setEnabled(False)
484
485         self.allow_auto_refresh = True
486         QTimer.singleShot(0, self.InitialUpdate)
487
488
489     # ***********************************************************************************
490     #
491     # INIT
492     #
493     # ***********************************************************************************
494
495     def InitPixmaps(self):
496         self.func_icons_cached = False
497         self.func_icons = {}
498         self.device_icons = {}
499
500         # TODO: Use Qt pixmap cache for all pixmaps?
501
502         # Device icon list overlays
503         self.warning_pix = load_pixmap('warning', '16x16')
504         self.error_pix = load_pixmap('error', '16x16')
505         self.ok_pix = load_pixmap('ok', '16x16')
506         self.lowink_pix = load_pixmap('inkdrop', '16x16')
507         self.lowtoner_pix = load_pixmap('toner', '16x16')
508         self.busy_pix = load_pixmap('busy', '16x16')
509         self.lowpaper_pix = load_pixmap('paper', '16x16')
510         self.refresh_pix = load_pixmap('refresh', '16x16')
511         self.refresh1_pix = load_pixmap('refresh1', '16x16')
512         self.fax_icon = load_pixmap('fax2', 'other')
513         self.idle_pix = load_pixmap('idle', '16x16')
514         self.scan_pix = load_pixmap("scan", '16x16')
515         self.print_pix = load_pixmap("print", '16x16')
516         self.sendfax_pix =load_pixmap("fax", '16x16')
517         self.pcard_pix = load_pixmap("pcard", '16x16')
518         self.makecopies_pix = load_pixmap("makecopies", '16x16')
519         self.help_pix = load_pixmap("help", '16x16')
520
521
522         # pixmaps: (inkjet, laserjet)
523         self.SMALL_ICONS = { ERROR_STATE_CLEAR : (None, None),
524             ERROR_STATE_BUSY : (self.busy_pix, self.busy_pix),
525             ERROR_STATE_ERROR : (self.error_pix, self.error_pix),
526             ERROR_STATE_LOW_SUPPLIES : (self.lowink_pix, self.lowtoner_pix),
527             ERROR_STATE_OK : (self.ok_pix, self.ok_pix),
528             ERROR_STATE_WARNING : (self.warning_pix, self.warning_pix),
529             ERROR_STATE_LOW_PAPER: (self.lowpaper_pix, self.lowpaper_pix),
530             ERROR_STATE_PRINTING : (self.busy_pix, self.busy_pix),
531             ERROR_STATE_SCANNING : (self.busy_pix, self.busy_pix),
532             ERROR_STATE_PHOTOCARD : (self.busy_pix, self.busy_pix),
533             ERROR_STATE_FAXING : (self.busy_pix, self.busy_pix),
534             ERROR_STATE_COPYING : (self.busy_pix, self.busy_pix),
535             ERROR_STATE_REFRESHING : (self.refresh1_pix, self.refresh1_pix),
536         }
537
538         self.STATUS_ICONS = { ERROR_STATE_CLEAR : (self.idle_pix, self.idle_pix),
539               ERROR_STATE_BUSY : (self.busy_pix, self.busy_pix),
540               ERROR_STATE_ERROR : (self.error_pix, self.error_pix),
541               ERROR_STATE_LOW_SUPPLIES : (self.lowink_pix, self.lowtoner_pix),
542               ERROR_STATE_OK : (self.ok_pix, self.ok_pix),
543               ERROR_STATE_WARNING : (self.warning_pix, self.warning_pix),
544               ERROR_STATE_LOW_PAPER: (self.lowpaper_pix, self.lowpaper_pix),
545               ERROR_STATE_PRINTING : (self.print_pix, self.print_pix),
546               ERROR_STATE_SCANNING : (self.scan_pix, self.scan_pix),
547               ERROR_STATE_PHOTOCARD : (self.pcard_pix, self.print_pix),
548               ERROR_STATE_FAXING : (self.sendfax_pix, self.sendfax_pix),
549               ERROR_STATE_COPYING :  (self.makecopies_pix, self.makecopies_pix),
550             }
551
552
553
554     def InitUI(self):
555         # Setup device icon list
556         self.DeviceList.setAutoArrange(True)
557         self.DeviceList.setSorting(True)
558
559         # Setup main menu
560         self.deviceRescanAction.setIconSet(QIconSet(self.refresh1_pix))
561         self.deviceRefreshAll.setIconSet(QIconSet(self.refresh_pix))
562         self.deviceInstallAction.setIconSet(QIconSet(load_pixmap('list_add', '16x16')))
563         self.deviceRemoveAction.setIconSet(QIconSet(load_pixmap('list_remove', '16x16')))
564         self.settingsConfigure.setIconSet(QIconSet(load_pixmap('settings', '16x16')))
565         self.helpContentsAction.setIconSet(QIconSet(self.help_pix))
566
567         # Setup toolbar
568         self.deviceRescanAction.addTo(self.Toolbar)
569         self.deviceRefreshAll.addTo(self.Toolbar)
570         self.Toolbar.addSeparator()
571         self.deviceInstallAction.addTo(self.Toolbar)
572         self.deviceRemoveAction.addTo(self.Toolbar)
573         self.Toolbar.addSeparator()
574         self.settingsConfigure.addTo(self.Toolbar)
575         self.helpContentsAction.addTo(self.Toolbar)
576
577         # Init tabs/controls
578         self.InitFuncsTab()
579         self.InitStatusTab()
580         self.InitSuppliesTab()
581         self.InitPrintSettingsTab()
582         self.InitPrintControlTab()
583
584         # Resize the splitter so that the device list starts as a single column
585         self.splitter2.setSizes([120, 700])
586
587
588
589     def InitMisc(self):
590         self.unit_names = { "year" : (self.__tr("year"), self.__tr("years")),
591             "month" : (self.__tr("month"), self.__tr("months")),
592             "week" : (self.__tr("week"), self.__tr("weeks")),
593             "day" : (self.__tr("day"), self.__tr("days")),
594             "hour" : (self.__tr("hour"), self.__tr("hours")),
595             "minute" : (self.__tr("minute"), self.__tr("minutes")),
596             "second" : (self.__tr("second"), self.__tr("seconds")),
597             }
598
599         self.num_repr = { 1 : self.__tr("one"),
600               2 : self.__tr("two"),
601               3 : self.__tr("three"),
602               4 : self.__tr("four"),
603               5 : self.__tr("five"),
604               6 : self.__tr("six"),
605               7 : self.__tr("seven"),
606               8 : self.__tr("eight"),
607               9 : self.__tr("nine"),
608               10 : self.__tr("ten"),
609               11 : self.__tr("eleven"),
610               12 : self.__tr("twelve")
611               }
612
613         if self.Latest_ver is "":
614             self.TabIndex = { self.FunctionsTab: self.UpdateFuncsTab,
615                 self.StatusTab: self.UpdateStatusTab,
616                 self.SuppliesTab: self.UpdateSuppliesTab,
617                 self.PrintSettingsTab: self.UpdatePrintSettingsTab,
618                 self.PrintJobsTab: self.UpdatePrintControlTab,
619                 }
620         else:
621             self.TabIndex = { self.FunctionsTab: self.UpdateFuncsTab,
622                 self.StatusTab: self.UpdateStatusTab,
623                 self.SuppliesTab: self.UpdateSuppliesTab,
624                 self.PrintSettingsTab: self.UpdatePrintSettingsTab,
625                 self.PrintJobsTab: self.UpdatePrintControlTab,
626                 self.UpgradeTab:self.UpdateUpgradeTab,
627                 }
628
629
630
631     def InitialUpdate(self):
632         self.RescanDevices()
633
634         cont = True
635         if self.initial_device_uri is not None:
636             if not self.ActivateDevice(self.initial_device_uri):
637                 log.error("Device %s not found" % self.initial_device_uri)
638                 cont = False
639
640         self.refresh_timer = QTimer(self, "RefreshTimer")
641         self.connect(self.refresh_timer, SIGNAL('timeout()'), self.TimedRefresh)
642
643         if MIN_AUTO_REFRESH_RATE <= self.user_settings.auto_refresh_rate <= MAX_AUTO_REFRESH_RATE:
644             self.refresh_timer.start(self.user_settings.auto_refresh_rate * 1000)
645
646         self.update_timer = QTimer(self)
647         self.connect(self.update_timer, SIGNAL("timeout()"), self.ThreadUpdate)
648         self.update_timer.start(500)
649
650
651     def ActivateDevice(self, device_uri):
652         log.debug(log.bold("Activate: %s %s %s" % ("*"*20, device_uri, "*"*20)))
653         d = self.DeviceList.firstItem()
654         found = False
655
656         while d is not None:
657             if d.device_uri == device_uri:
658                 found = True
659                 self.DeviceList.setSelected(d, True)
660                 self.DeviceList.setCurrentItem(d)
661                 break
662
663             d = d.nextItem()
664
665         return found
666
667
668
669     # ***********************************************************************************
670     #
671     # UPDATES/NOTIFICATIONS
672     #
673     # ***********************************************************************************
674
675     def notifier_activated(self, sock): # dbus message has arrived
676         m = ''
677         while True:
678             ready = select.select([sock], [], [], 0.1)
679
680             if ready[0]:
681                 m = ''.join([m, os.read(sock, self.fmt_size)])
682                 if len(m) == self.fmt_size:
683                     if self.cur_device is None or self.rescanning:
684                         return
685
686                     event = device.Event(*struct.unpack(self.fmt, m))
687                     desc = device.queryString(event.event_code)
688                     error_state = STATUS_TO_ERROR_STATE_MAP.get(event.event_code, ERROR_STATE_CLEAR)
689                     log.debug("Status event: %s (%d)" % (event.device_uri, event.event_code))
690
691                     if event.event_code > EVENT_MAX_USER_EVENT:
692
693                         if event.event_code == EVENT_HISTORY_UPDATE: # 9003
694                             log.debug("History update: %s" % event.device_uri)
695
696                             if not self.rescanning:
697                                 dev = self.findDeviceByURI(event.device_uri)
698
699                                 self.UpdateHistory(dev)
700                                 self.UpdateDevice(dev)
701
702                         elif event.event_code == EVENT_CUPS_QUEUES_CHANGED:
703                             pass
704
705                         elif event.event_code == EVENT_RAISE_DEVICE_MANAGER: # 9001
706                             log.debug("Raise requested")
707                             self.showNormal()
708                             self.setActiveWindow()
709                             self.raiseW()
710
711                     else:
712                         log.debug("Ignored")
713
714             else:
715                 break
716
717
718     def TimedRefresh(self):
719         if not self.rescanning and self.user_settings.auto_refresh and self.allow_auto_refresh:
720             log.debug("Refresh timer...")
721             self.CleanupChildren()
722
723             if self.user_settings.auto_refresh_type == 0:
724                 self.RequestDeviceUpdate()
725             else:
726                 self.RescanDevices()
727
728
729     def ThreadUpdate(self): # periodically check for updates from update thread
730         if not self.response_queue.empty():
731             response_code, device_uri = self.response_queue.get()
732
733             if response_code == RESPONSE_START:
734                 self.statusBar().message(self.__tr("Updating %1...").arg(device_uri))
735                 qApp.processEvents()
736
737             elif response_code == RESPONSE_DONE:
738                 self.statusBar().message(QString("%1 (%2)").arg(self.cur_device_uri).\
739                     arg(', '.join(self.cur_device.cups_printers)))
740
741                 dev = self.findDeviceByURI(device_uri)
742
743                 if dev is not None:
744                     self.UpdateHistory(dev)
745                     self.UpdateDevice(dev)
746
747                 qApp.processEvents()
748
749             if self.response_queue.empty() and self.request_queue.empty():
750                 self.UpdateTitle()
751                 # Disable thread timer until more items placed in request queue?
752
753
754     # ***********************************************************************************
755     #
756     # TAB/DEVICE CHANGE SLOTS
757     #
758     # ***********************************************************************************
759
760     def Tabs_currentChanged(self, tab=None):
761         """ Called when the active tab changes.
762             Update newly displayed tab.
763         """
764
765         if tab is None:
766             tab = self.Tabs.currentPage()
767
768         try:
769             self.TabIndex[tab]()
770         except AttributeError:
771             pass
772
773     def Tabs_deviceChanged(self, tab=None):
774         """ Called when the device changes.
775             Update the currently displayed tab.
776         """
777         if tab is None:
778             tab = self.Tabs.currentPage()
779
780         self.TabIndex[tab]()
781
782
783     # ***********************************************************************************
784     #
785     # DEVICE ICON LIST/DEVICE UPDATE(S)
786     #
787     # ***********************************************************************************
788
789     def DeviceList_onItem(self, a0):
790         pass
791
792
793     def deviceRescanAction_activated(self):
794         self.deviceRescanAction.setEnabled(False)
795         self.RequestDeviceUpdate()
796         self.deviceRescanAction.setEnabled(True)
797
798
799     def deviceRefreshAll_activated(self):
800         self.RescanDevices()
801
802
803     def DeviceList_clicked(self,a0):
804         pass
805
806
807     def CreatePixmap(self, dev=None):
808         if dev is None:
809             dev = self.cur_device
810
811         try:
812             dev.icon
813         except AttributeError:
814             dev.icon = "default_printer"
815
816         try:
817             self.device_icons[dev.icon]
818         except:
819             self.device_icons[dev.icon] = load_pixmap(dev.icon, 'devices')
820
821         pix = self.device_icons[dev.icon]
822
823         w, h = pix.width(), pix.height()
824         error_state = dev.error_state
825         icon = QPixmap(w, h)
826         p = QPainter(icon)
827         p.eraseRect(0, 0, icon.width(), icon.height())
828         p.drawPixmap(0, 0, pix)
829
830         try:
831             tech_type = dev.tech_type
832         except AttributeError:
833             tech_type = TECH_TYPE_NONE
834
835         if dev.device_type == DEVICE_TYPE_FAX:
836             p.drawPixmap(w - self.fax_icon.width(), 0, self.fax_icon)
837
838         if error_state != ERROR_STATE_CLEAR:
839             if tech_type in (TECH_TYPE_COLOR_INK, TECH_TYPE_MONO_INK):
840                 status_icon = self.SMALL_ICONS[error_state][0] # ink
841             else:
842                 status_icon = self.SMALL_ICONS[error_state][1] # laser
843
844             if status_icon is not None:
845                 p.drawPixmap(0, 0, status_icon)
846
847         p.end()
848
849         return icon
850
851
852     def DeviceListRefresh(self):
853         global devices
854         log.debug("Rescanning device list...")
855
856         if not self.rescanning:
857             self.setCaption(self.__tr("Refreshing Device List - HP Device Manager"))
858             self.statusBar().message(self.__tr("Refreshing device list..."))
859
860             self.rescanning = True
861             self.cups_devices = device.getSupportedCUPSDevices(['hp', 'hpfax'])
862
863             devices_lock.acquire()
864
865             try:
866                 adds = []
867                 for d in self.cups_devices:
868                     if d not in devices:
869                         adds.append(d)
870
871                 log.debug("Adds: %s" % ','.join(adds))
872
873                 removals = []
874                 for d in devices:
875                     if d not in self.cups_devices:
876                         removals.append(d)
877
878                 log.debug("Removals (1): %s" % ','.join(removals))
879
880                 updates = []
881                 for d in devices:
882                     if d not in adds and d not in removals:
883                         updates.append(d)
884
885                 log.debug("Updates: %s" % ','.join(updates))
886
887
888                 for d in adds:
889                     log.debug("adding: %s" % d)
890                     try:
891                         dev = device.Device(d, service=self.service, callback=self.callback,
892                   disable_dbus=self.disable_dbus)
893                     except Error:
894                         log.error("Unexpected error in Device class.")
895                         log.exception()
896
897                     if not dev.supported:
898                         log.debug("Unsupported model - removing device.")
899                         removals.append(d)
900                         continue
901
902                     self.CheckForDeviceSettingsUI(dev)
903                     icon = self.CreatePixmap(dev)
904
905                     if dev.device_type == DEVICE_TYPE_FAX:
906                         DeviceViewItem(self.DeviceList,  self.__tr("%1 (Fax)").arg(dev.model_ui),
907                             icon, d)
908                     else:
909                         if dev.fax_type:
910                             DeviceViewItem(self.DeviceList, self.__tr("%1 (Printer)").arg(dev.model_ui),
911                                 icon, d)
912                         else:
913                             DeviceViewItem(self.DeviceList, dev.model_ui,
914                                 icon, d)
915
916                     devices[d] = dev
917
918                 log.debug("Removals (2): %s" % ','.join(removals))
919
920                 for d in removals:
921                     item = self.DeviceList.firstItem()
922                     log.debug("removing: %s" % d)
923
924                     try:
925                         del devices[d]
926                     except KeyError:
927                         pass
928
929
930                     while item is not None:
931                         if item.device_uri == d:
932                             self.DeviceList.takeItem(item)
933                             break
934
935                         item = item.nextItem()
936
937                     qApp.processEvents()
938
939                 self.DeviceList.adjustItems()
940                 self.DeviceList.updateGeometry()
941                 qApp.processEvents()
942
943                 if len(devices):
944                     for tab in self.TabIndex:
945                         self.Tabs.setTabEnabled(tab, True)
946
947                     if self.cur_device_uri:
948                         item = first_item = self.DeviceList.firstItem()
949
950                         while item is not None:
951                             qApp.processEvents()
952                             if item.device_uri == self.cur_device_uri:
953                                 self.DeviceList.setCurrentItem(item)
954                                 self.DeviceList.setSelected(item, True)
955                                 self.statusBar().message(self.cur_device_uri)
956                                 break
957
958                             item = item.nextItem()
959
960                         else:
961                             self.cur_device = None
962                             self.cur_device_uri = ''
963
964                     if self.cur_device is None:
965                         self.cur_device_uri = self.DeviceList.firstItem().device_uri
966                         self.cur_device = devices[self.cur_device_uri]
967                         self.DeviceList.setCurrentItem(self.DeviceList.firstItem())
968
969                     self.Tabs.setTabEnabled(self.SuppliesTab, self.cur_device.device_type == DEVICE_TYPE_PRINTER and
970                         self.cur_device.error_state != ERROR_STATE_ERROR)
971
972                     self.UpdatePrinterCombos()
973
974                     user_conf.set('last_used', 'device_uri', self.cur_device_uri)
975
976                     for d in updates + adds:
977                         if d not in removals:
978                             self.RequestDeviceUpdate(devices[d])
979
980                 else:
981                     self.cur_device = None
982                     self.deviceRescanAction.setEnabled(False)
983                     self.deviceRemoveAction.setEnabled(False)
984                     self.rescanning = False
985                     self.statusBar().message(self.__tr("Press F6 to refresh."))
986
987                     for tab in self.TabIndex:
988                         self.Tabs.setTabEnabled(tab, False)
989
990                     dlg = NoDevicesForm(self, "", True)
991                     dlg.show()
992
993             finally:
994                 self.rescanning = False
995                 devices_lock.release()
996
997             self.deviceRescanAction.setEnabled(True)
998             self.deviceRemoveAction.setEnabled(True)
999
1000
1001
1002     def UpdateTitle(self):
1003         if self.cur_device.device_type == DEVICE_TYPE_FAX:
1004                 self.setCaption(self.__tr("HP Device Manager - %1 (Fax)").arg(self.cur_device.model_ui))
1005         else:
1006             if self.cur_device.fax_type:
1007                 self.setCaption(self.__tr("HP Device Manager - %1 (Printer)").arg(self.cur_device.model_ui))
1008             else:
1009                 self.setCaption(self.__tr("HP Device Manager - %1").arg(self.cur_device.model_ui))
1010
1011
1012     def UpdateDeviceByURI(self, device_uri):
1013         return self.UpdateDevice(self.findDeviceByURI(device_uri))
1014
1015
1016     def UpdateDevice(self, dev=None, update_tab=True):
1017         """ Update the device icon and currently displayed tab.
1018         """
1019         if dev is None:
1020             dev = self.cur_device
1021
1022         log.debug("UpdateDevice(%s)" % dev.device_uri)
1023
1024         item = self.findItem(dev)
1025
1026         if item is not None:
1027             item.setPixmap(self.CreatePixmap(dev))
1028
1029         if dev is self.cur_device and dev.error_state == ERROR_STATE_ERROR:
1030             self.Tabs.setCurrentPage(1)
1031
1032         if dev is self.cur_device and update_tab:
1033             self.UpdatePrinterCombos()
1034             self.TabIndex[self.Tabs.currentPage()]()
1035
1036         qApp.processEvents()
1037
1038
1039     def DeviceList_currentChanged(self, i):
1040         if i is not None: # and not self.rescanning:
1041             self.cur_device_uri = self.DeviceList.currentItem().device_uri
1042             self.cur_device = devices[self.cur_device_uri]
1043             user_conf.set('last_used', 'device_uri', self.cur_device_uri)
1044
1045             self.Tabs.setTabEnabled(self.SuppliesTab, self.cur_device.device_type == DEVICE_TYPE_PRINTER and
1046                 self.cur_device.error_state != ERROR_STATE_ERROR)
1047
1048             self.UpdateDevice()
1049             self.UpdateTitle()
1050
1051
1052     def findItem(self, dev):
1053         if dev is None:
1054             dev = self.cur_device
1055
1056         return self.findItemByURI(dev.device_uri)
1057
1058
1059     def findItemByURI(self, device_uri):
1060         item = self.DeviceList.firstItem()
1061
1062         while item is not None:
1063             if item.device_uri == device_uri:
1064                 return item
1065             item = item.nextItem()
1066
1067
1068     def findDeviceByURI(self, device_uri):
1069         try:
1070             return devices[device_uri]
1071         except:
1072             return None
1073
1074
1075     def RequestDeviceUpdate(self, dev=None, item=None):
1076         """ Submit device update request to update thread. """
1077
1078         if dev is None:
1079             dev = self.cur_device
1080
1081         if dev is not None:
1082             #log.debug("RequestDeviceUpdate(%s)" % dev.device_uri)
1083             dev.error_state = ERROR_STATE_REFRESHING
1084             self.UpdateDevice(dev, update_tab=False)
1085             qApp.processEvents()
1086
1087             self.request_queue.put(dev)
1088
1089
1090     def RescanDevices(self):
1091         #log.debug("RescanDevices()")
1092         if not self.rescanning:
1093             self.deviceRefreshAll.setEnabled(False)
1094             try:
1095                 self.DeviceListRefresh()
1096             finally:
1097                 self.deviceRefreshAll.setEnabled(True)
1098
1099
1100     def callback(self):
1101         qApp.processEvents()
1102
1103
1104     # ***********************************************************************************
1105     #
1106     # DEVICE LIST RIGHT CLICK
1107     #
1108     # ***********************************************************************************
1109
1110     def DeviceList_rightButtonClicked(self, item, pos):
1111         popup = QPopupMenu(self)
1112
1113         if item is not None and item is self.DeviceList.currentItem():
1114             if self.cur_device.error_state != ERROR_STATE_ERROR:
1115                 if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
1116                     popup.insertItem(self.__tr("Print..."), self.PrintButton_clicked)
1117
1118                     if self.cur_device.scan_type:
1119                         popup.insertItem(self.__tr("Scan..."), self.ScanButton_clicked)
1120
1121                     if self.cur_device.pcard_type:
1122                         popup.insertItem(self.__tr("Access Photo Cards..."), self.PCardButton_clicked)
1123
1124                     if self.cur_device.copy_type:
1125                         popup.insertItem(self.__tr("Make Copies..."), self.MakeCopiesButton_clicked)
1126
1127                 elif self.cur_device.device_type == DEVICE_TYPE_FAX:
1128                     if self.cur_device.fax_type:
1129                         popup.insertItem(self.__tr("Send Fax..."), self.SendFaxButton_clicked)
1130
1131                 popup.insertSeparator()
1132
1133                 if self.cur_device.device_settings_ui is not None:
1134                     popup.insertItem(self.__tr("Device Settings..."), self.deviceSettingsButton_clicked)
1135
1136             if not self.rescanning:
1137                 popup.insertItem(self.__tr("Refresh Device"), self.deviceRescanAction_activated)
1138
1139         if not self.rescanning:
1140             popup.insertItem(self.__tr("Refresh All"), self.deviceRefreshAll_activated)
1141
1142         popup.popup(pos)
1143
1144
1145     # ***********************************************************************************
1146     #
1147     # PRINTER NAME COMBOS
1148     #
1149     # ***********************************************************************************
1150
1151     def updatePrinterList(self):
1152         if self.cur_device is not None and \
1153             self.cur_device.supported:
1154
1155             printers = cups.getPrinters()
1156             self.cur_device.cups_printers = []
1157
1158             for p in printers:
1159                 if p.device_uri == self.cur_device_uri:
1160                     self.cur_device.cups_printers.append(p.name)
1161
1162
1163     def UpdatePrinterCombos(self):
1164         self.PrintSettingsPrinterCombo.clear()
1165         self.PrintJobPrinterCombo.clear()
1166
1167         if self.cur_device is not None and \
1168             self.cur_device.supported:
1169
1170             for c in self.cur_device.cups_printers:
1171                 self.PrintSettingsPrinterCombo.insertItem(c.decode("utf-8"))
1172                 self.PrintJobPrinterCombo.insertItem(c.decode("utf-8"))
1173
1174             self.cur_printer = unicode(self.PrintSettingsPrinterCombo.currentText())
1175
1176     def PrintSettingsPrinterCombo_activated(self, s):
1177         self.cur_printer = unicode(s)
1178         self.PrintJobPrinterCombo.setCurrentText(self.cur_printer.encode("latin1")) # TODO: ?
1179         return self.PrinterCombo_activated(self.cur_printer)
1180
1181     def PrintJobPrinterCombo_activated(self, s):
1182         self.cur_printer = unicode(s)
1183         self.PrintSettingsPrinterCombo.setCurrentText(self.cur_printer.encode("latin1")) # TODO: ?
1184         return self.PrinterCombo_activated(self.cur_printer)
1185
1186     def PrinterCombo_activated(self, printer):
1187         self.TabIndex[self.Tabs.currentPage()]()
1188         self.UpdatePrintSettingsTabPrinter()
1189
1190
1191
1192     # ***********************************************************************************
1193     #
1194     # FUNCTIONS/ACTION TAB
1195     #
1196     # ***********************************************************************************
1197
1198     def InitFuncsTab(self):
1199         self.click_lock = None
1200
1201     def UpdateFuncsTab(self):
1202         self.iconList.clear()
1203
1204         d = self.cur_device
1205
1206         if d is not None:
1207
1208             avail = d.device_state != DEVICE_STATE_NOT_FOUND and d.supported
1209             fax = d.fax_type and prop.fax_build and d.device_type == DEVICE_TYPE_FAX and \
1210                 sys.hexversion >= 0x020300f0 and avail
1211             printer = d.device_type == DEVICE_TYPE_PRINTER and avail
1212             req_plugin = d.plugin == PLUGIN_REQUIRED
1213             opt_plugin = d.plugin == PLUGIN_OPTIONAL
1214
1215             hplip_conf = ConfigParser.ConfigParser()
1216             fp = open("/etc/hp/hplip.conf", "r")
1217             hplip_conf.readfp(fp)
1218             fp.close()
1219
1220             try:
1221                 plugin_installed = utils.to_bool(hplip_conf.get("hplip", "plugin"))
1222             except ConfigParser.NoOptionError:
1223                 plugin_installed = False
1224
1225             if d.plugin:
1226                 if req_plugin and plugin_installed:
1227                     x = self.__tr("Download and install<br>required plugin (already installed).")
1228
1229                 elif req_plugin and not plugin_installed:
1230                     x = self.__tr("Download and install<br>required plugin (needs installation).")
1231
1232                 elif opt_plugin and plugin_installed:
1233                     x = self.__tr("Download and install<br>optional plugin (already installed).")
1234
1235                 elif opt_plugin and not plugin_installed:
1236                     x = self.__tr("Download and install<br>optional plugin (needs installation).")
1237
1238             else:
1239                 x = ''
1240
1241
1242             self.ICONS = [
1243
1244                 # PRINTER
1245
1246                 (lambda : printer,                 # filter func
1247                 self.__tr("Print"),                      # Text
1248                 "print",       # Icon
1249                 self.__tr("Print documents or files."),  # Tooltip
1250                 self.user_settings.cmd_print),           # command/action
1251
1252                 (lambda : d.scan_type and prop.scan_build and \
1253                     d.device_type == DEVICE_TYPE_PRINTER and avail,
1254                 self.__tr("Scan"),
1255                 "scan",
1256                 self.__tr("Scan a document, image, or photograph.<br>"),
1257                 self.user_settings.cmd_scan),
1258
1259                 (lambda : d.copy_type and d.device_type == DEVICE_TYPE_PRINTER and avail,
1260                 self.__tr("Make Copies"),
1261                 "makecopies",
1262                 self.__tr("Make copies on the device controlled by the PC.<br>"),
1263                 self.user_settings.cmd_copy),
1264
1265                 (lambda : d.pcard_type and d.device_type == DEVICE_TYPE_PRINTER and avail,
1266                 self.__tr("Unload Photo Card"),
1267                 "makecopies",
1268                 self.__tr("Copy images from the device's photo card to the PC."),
1269                 self.PCardButton_clicked),
1270
1271                 # FAX
1272
1273                 (lambda: fax,
1274                 self.__tr("Send Fax"),
1275                 "fax",
1276                 self.__tr("Send a fax from the PC."),
1277                 self.user_settings.cmd_fax),
1278
1279                 (lambda: fax,
1280                 self.__tr("Fax Setup"),
1281                 "fax_setup",
1282                 self.__tr("Fax support must be setup before you can send faxes."),
1283                 self.faxSettingsButton_clicked),
1284
1285                 (lambda: fax,
1286                 self.__tr("Fax Address Book"),
1287                 "fab",
1288                 self.__tr("Setup fax phone numbers to use when sending faxes from the PC."),
1289                 self.cmd_fab),
1290
1291                 # SETTINGS/TOOLS
1292
1293                 (lambda : self.cur_device.device_settings_ui is not None and avail,
1294                 self.__tr("Device Settings"),
1295                 "settings",
1296                 self.__tr("Your device has special device settings.<br>You may alter these settings here."),
1297                 self.deviceSettingsButton_clicked),
1298
1299                 (lambda : printer,
1300                 self.__tr("Print Test Page"),
1301                 "testpage",
1302                 self.__tr("Print a test page to test the setup of your printer."),
1303                 self.PrintTestPageButton_clicked),
1304
1305                 (lambda : True,
1306                 self.__tr("View Printer (Queue) Information"),
1307                 "cups",
1308                 self.__tr("View the printers (queues) installed in CUPS."),
1309                 self.viewPrinterInformation),
1310
1311                 (lambda : True,
1312                 self.__tr("View Device Information"),
1313                 "info",
1314                 self.__tr("This information is primarily useful for <br>debugging and troubleshooting (advanced)."),
1315                 self.viewInformation),
1316
1317                 (lambda: printer and d.align_type,
1318                 self.__tr("Align Cartridges (Print Heads)"),
1319                 "align",
1320                 self.__tr("This will improve the quality of output when a new cartridge is installed."),
1321                 self.AlignPensButton_clicked),
1322
1323                 (lambda: printer and d.clean_type,
1324                 self.__tr("Clean Cartridges"),
1325                 "clean",
1326                 self.__tr("You only need to perform this action if you are<br>having problems with poor printout quality due to clogged ink nozzles."),
1327                 self.CleanPensButton_clicked),
1328
1329                 (lambda: printer and d.color_cal_type and d.color_cal_type == COLOR_CAL_TYPE_TYPHOON,
1330                 self.__tr("Color Calibration"),
1331                 "colorcal",
1332                 self.__tr("Use this procedure to optimimize your printer's color output<br>(requires glossy photo paper)."),
1333                 self.ColorCalibrationButton_clicked),
1334
1335                 (lambda: printer and d.color_cal_type and d.color_cal_type != COLOR_CAL_TYPE_TYPHOON,
1336                 self.__tr("Color Calibration"),
1337                 "colorcal",
1338                 self.__tr("Use this procedure to optimimize your printer's color output."),
1339                 self.ColorCalibrationButton_clicked),
1340
1341                 (lambda: printer and d.linefeed_cal_type,
1342                 self.__tr("Line Feed Calibration"),
1343                 "linefeed_cal",
1344                 self.__tr("Use line feed calibration to optimize print quality<br>(to remove gaps in the printed output)."),
1345                 self.linefeedCalibration),
1346
1347                 (lambda: printer and d.pq_diag_type,
1348                 self.__tr("Print Diagnostic Page"),
1349                 "pq_diag",
1350                 self.__tr("Your printer can print a test page <br>to help diagnose print quality problems."),
1351                 self.pqDiag),
1352
1353                 # FIRMWARE
1354
1355                 (lambda : printer and d.fw_download,
1356                 self.__tr("Download Firmware"),
1357                 "firmware",
1358                 self.__tr("Download firmware to your printer <br>(required on some devices after each power-up)."),
1359                 self.ShowFirmwareDlg),
1360
1361                 # PLUGIN
1362
1363                 (lambda : req_plugin,
1364                 self.__tr("Install Required Plugin"),
1365                 "plugin",
1366                 x, #self.__tr("Download and install the HPLIP plugin."),
1367                 self.downloadPlugin),
1368
1369                 (lambda : opt_plugin,
1370                 self.__tr("Install Optional Plugin"),
1371                 "plugin",
1372                 x, #self.__tr("Download and install the HPLIP plugin."),
1373                 self.downloadPlugin),
1374
1375                 # HELP/WEBSITE
1376
1377                 (lambda : True,
1378                 self.__tr("Visit HPLIP Website"),
1379                 "hp_logo",
1380                 self.__tr("Visit HPLIP website."),
1381                 self.viewSupport),
1382
1383                 (lambda : True,
1384                 self.__tr("Help"),
1385                 "help",
1386                 self.__tr("View HPLIP help."),
1387                 self.viewHelp),
1388             ]
1389
1390             if not self.func_icons_cached:
1391                 for filter, text, icon, tooltip, cmd in self.ICONS:
1392                     self.func_icons[icon] = load_pixmap(icon, '32x32')
1393                 self.func_icons_cached = True
1394
1395             for filter, text, icon, tooltip, cmd in self.ICONS:
1396                 if filter is not None:
1397                     if not filter():
1398                         continue
1399
1400                 FuncViewItem(self.iconList, text,
1401                     self.func_icons[icon],
1402                     tooltip,
1403                     cmd)
1404
1405
1406     def downloadPlugin(self):
1407         ok, sudo_ok = pkit.run_plugin_command(self.cur_device.plugin == PLUGIN_REQUIRED, self.cur_device.mq['plugin-reason'])
1408         if not sudo_ok:
1409             QMessageBox.critical(self,
1410                 self.caption(),
1411                 self.__tr("<b>Unable to find an appropriate su/sudo utility to run hp-plugin.</b><p>Install kdesu, gnomesu, or gksu.</p>"),
1412                 QMessageBox.Ok,
1413                 QMessageBox.NoButton,
1414                 QMessageBox.NoButton)
1415         else:
1416             self.UpdateFuncsTab()
1417
1418
1419     def iconList_clicked(self, item):
1420         return self.RunFuncCmd(item)
1421
1422
1423     def RunFuncCmd(self, item):
1424         if item is not None and self.click_lock is not item:
1425             try:
1426                 item.cmd()
1427             except TypeError:
1428                 self.RunCommand(item.cmd)
1429
1430             self.click_lock = item
1431             QTimer.singleShot(1000, self.UnlockClick)
1432
1433
1434     def UnlockClick(self):
1435         self.click_lock = None
1436
1437
1438     def RunFuncCmdContext(self):
1439         return self.RunFuncCmd(self.iconList.currentItem())
1440
1441
1442     def iconList_contextMenuRequested(self, item, pos):
1443         if item is not None and item is self.iconList.currentItem():
1444             popup = QPopupMenu(self)
1445             popup.insertItem(self.__tr("Open..."), self.RunFuncCmdContext)
1446             popup.popup(pos)
1447
1448
1449     def iconList_returnPressed(self, item):
1450         return self.RunFuncCmd(item)
1451
1452
1453     def deviceSettingsButton_clicked(self):
1454         try:
1455             self.cur_device.open()
1456             self.cur_device.device_settings_ui(self.cur_device, self)
1457         finally:
1458             self.cur_device.close()
1459
1460
1461     def setupDevice_activated(self):
1462         try:
1463             self.cur_device.open()
1464             self.cur_device.device_settings_ui(self.cur_device, self)
1465         finally:
1466             self.cur_device.close()
1467
1468
1469     def PrintButton_clicked(self):
1470         self.RunCommand(self.user_settings.cmd_print)
1471
1472
1473     def ScanButton_clicked(self):
1474         self.RunCommand(self.user_settings.cmd_scan)
1475
1476
1477     def PCardButton_clicked(self):
1478         if self.cur_device.pcard_type == PCARD_TYPE_MLC:
1479             self.RunCommand(self.user_settings.cmd_pcard)
1480
1481         elif self.cur_device.pcard_type == PCARD_TYPE_USB_MASS_STORAGE:
1482             self.FailureUI(self.__tr("<p><b>Photocards on your printer are only available by mounting them as drives using USB mass storage.</b><p>Please refer to your distribution's documentation for setup and usage instructions."))
1483
1484
1485     def SendFaxButton_clicked(self):
1486         self.RunCommand(self.user_settings.cmd_fax)
1487
1488
1489     def MakeCopiesButton_clicked(self):
1490         self.RunCommand(self.user_settings.cmd_copy)
1491
1492
1493     def ConfigureFeaturesButton_clicked(self):
1494         self.settingsConfigure_activated(2)
1495
1496
1497     def viewInformation(self):
1498         dlg = ScrollDialog(ScrollDeviceInfoView, self.cur_device, self.cur_printer, self.service, self)
1499         dlg.exec_loop()
1500
1501
1502     def viewPrinterInformation(self):
1503         dlg = ScrollDialog(ScrollPrinterInfoView, self.cur_device, self.cur_printer, self.service, self)
1504         dlg.exec_loop()
1505
1506
1507     def viewHelp(self):
1508         f = "http://hplip.sf.net"
1509
1510         if prop.doc_build:
1511             g = os.path.join(sys_conf.get('dirs', 'doc'), 'index.html')
1512             if os.path.exists(g):
1513                 f = "file://%s" % g
1514
1515         log.debug(f)
1516         utils.openURL(f)
1517
1518
1519     def viewSupport(self):
1520         f = "http://hplip.sf.net"
1521         log.debug(f)
1522         utils.openURL(f)
1523
1524
1525     def pqDiag(self):
1526         d = self.cur_device
1527         pq_diag = d.pq_diag_type
1528
1529         try:
1530             QApplication.setOverrideCursor(QApplication.waitCursor)
1531
1532             try:
1533                 d.open()
1534             except Error:
1535                 self.CheckDeviceUI()
1536             else:
1537                 if d.isIdleAndNoError():
1538                     QApplication.restoreOverrideCursor()
1539
1540                     if pq_diag == 1:
1541                         maint.printQualityDiagType1(d, self.LoadPaperUI)
1542
1543                     elif pq_diag == 2:
1544                         maint.printQualityDiagType2(d, self.LoadPaperUI)
1545
1546                 else:
1547                     self.CheckDeviceUI()
1548
1549         finally:
1550             d.close()
1551             QApplication.restoreOverrideCursor()
1552
1553
1554     def linefeedCalibration(self):
1555         d = self.cur_device
1556         linefeed_type = d.linefeed_cal_type
1557
1558         try:
1559             QApplication.setOverrideCursor(QApplication.waitCursor)
1560
1561             try:
1562                 d.open()
1563             except Error:
1564                 self.CheckDeviceUI()
1565             else:
1566                 if d.isIdleAndNoError():
1567                     QApplication.restoreOverrideCursor()
1568
1569                     if linefeed_type == 1:
1570                         maint.linefeedCalType1(d, self.LoadPaperUI)
1571
1572                     elif linefeed_type == 2:
1573                         maint.linefeedCalType2(d, self.LoadPaperUI)
1574
1575                 else:
1576                     self.CheckDeviceUI()
1577
1578         finally:
1579             d.close()
1580             QApplication.restoreOverrideCursor()
1581
1582
1583     def downloadFirmware(self):
1584         d = self.cur_device
1585         ok = False
1586
1587         try:
1588             QApplication.setOverrideCursor(QApplication.waitCursor)
1589             d.open()
1590
1591             if d.isIdleAndNoError():
1592                 ok = d.downloadFirmware()
1593
1594         finally:
1595             d.close()
1596             QApplication.restoreOverrideCursor()
1597
1598             if not ok:
1599                 self.FailureUI(self.__tr("<b>An error occured downloading firmware file.</b><p>Please check your printer and ensure that the HPLIP plugin has been installed."))
1600
1601
1602     def CheckDeviceUI(self):
1603         self.FailureUI(self.__tr("<b>Device is busy or in an error state.</b><p>Please check device and try again."))
1604
1605
1606     def LoadPaperUI(self):
1607         if LoadPaperForm(self).exec_loop() == QDialog.Accepted:
1608             return True
1609         return False
1610
1611
1612     def AlignmentNumberUI(self, letter, hortvert, colors, line_count, choice_count):
1613         dlg = AlignForm(self, letter, hortvert, colors, line_count, choice_count)
1614         if dlg.exec_loop() == QDialog.Accepted:
1615             return True, dlg.value
1616         else:
1617             return False, 0
1618
1619
1620     def PaperEdgeUI(self, maximum):
1621         dlg = PaperEdgeAlignForm(self)
1622         if dlg.exec_loop() == QDialog.Accepted:
1623             return True, dlg.value
1624         else:
1625             return False, 0
1626
1627
1628     def BothPensRequiredUI(self):
1629         self.WarningUI(self.__tr("<p><b>Both cartridges are required for alignment.</b><p>Please install both cartridges and try again."))
1630
1631
1632     def InvalidPenUI(self):
1633         self.WarningUI(self.__tr("<p><b>One or more cartiridges are missing from the printer.</b><p>Please install cartridge(s) and try again."))
1634
1635
1636     def PhotoPenRequiredUI(self):
1637         self.WarningUI(self.__tr("<p><b>Both the photo and color cartridges must be inserted into the printer to perform color calibration.</b><p>If you are planning on printing with the photo cartridge, please insert it and try again."))
1638
1639
1640     def PhotoPenRequiredUI2(self):
1641         self.WarningUI(self.__tr("<p><b>Both the photo (regular photo or photo blue) and color cartridges must be inserted into the printer to perform color calibration.</b><p>If you are planning on printing with the photo or photo blue cartridge, please insert it and try again."))
1642
1643
1644     def NotPhotoOnlyRequired(self): # Type 11
1645         self.WarningUI(self.__tr("<p><b>Cannot align with only the photo cartridge installed.</b><p>Please install other cartridges and try again."))
1646
1647
1648     def AioUI1(self):
1649         dlg = AlignType6Form1(self)
1650         return dlg.exec_loop() == QDialog.Accepted
1651
1652
1653     def AioUI2(self):
1654         AlignType6Form2(self).exec_loop()
1655
1656
1657     def Align10and11UI(self, pattern, align_type):
1658         dlg = Align10Form(pattern, align_type, self)
1659         dlg.exec_loop()
1660         return dlg.getValues()
1661
1662
1663     def Align13UI(self):
1664         dlg = Align13Form(self)
1665         dlg.exec_loop()
1666         return True
1667
1668
1669     def AlignPensButton_clicked(self):
1670         d = self.cur_device
1671         align_type = d.align_type
1672
1673         log.debug("Align: %s %s (type=%d) %s" % ("*"*20, self.cur_device.device_uri, align_type, "*"*20))
1674
1675         try:
1676             QApplication.setOverrideCursor(QApplication.waitCursor)
1677
1678             try:
1679                 d.open()
1680             except Error:
1681                 self.CheckDeviceUI()
1682             else:
1683                 if d.isIdleAndNoError():
1684                     QApplication.restoreOverrideCursor()
1685
1686                     if align_type == ALIGN_TYPE_AUTO:
1687                         maint.AlignType1(d, self.LoadPaperUI)
1688
1689                     elif align_type == ALIGN_TYPE_8XX:
1690                         maint.AlignType2(d, self.LoadPaperUI, self.AlignmentNumberUI,
1691                self.BothPensRequiredUI)
1692
1693                     elif align_type in (ALIGN_TYPE_9XX,ALIGN_TYPE_9XX_NO_EDGE_ALIGN):
1694                          maint.AlignType3(d, self.LoadPaperUI, self.AlignmentNumberUI,
1695                 self.PaperEdgeUI, align_type)
1696
1697                     elif align_type in (ALIGN_TYPE_LIDIL_0_3_8, ALIGN_TYPE_LIDIL_0_4_3, ALIGN_TYPE_LIDIL_VIP):
1698                         maint.AlignxBow(d, align_type, self.LoadPaperUI, self.AlignmentNumberUI,
1699               self.PaperEdgeUI, self.InvalidPenUI, self.ColorAdjUI)
1700
1701                     elif align_type == ALIGN_TYPE_LIDIL_AIO:
1702                         maint.AlignType6(d, self.AioUI1, self.AioUI2, self.LoadPaperUI)
1703
1704                     elif align_type == ALIGN_TYPE_DESKJET_450:
1705                         maint.AlignType8(d, self.LoadPaperUI, self.AlignmentNumberUI)
1706
1707                     elif align_type == ALIGN_TYPE_LBOW:
1708                         maint.AlignType10(d, self.LoadPaperUI, self.Align10and11UI)
1709
1710                     elif align_type == ALIGN_TYPE_LIDIL_0_5_4:
1711                         maint.AlignType11(d, self.LoadPaperUI, self.Align10and11UI, self.NotPhotoOnlyRequired)
1712
1713                     elif align_type == ALIGN_TYPE_OJ_PRO:
1714                         maint.AlignType12(d, self.LoadPaperUI)
1715
1716                     elif align_type == ALIGN_TYPE_AIO:
1717                         maint.AlignType13(d, self.LoadPaperUI, self.Align13UI)
1718
1719                     elif align_type == ALIGN_TYPE_LEDM:
1720                         maint.AlignType15(d, self.LoadPaperUI, self.Align13UI)
1721
1722                     elif align_type == ALIGN_TYPE_LEDM_MANUAL:
1723                         maint.AlignType16(d, self.LoadPaperUI, self.AlignmentNumberUI)
1724
1725                     elif align_type == ALIGN_TYPE_LEDM_FF_CC_0:
1726                         maint.AlignType17(d, self.LoadPaperUI, self.Align13UI)
1727                 else:
1728                     self.CheckDeviceUI()
1729
1730         finally:
1731             d.close()
1732             QApplication.restoreOverrideCursor()
1733
1734
1735     def ColorAdjUI(self, line, maximum=0):
1736         dlg = ColorAdjForm(self, line)
1737         if dlg.exec_loop() == QDialog.Accepted:
1738             return True, dlg.value
1739         else:
1740             return False, 0
1741
1742
1743     def ColorCalUI(self):
1744         dlg = ColorCalForm(self)
1745         if dlg.exec_loop() == QDialog.Accepted:
1746             return True, dlg.value
1747         else:
1748             return False, 0
1749
1750
1751     def ColorCalUI2(self):
1752         dlg = ColorCalForm2(self)
1753         if dlg.exec_loop() == QDialog.Accepted:
1754             return True, dlg.value
1755         else:
1756             return False, 0
1757
1758
1759     def ColorCalUI4(self):
1760         dlg = ColorCal4Form(self)
1761         if dlg.exec_loop() == QDialog.Accepted:
1762             return True, dlg.values
1763         else:
1764             return False, None
1765
1766
1767     def ColorCalibrationButton_clicked(self):
1768         d = self.cur_device
1769         color_cal_type = d.color_cal_type
1770         log.debug("Color-cal: %s %s (type=%d) %s" % ("*"*20, self.cur_device.device_uri, color_cal_type, "*"*20))
1771
1772         if color_cal_type == COLOR_CAL_TYPE_TYPHOON:
1773             dlg = ScrollDialog(ScrollColorCalView, self.cur_device, self.cur_printer, self.service, self)
1774             dlg.exec_loop()
1775         else:
1776             try:
1777                 QApplication.setOverrideCursor(QApplication.waitCursor)
1778
1779                 try:
1780                     d.open()
1781                 except Error:
1782                     self.CheckDeviceUI()
1783                 else:
1784                     if d.isIdleAndNoError():
1785                         QApplication.restoreOverrideCursor()
1786
1787                         if color_cal_type == COLOR_CAL_TYPE_DESKJET_450:
1788                             maint.colorCalType1(d, self.LoadPaperUI, self.ColorCalUI,
1789                                 self.PhotoPenRequiredUI)
1790
1791                         elif color_cal_type == COLOR_CAL_TYPE_MALIBU_CRICK:
1792                             maint.colorCalType2(d, self.LoadPaperUI, self.ColorCalUI2,
1793                                 self.InvalidPenUI)
1794
1795                         elif color_cal_type == COLOR_CAL_TYPE_STRINGRAY_LONGBOW_TORNADO:
1796                             maint.colorCalType3(d, self.LoadPaperUI, self.ColorAdjUI,
1797                                 self.PhotoPenRequiredUI2)
1798
1799                         elif color_cal_type == COLOR_CAL_TYPE_CONNERY:
1800                             maint.colorCalType4(d, self.LoadPaperUI, self.ColorCalUI4,
1801                                 self.WaitUI)
1802
1803                         elif color_cal_type == COLOR_CAL_TYPE_COUSTEAU:
1804                             maint.colorCalType5(d, self.LoadPaperUI)
1805
1806                         elif color_cal_type == COLOR_CAL_TYPE_CARRIER:
1807                             maint.colorCalType6(d, self.LoadPaperUI)
1808
1809                     else:
1810                         self.CheckDeviceUI()
1811
1812             finally:
1813                 d.close()
1814                 QApplication.restoreOverrideCursor()
1815
1816
1817     def PrintTestPageButton_clicked(self):
1818         dlg = ScrollDialog(ScrollTestpageView, self.cur_device, self.cur_printer, self.service, self)
1819         dlg.exec_loop()
1820
1821
1822     def CleanUI1(self):
1823         return CleaningForm(self, self.cur_device, 1).exec_loop() == QDialog.Accepted
1824
1825
1826     def CleanUI2(self):
1827         return CleaningForm(self, self.cur_device, 2).exec_loop() == QDialog.Accepted
1828
1829
1830     def CleanUI3(self):
1831         CleaningForm2(self).exec_loop()
1832         return True
1833
1834
1835     def WaitUI(self, seconds):
1836         WaitForm(seconds, None, self).exec_loop()
1837
1838
1839     def CleanPensButton_clicked(self):
1840         d = self.cur_device
1841         clean_type = d.clean_type
1842         log.debug("Clean: %s %s (type=%d) %s" % ("*"*20, self.cur_device.device_uri, clean_type, "*"*20))
1843
1844         try:
1845             QApplication.setOverrideCursor(QApplication.waitCursor)
1846
1847             try:
1848                 d.open()
1849             except Error:
1850                 self.CheckDeviceUI()
1851             else:
1852                 if d.isIdleAndNoError():
1853                     QApplication.restoreOverrideCursor()
1854
1855                     if clean_type == CLEAN_TYPE_PCL:
1856                         maint.cleaning(d, clean_type, maint.cleanType1, maint.primeType1,
1857                             maint.wipeAndSpitType1, self.LoadPaperUI,
1858                             self.CleanUI1, self.CleanUI2, self.CleanUI3,
1859                             self.WaitUI)
1860
1861                     elif clean_type == CLEAN_TYPE_LIDIL:
1862                         maint.cleaning(d, clean_type, maint.cleanType2, maint.primeType2,
1863                             maint.wipeAndSpitType2, self.LoadPaperUI,
1864                             self.CleanUI1, self.CleanUI2, self.CleanUI3,
1865                             self.WaitUI)
1866
1867                     elif clean_type == CLEAN_TYPE_PCL_WITH_PRINTOUT:
1868                         maint.cleaning(d, clean_type, maint.cleanType1, maint.primeType1,
1869                             maint.wipeAndSpitType1, self.LoadPaperUI,
1870                             self.CleanUI1, self.CleanUI2, self.CleanUI3,
1871                             self.WaitUI)
1872                 else:
1873                     self.CheckDeviceUI()
1874
1875         finally:
1876             d.close()
1877             QApplication.restoreOverrideCursor()
1878
1879
1880     def OpenEmbeddedBrowserButton_clicked(self):
1881         utils.openURL("http://%s" % self.cur_device.host)
1882
1883
1884     def faxAddressBookButton_clicked(self):
1885         self.RunCommand(self.cmd_fab)
1886
1887
1888     def faxSettingsButton_clicked(self):
1889         try:
1890             try:
1891                 self.cur_device.open()
1892             except Error:
1893                 self.CheckDeviceUI()
1894             else:
1895                 try:
1896                     result_code, fax_num = self.cur_device.getPML(pml.OID_FAX_LOCAL_PHONE_NUM)
1897                 except Error:
1898                     log.error("PML failure.")
1899                     self.FailureUI(self.__tr("<p><b>Operation failed. Device busy.</b>"))
1900                     return
1901
1902                 fax_num = str(fax_num)
1903
1904                 try:
1905                     result_code, name = self.cur_device.getPML(pml.OID_FAX_STATION_NAME)
1906                 except Error:
1907                     log.error("PML failure.")
1908                     self.FailureUI(self.__tr("<p><b>Operation failed. Device busy.</b>"))
1909                     return
1910
1911                 name = str(name)
1912
1913                 dlg = FaxSettingsForm(self.cur_device, fax_num, name, self)
1914                 dlg.exec_loop()
1915
1916         finally:
1917             self.cur_device.close()
1918
1919
1920     def addressBookButton_clicked(self):
1921         self.RunCommand(self.cmd_fab)
1922
1923
1924     def ShowFirmwareDlg(self):
1925         dlg = FirmwareDialog(self, self.cur_device_uri)
1926         dlg.show()
1927         return dlg.exec_loop() == QDialog.Accepted
1928
1929     # ***********************************************************************************
1930     #
1931     # STATUS TAB
1932     #
1933     # ***********************************************************************************
1934
1935     def InitStatusTab(self):
1936         self.statusListView.setSorting(-1)
1937         self.statusListView.setColumnText(0, QString(""))
1938         #self.statusListView.setColumnWidthMode(0, QListView.Manual)
1939         self.statusListView.setColumnWidth(0, 16)
1940
1941
1942     def UpdateStatusTab(self):
1943         #log.debug("UpdateStatusTab()")
1944         self.UpdateHistory()
1945         self.UpdatePanel()
1946         self.UpdateStatusList()
1947
1948
1949     def UpdatePanel(self):
1950         if self.cur_device is not None and \
1951             self.cur_device.hist and \
1952             self.cur_device.supported:
1953
1954             dq = self.cur_device.dq
1955
1956             if dq.get('panel', 0) == 1:
1957                 line1 = dq.get('panel-line1', '')
1958                 line2 = dq.get('panel-line2', '')
1959             else:
1960                 try:
1961                     line1 = device.queryString(self.cur_device.hist[0].event_code)
1962                 except (AttributeError, TypeError):
1963                     line1 = ''
1964
1965                 line2 = ''
1966
1967             pm = load_pixmap('panel_lcd', 'other')
1968
1969             p = QPainter()
1970             p.begin(pm)
1971             p.setPen(QColor(0, 0, 0))
1972             p.setFont(self.font())
1973
1974             x, y_line1, y_line2 = 10, 17, 33
1975
1976             # TODO: Scroll long lines
1977             p.drawText(x, y_line1, line1)
1978             p.drawText(x, y_line2, line2)
1979             p.end()
1980
1981             self.panel.setPixmap(pm)
1982
1983         else:
1984             self.panel.setPixmap(load_pixmap('panel_lcd', 'other'))
1985
1986
1987     def UpdateHistory(self, dev=None):
1988         if self.dbus_avail:
1989             if dev is None:
1990                 dev = self.cur_device
1991
1992             return dev.queryHistory()
1993
1994         self.cur_device.hist = [self.cur_device.last_event]
1995
1996
1997
1998     def UpdateStatusList(self):
1999         self.statusListView.clear()
2000         row = 0
2001         hist = self.cur_device.hist[:]
2002
2003         if hist:
2004             hist.reverse()
2005             row = len(hist)-1
2006
2007             for e in hist:
2008                 if e is None:
2009                     continue
2010
2011                 ess = device.queryString(e.event_code, 0)
2012                 esl = device.queryString(e.event_code, 1)
2013
2014                 if row == 0:
2015                     desc = self.__tr("(most recent)")
2016
2017                 else:
2018                     desc = self.getTimeDeltaDesc(e.timedate)
2019
2020                 dt = QDateTime()
2021                 dt.setTime_t(int(e.timedate), Qt.LocalTime)
2022
2023                 # TODO: In Qt4.x, use QLocale.toString(date, format)
2024                 tt = QString("%1 %2").arg(dt.toString()).arg(desc)
2025
2026                 if e.job_id:
2027                     job_id = unicode(e.job_id)
2028                 else:
2029                     job_id = u''
2030
2031                 error_state = STATUS_TO_ERROR_STATE_MAP.get(e.event_code, ERROR_STATE_CLEAR)
2032                 tech_type = self.cur_device.tech_type
2033
2034                 try:
2035                     if tech_type in (TECH_TYPE_COLOR_INK, TECH_TYPE_MONO_INK):
2036                         status_pix = self.STATUS_ICONS[error_state][0] # ink
2037                     else:
2038                         status_pix = self.STATUS_ICONS[error_state][1] # laser
2039                 except KeyError:
2040                     status_pix = self.STATUS_ICONS[ERROR_STATE_CLEAR][0]
2041
2042                 StatusListViewItem(self.statusListView, status_pix, ess, tt, unicode(e.event_code),
2043                     job_id, unicode(e.username))
2044
2045                 row -= 1
2046
2047         i = self.statusListView.firstChild()
2048         if i is not None:
2049             self.statusListView.setCurrentItem(i)
2050
2051
2052     def getTimeDeltaDesc(self, past):
2053         t1 = QDateTime()
2054         t1.setTime_t(int(past))
2055         t2 = QDateTime.currentDateTime()
2056         delta = t1.secsTo(t2)
2057         return self.__tr("(about %1 ago)").arg(self.stringify(delta))
2058
2059
2060     # "Nicely readable timedelta"
2061     # Credit: Bjorn Lindqvist
2062     # ASPN Python Recipe 498062
2063     # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498062
2064     # Note: Modified from recipe
2065     def seconds_in_units(self, seconds):
2066         unit_limits = [("year", 31536000),
2067                        ("month", 2592000),
2068                        ("week", 604800),
2069                        ("day", 86400),
2070                        ("hour", 3600),
2071                        ("minute", 60)]
2072
2073         for unit_name, limit in unit_limits:
2074             if seconds >= limit:
2075                 amount = int(round(float(seconds) / limit))
2076                 return amount, unit_name
2077
2078         return seconds, "second"
2079
2080
2081     def stringify(self, seconds):
2082         amount, unit_name = self.seconds_in_units(seconds)
2083
2084         try:
2085             i18n_amount = self.num_repr[amount]
2086         except KeyError:
2087             i18n_amount = unicode(amount)
2088
2089         if amount == 1:
2090             i18n_unit = self.unit_names[unit_name][0]
2091         else:
2092             i18n_unit = self.unit_names[unit_name][1]
2093
2094         return QString("%1 %2").arg(i18n_amount).arg(i18n_unit)
2095
2096
2097
2098
2099     # ***********************************************************************************
2100     #
2101     # SUPPLIES TAB
2102     #
2103     # ***********************************************************************************
2104
2105     def InitSuppliesTab(self):
2106         self.pix_battery = load_pixmap('battery', '16x16')
2107
2108         yellow = "#ffff00"
2109         light_yellow = "#ffffcc"
2110         cyan = "#00ffff"
2111         light_cyan = "#ccffff"
2112         magenta = "#ff00ff"
2113         light_magenta = "#ffccff"
2114         black = "#000000"
2115         blue = "#0000ff"
2116         dark_grey = "#808080"
2117         light_grey = "#c0c0c0"
2118
2119         self.TYPE_TO_PIX_MAP = {
2120                                AGENT_TYPE_UNSPECIFIED : [black],
2121                                AGENT_TYPE_BLACK: [black],
2122                                AGENT_TYPE_CMY: [cyan, magenta, yellow],
2123                                AGENT_TYPE_KCM: [light_cyan, light_magenta, light_yellow],
2124                                AGENT_TYPE_GGK: [dark_grey],
2125                                AGENT_TYPE_YELLOW: [yellow],
2126                                AGENT_TYPE_MAGENTA: [magenta],
2127                                AGENT_TYPE_CYAN : [cyan],
2128                                AGENT_TYPE_CYAN_LOW: [light_cyan],
2129                                AGENT_TYPE_YELLOW_LOW: [light_yellow],
2130                                AGENT_TYPE_MAGENTA_LOW: [light_magenta],
2131                                AGENT_TYPE_BLUE: [blue],
2132                                AGENT_TYPE_KCMY_CM: [yellow, cyan, magenta],
2133                                AGENT_TYPE_LC_LM: [light_cyan, light_magenta],
2134                                #AGENT_TYPE_Y_M: [yellow, magenta],
2135                                #AGENT_TYPE_C_K: [black, cyan],
2136                                AGENT_TYPE_LG_PK: [light_grey, dark_grey],
2137                                AGENT_TYPE_LG: [light_grey],
2138                                AGENT_TYPE_G: [dark_grey],
2139                                AGENT_TYPE_PG: [light_grey],
2140                                AGENT_TYPE_C_M: [cyan, magenta],
2141                                AGENT_TYPE_K_Y: [black, yellow],
2142                                }
2143
2144         self.suppliesList.setSorting(-1)
2145         self.suppliesList.setColumnText(0, QString(""))
2146         #self.suppliesList.setColumnWidthMode(0, QListView.Manual)
2147         self.suppliesList.setColumnWidth(0, 16)
2148         self.suppliesList.setColumnWidth(3, 100)
2149
2150
2151     def UpdateSuppliesTab(self):
2152         #log.debug("UpdateSuppliesTab()")
2153
2154         self.suppliesList.clear()
2155
2156         if self.cur_device is not None and \
2157             self.cur_device.supported and \
2158             self.cur_device.status_type != STATUS_TYPE_NONE and \
2159             self.cur_device.device_state != DEVICE_STATE_NOT_FOUND:
2160
2161             try:
2162                 self.cur_device.sorted_supplies
2163             except AttributeError:
2164                 self.cur_device.sorted_supplies = []
2165
2166             if not self.cur_device.sorted_supplies:
2167                 a = 1
2168                 while True:
2169                     try:
2170                         agent_type = int(self.cur_device.dq['agent%d-type' % a])
2171                         agent_kind = int(self.cur_device.dq['agent%d-kind' % a])
2172                         agent_sku = self.cur_device.dq['agent%d-sku' % a]
2173                     except KeyError:
2174                         break
2175                     else:
2176                         self.cur_device.sorted_supplies.append((a, agent_kind, agent_type, agent_sku))
2177
2178                     a += 1
2179
2180                 self.cur_device.sorted_supplies.sort(lambda x, y: cmp(x[1], y[1]) or cmp(x[3], y[3]), reverse=True)
2181
2182
2183             for x in self.cur_device.sorted_supplies:
2184                 a, agent_kind, agent_type, agent_sku = x
2185                 agent_level = int(self.cur_device.dq['agent%d-level' % a])
2186                 agent_desc = self.cur_device.dq['agent%d-desc' % a]
2187                 agent_health_desc = self.cur_device.dq['agent%d-health-desc' % a]
2188
2189                 # Bar graph level
2190                 level_pixmap = None
2191                 if agent_kind in (AGENT_KIND_SUPPLY,
2192                                   AGENT_KIND_HEAD,
2193                                   AGENT_KIND_HEAD_AND_SUPPLY,
2194                                   AGENT_KIND_TONER_CARTRIDGE,
2195                                   AGENT_KIND_MAINT_KIT,
2196                                   AGENT_KIND_ADF_KIT,
2197                                   AGENT_KIND_INT_BATTERY,
2198                                   AGENT_KIND_DRUM_KIT,
2199                                   ):
2200
2201                     level_pixmap = self.createBarGraph(agent_level, agent_type)
2202
2203                 # Color icon
2204                 pixmap = None
2205                 if agent_kind in (AGENT_KIND_SUPPLY,
2206                                   AGENT_KIND_HEAD,
2207                                   AGENT_KIND_HEAD_AND_SUPPLY,
2208                                   AGENT_KIND_TONER_CARTRIDGE,
2209                                   #AGENT_KIND_MAINT_KIT,
2210                                   #AGENT_KIND_ADF_KIT,
2211                                   AGENT_KIND_INT_BATTERY,
2212                                   #AGENT_KIND_DRUM_KIT,
2213                                   ):
2214
2215                     pixmap = self.getIcon(agent_kind, agent_type)
2216
2217
2218                 SuppliesListViewItem(self.suppliesList, pixmap, agent_desc,
2219                     agent_sku, level_pixmap, agent_health_desc)
2220
2221             i = self.suppliesList.firstChild()
2222             if i is not None:
2223                 self.suppliesList.setCurrentItem(i)
2224
2225
2226
2227
2228     def getIcon(self, agent_kind, agent_type):
2229         if agent_kind in (AGENT_KIND_SUPPLY,
2230                           AGENT_KIND_HEAD,
2231                           AGENT_KIND_HEAD_AND_SUPPLY,
2232                           AGENT_KIND_TONER_CARTRIDGE):
2233
2234             map = self.TYPE_TO_PIX_MAP[agent_type]
2235
2236             if isinstance(map, list):
2237                 map_len = len(map)
2238                 pix = QPixmap(16, 16) #, -1, QPixmap.DefaultOptim)
2239                 pix.fill(qApp.palette().color(QPalette.Active, QColorGroup.Background))
2240                 p = QPainter()
2241                 p.begin(pix)
2242                 p.setBackgroundMode(Qt.OpaqueMode)
2243
2244                 if map_len == 1:
2245                     p.setPen(QColor(map[0]))
2246                     p.setBrush(QBrush(QColor(map[0]), Qt.SolidPattern))
2247                     p.drawPie(2, 2, 10, 10, 0, 5760)
2248
2249                 elif map_len == 2:
2250                     p.setPen(QColor(map[0]))
2251                     p.setBrush(QBrush(QColor(map[0]), Qt.SolidPattern))
2252                     p.drawPie(2, 4, 8, 8, 0, 5760)
2253
2254                     p.setPen(QColor(map[1]))
2255                     p.setBrush(QBrush(QColor(map[1]), Qt.SolidPattern))
2256                     p.drawPie(6, 4, 8, 8, 0, 5760)
2257
2258                 elif map_len == 3:
2259                     p.setPen(QColor(map[2]))
2260                     p.setBrush(QBrush(QColor(map[2]), Qt.SolidPattern))
2261                     p.drawPie(6, 6, 8, 8, 0, 5760)
2262
2263                     p.setPen(QColor(map[1]))
2264                     p.setBrush(QBrush(QColor(map[1]), Qt.SolidPattern))
2265                     p.drawPie(2, 6, 8, 8, 0, 5760)
2266
2267                     p.setPen(QColor(map[0]))
2268                     p.setBrush(QBrush(QColor(map[0]), Qt.SolidPattern))
2269                     p.drawPie(4, 2, 8, 8, 0, 5760)
2270
2271                 p.end()
2272                 return pix
2273
2274             else:
2275                 return map
2276
2277         elif agent_kind == AGENT_KIND_INT_BATTERY:
2278                 return self.pix_battery
2279
2280
2281     def createBarGraph(self, percent, agent_type, w=100, h=18):
2282         fw = w/100*percent
2283         px = QPixmap(w, h)
2284         px.fill(qApp.palette().color(QPalette.Active, QColorGroup.Background))
2285
2286         pp = QPainter(px)
2287         pp.setPen(Qt.black)
2288         pp.setBackgroundColor(qApp.palette().color(QPalette.Active, QColorGroup.Base))
2289
2290         map = self.TYPE_TO_PIX_MAP[agent_type]
2291         map_len = len(map)
2292
2293         if map_len == 1 or map_len > 3:
2294             pp.fillRect(0, 0, fw, h, QBrush(QColor(map[0])))
2295
2296         elif map_len == 2:
2297             h2 = h / 2
2298             pp.fillRect(0, 0, fw, h2, QBrush(QColor(map[0])))
2299             pp.fillRect(0, h2, fw, h, QBrush(QColor(map[1])))
2300
2301         elif map_len == 3:
2302             h3 = h / 3
2303             h23 = 2 * h3
2304             pp.fillRect(0, 0, fw, h3, QBrush(QColor(map[0])))
2305             pp.fillRect(0, h3, fw, h23, QBrush(QColor(map[1])))
2306             pp.fillRect(0, h23, fw, h, QBrush(QColor(map[2])))
2307
2308         # draw black frame
2309         pp.drawRect(0, 0, w, h)
2310
2311         if percent > 75 and agent_type in \
2312           (AGENT_TYPE_BLACK, AGENT_TYPE_UNSPECIFIED, AGENT_TYPE_BLUE):
2313             pp.setPen(Qt.white)
2314
2315         # 75% ticks
2316         w1 = 3 * w / 4
2317         h6 = h / 6
2318         pp.drawLine(w1, 0, w1, h6)
2319         pp.drawLine(w1, h, w1, h-h6)
2320
2321         if percent > 50 and agent_type in \
2322           (AGENT_TYPE_BLACK, AGENT_TYPE_UNSPECIFIED, AGENT_TYPE_BLUE):
2323             pp.setPen(Qt.white)
2324
2325         # 50% ticks
2326         w2 = w / 2
2327         h4 = h / 4
2328         pp.drawLine(w2, 0, w2, h4)
2329         pp.drawLine(w2, h, w2, h-h4)
2330
2331         if percent > 25 and agent_type in \
2332           (AGENT_TYPE_BLACK, AGENT_TYPE_UNSPECIFIED, AGENT_TYPE_BLUE):
2333             pp.setPen(Qt.white)
2334
2335         # 25% ticks
2336         w4 = w / 4
2337         pp.drawLine(w4, 0, w4, h6)
2338         pp.drawLine(w4, h, w4, h-h6)
2339
2340         return px
2341
2342
2343
2344     # ***********************************************************************************
2345     #
2346     # PRINTER SETTINGS TAB
2347     #
2348     # ***********************************************************************************
2349
2350     def InitPrintSettingsTab(self): # Add Scrolling Print Settings
2351         PrintJobsTabLayout = QGridLayout(self.PrintSettingsTab,1,1,11,6,"PrintJobsTabLayout")
2352
2353         self.PrintSettingsList = ScrollPrintSettingsView(self.service, self.PrintSettingsTab, "PrintSettingsView")
2354         PrintJobsTabLayout.addMultiCellWidget(self.PrintSettingsList,1,1,0,3)
2355
2356         self.PrintSettingsPrinterCombo = QComboBox(0,self.PrintSettingsTab,"comboBox5")
2357
2358         self.PrintSettingsPrinterCombo.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed,0,0,
2359             self.PrintSettingsPrinterCombo.sizePolicy().hasHeightForWidth()))
2360
2361         PrintJobsTabLayout.addWidget(self.PrintSettingsPrinterCombo, 0, 2)
2362
2363         self.settingTextLabel = QLabel(self.PrintSettingsTab,"self.settingTextLabel")
2364         PrintJobsTabLayout.addWidget(self.settingTextLabel,0,1)
2365
2366         self.settingTextLabel.setText(self.__tr("Printer Name:"))
2367
2368         spacer34 = QSpacerItem(20,20,QSizePolicy.Preferred, QSizePolicy.Minimum)
2369         PrintJobsTabLayout.addItem(spacer34,0,3)
2370
2371         spacer35 = QSpacerItem(20,20,QSizePolicy.Preferred, QSizePolicy.Minimum)
2372         PrintJobsTabLayout.addItem(spacer35,0,0)
2373
2374         self.connect(self.PrintSettingsPrinterCombo, SIGNAL("activated(const QString&)"),
2375             self.PrintSettingsPrinterCombo_activated)
2376
2377
2378     def UpdatePrintSettingsTab(self):
2379         #log.debug("UpdatePrintSettingsTab()")
2380         if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2381             self.settingTextLabel.setText(self.__tr("Printer Name:"))
2382         else:
2383             self.settingTextLabel.setText(self.__tr("Fax Name:"))
2384
2385         self.PrintSettingsList.onDeviceChange(self.cur_device)
2386
2387
2388     def UpdatePrintSettingsTabPrinter(self):
2389         self.PrintSettingsList.onPrinterChange(self.cur_printer)
2390
2391
2392     # ***********************************************************************************
2393     #
2394     # PRINTER CONTROL TAB
2395     #
2396     # ***********************************************************************************
2397
2398     def InitPrintControlTab(self):
2399         self.JOB_STATES = { cups.IPP_JOB_PENDING : self.__tr("Pending"),
2400                             cups.IPP_JOB_HELD : self.__tr("On hold"),
2401                             cups.IPP_JOB_PROCESSING : self.__tr("Printing"),
2402                             cups.IPP_JOB_STOPPED : self.__tr("Stopped"),
2403                             cups.IPP_JOB_CANCELLED : self.__tr("Canceled"),
2404                             cups.IPP_JOB_ABORTED : self.__tr("Aborted"),
2405                             cups.IPP_JOB_COMPLETED : self.__tr("Completed"),
2406                            }
2407
2408         self.cancelToolButton.setIconSet(QIconSet(load_pixmap('cancel', '16x16')))
2409         self.infoToolButton.setIconSet(QIconSet(load_pixmap('info', '16x16')))
2410
2411         self.JOB_STATE_ICONS = { cups.IPP_JOB_PENDING: self.busy_pix,
2412                                  cups.IPP_JOB_HELD : self.busy_pix,
2413                                  cups.IPP_JOB_PROCESSING : self.print_pix,
2414                                  cups.IPP_JOB_STOPPED : self.warning_pix,
2415                                  cups.IPP_JOB_CANCELLED : self.warning_pix,
2416                                  cups.IPP_JOB_ABORTED : self.error_pix,
2417                                  cups.IPP_JOB_COMPLETED : self.ok_pix,
2418                                 }
2419
2420         self.jobList.setSorting(-1)
2421         self.jobList.setColumnText(0, QString(""))
2422         #self.jobList.setColumnWidthMode(0, QListView.Manual)
2423         self.jobList.setColumnWidth(0, 16)
2424         self.jobList.setColumnText(1, QString(""))
2425         #self.jobList.setColumnWidthMode(1, QListView.Manual)
2426         self.jobList.setColumnWidth(1, 16)
2427         self.jobList.setColumnWidth(2, 300)
2428         self.cancelToolButton.setEnabled(False)
2429         self.infoToolButton.setEnabled(False)
2430
2431         self.printer_state = cups.IPP_PRINTER_STATE_IDLE
2432
2433         # TODO: Check queues at startup and send events if stopped or rejecting
2434
2435
2436     def UpdatePrintControlTab(self):
2437         #log.debug("UpdatePrintControlTab()")
2438
2439         if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2440             self.printerTextLabel.setText(self.__tr("Printer Name:"))
2441
2442         else:
2443             self.printerTextLabel.setText(self.__tr("Fax Name:"))
2444
2445         self.jobList.clear()
2446         self.UpdatePrintController()
2447
2448         jobs = cups.getJobs()
2449         num_jobs = 0
2450         for j in jobs:
2451             if j.dest.decode('utf-8') == unicode(self.cur_printer):
2452                 num_jobs += 1
2453
2454         for j in jobs:
2455             if j.dest == self.cur_printer:
2456                 JobListViewItem(self.jobList, self.JOB_STATE_ICONS[j.state],
2457                     j.title, self.JOB_STATES[j.state], unicode(j.id))
2458
2459         i = self.jobList.firstChild()
2460         if i is not None:
2461             self.jobList.setCurrentItem(i)
2462
2463
2464     def jobList_clicked(self, i):
2465         num = 0
2466         item = self.jobList.firstChild()
2467         while item is not None:
2468             if item.isOn():
2469                 num += 1
2470
2471             item = item.nextSibling()
2472
2473         self.cancelToolButton.setEnabled(num)
2474         self.infoToolButton.setEnabled(num == 1)
2475
2476
2477     def infoToolButton_clicked(self):
2478         item = self.jobList.firstChild()
2479         while item is not None:
2480             if item.isOn():
2481                 return self.showJobInfoDialog(item)
2482
2483             item = item.nextSibling()
2484
2485
2486     def cancelToolButton_clicked(self):
2487         self.cancelCheckedJobs()
2488
2489
2490     def jobList_contextMenuRequested(self, item, pos, a2):
2491         if item is not None and item is self.jobList.currentItem():
2492             popup = QPopupMenu(self)
2493
2494             popup.insertItem(self.__tr("Cancel Job"), self.cancelJob)
2495             popup.insertSeparator()
2496             popup.insertItem(self.__tr("View Job Log (advanced)..."), self.getJobInfo)
2497
2498             popup.popup(pos)
2499
2500
2501     def cancelJob(self):
2502         item = self.jobList.currentItem()
2503
2504         if item is not None:
2505             self.cur_device.cancelJob(int(item.job_id))
2506
2507
2508     def getJobInfo(self):
2509         return self.showJobInfoDialog(self.jobList.currentItem())
2510
2511
2512     def showJobInfoDialog(self, item):
2513         if item is not None:
2514             text = cups.getPrintJobErrorLog(int(item.job_id))
2515
2516             if text:
2517                 dlg = JobInfoDialog(text, self)
2518                 dlg.setCaption(self.__tr("HP Device Manager - Job Log - %1 - Job %2").\
2519                     arg(self.cur_printer).arg(unicode(item.job_id)))
2520
2521                 dlg.exec_loop()
2522
2523             else:
2524                 self.FailureUI(self.__tr("<b>No log output found.</b><p>If the print job is stopped or the printer is rejecting jobs, there might not be any output. Also, you will receive more output in the CUPS LogLevel is set to 'debug'."))
2525
2526
2527     def UpdatePrintController(self):
2528         # default printer
2529         self.defaultPushButton.setText(self.__tr("Set as Default"))
2530
2531         default_printer = cups.getDefaultPrinter()
2532         if default_printer is not None:
2533             default_printer = default_printer.decode('utf8')
2534
2535         if default_printer == self.cur_printer:
2536             s = self.__tr("SET AS DEFAULT")
2537             self.defaultPushButton.setEnabled(False)
2538
2539         else:
2540             s = self.__tr("NOT SET AS DEFAULT")
2541             self.defaultPushButton.setEnabled(True)
2542
2543         if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2544             QToolTip.add(self.defaultPushButton, self.__tr("The printer is currently: %1").arg(s))
2545
2546         else:
2547             QToolTip.add(self.defaultPushButton, self.__tr("The fax is currently: %1").arg(s))
2548
2549         self.printer_state = cups.IPP_PRINTER_STATE_IDLE
2550
2551         cups_printers = cups.getPrinters()
2552
2553         for p in cups_printers:
2554             if p.name.decode('utf-8') == self.cur_printer:
2555                 self.printer_state = p.state
2556                 self.printer_accepting = p.accepting
2557                 break
2558
2559         # start/stop
2560         if self.printer_state == cups.IPP_PRINTER_STATE_IDLE:
2561             s = self.__tr("IDLE")
2562
2563             if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2564                 self.stopstartPushButton.setText(self.__tr("Stop Printer"))
2565
2566             else:
2567                 self.stopstartPushButton.setText(self.__tr("Stop Fax"))
2568
2569         elif self.printer_state == cups.IPP_PRINTER_STATE_PROCESSING:
2570             s = self.__tr("PROCESSING")
2571
2572             if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2573                 self.stopstartPushButton.setText(self.__tr("Stop Printer"))
2574
2575             else:
2576                 self.stopstartPushButton.setText(self.__tr("Stop Fax"))
2577         else:
2578             s = self.__tr("STOPPED")
2579
2580             if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2581                 self.stopstartPushButton.setText(self.__tr("Start Printer"))
2582
2583             else:
2584                 self.stopstartPushButton.setText(self.__tr("Start Fax"))
2585
2586         if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2587             QToolTip.add(self.stopstartPushButton, self.__tr("The printer is currently: %1").arg(s))
2588
2589         else:
2590             QToolTip.add(self.stopstartPushButton, self.__tr("The fax is currently: %1").arg(s))
2591
2592         # reject/accept
2593         if self.printer_accepting:
2594             s = self.__tr("ACCEPTING JOBS")
2595             self.rejectacceptPushButton.setText(self.__tr("Reject Jobs"))
2596
2597         else:
2598             s = self.__tr("REJECTING JOBS")
2599             self.rejectacceptPushButton.setText(self.__tr("Accept Jobs"))
2600
2601         if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2602             QToolTip.add(self.rejectacceptPushButton, self.__tr("The printer is currently: %1").arg(s))
2603
2604         else:
2605             QToolTip.add(self.rejectacceptPushButton, self.__tr("The fax is currently: %1").arg(s))
2606
2607
2608     def stopstartPushButton_clicked(self):
2609         QApplication.setOverrideCursor(QApplication.waitCursor)
2610         try:
2611             if self.printer_state in (cups.IPP_PRINTER_STATE_IDLE, cups.IPP_PRINTER_STATE_PROCESSING):
2612                 result = cups.stop(self.cur_printer)
2613                 if result:
2614                     if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2615                         e = EVENT_PRINTER_QUEUE_STOPPED
2616                     else:
2617                         e = EVENT_FAX_QUEUE_STOPPED
2618
2619             else:
2620                 result = cups.start(self.cur_printer)
2621                 if result:
2622                     if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2623                         e = EVENT_PRINTER_QUEUE_STARTED
2624                     else:
2625                         e = EVENT_FAX_QUEUE_STARTED
2626
2627             if result:
2628                 self.UpdatePrintController()
2629                 self.cur_device.sendEvent(e, self.cur_printer)
2630             else:
2631                 log.error("Start/Stop printer operation failed")
2632                 self.FailureUI(self.__tr("<b>Start/Stop printer operation failed.</b><p>Try after add user to \"lp\" group."))
2633
2634         finally:
2635             QApplication.restoreOverrideCursor()
2636
2637
2638     def rejectacceptPushButton_clicked(self):
2639         QApplication.setOverrideCursor(QApplication.waitCursor)
2640         try:
2641             if self.printer_accepting:
2642                 result = cups.reject(self.cur_printer)
2643                 if result:
2644                     if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2645                         e = EVENT_PRINTER_QUEUE_REJECTING_JOBS
2646                     else:
2647                         e = EVENT_FAX_QUEUE_REJECTING_JOBS
2648
2649             else:
2650                 result = cups.accept(self.cur_printer)
2651                 if result:
2652                     if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2653                         e = EVENT_PRINTER_QUEUE_ACCEPTING_JOBS
2654                     else:
2655                         e = EVENT_FAX_QUEUE_ACCEPTING_JOBS
2656
2657             if result:
2658                 self.UpdatePrintController()
2659                 self.cur_device.sendEvent(e, self.cur_printer)
2660             else:
2661                 log.error("Reject/Accept jobs operation failed")
2662                 self.FailureUI(self.__tr("<b>Accept/Reject printer operation failed.</b><p>Try after add user to \"lp\" group."))
2663
2664         finally:
2665             QApplication.restoreOverrideCursor()
2666
2667
2668     def defaultPushButton_clicked(self):
2669         QApplication.setOverrideCursor(QApplication.waitCursor)
2670         try:
2671             result = cups.setDefaultPrinter(self.cur_printer.encode('utf8'))
2672             if not result:
2673                 log.error("Set default printer failed.")
2674                 self.FailureUI(self.__tr("<b>Set default printer operation failed.</b><p>Try after add user to \"lp\" group."))
2675             else:
2676                 self.UpdatePrintController()
2677                 if self.cur_device.device_type == DEVICE_TYPE_PRINTER:
2678                     e = EVENT_PRINTER_QUEUE_SET_AS_DEFAULT
2679                 else:
2680                     e = EVENT_FAX_QUEUE_SET_AS_DEFAULT
2681
2682                 self.cur_device.sendEvent(e, self.cur_printer)
2683
2684         finally:
2685             QApplication.restoreOverrideCursor()
2686
2687
2688     def cancelCheckedJobs(self):
2689         QApplication.setOverrideCursor(QApplication.waitCursor)
2690         try:
2691             item = self.jobList.firstChild()
2692             while item is not None:
2693                 if item.isOn():
2694                     self.cur_device.cancelJob(int(item.job_id))
2695
2696                 item = item.nextSibling()
2697
2698         finally:
2699             QApplication.restoreOverrideCursor()
2700
2701         self.UpdatePrintControlTab()
2702
2703     def UpdateUpgradeTab(self):
2704         log.debug("Upgrade Tab is pressed")
2705         self.InstallPushButton_lock = False
2706
2707     def InstallPushButton_clicked(self):
2708         if self.InstallPushButton_lock is True:
2709             return
2710
2711         if self.Is_autoInstaller_distro:
2712             self.InstallPushButton.setEnabled(False)
2713             terminal_cmd = utils.get_terminal()
2714             if terminal_cmd is not None and utils.which("hp-upgrade"):
2715                 cmd = terminal_cmd + " 'hp-upgrade'"
2716                 log.debug("cmd = %s " %cmd)
2717                 os.system(cmd)
2718             else:
2719                 log.error("Failed to run hp-upgrade command from terminal =%s "%terminal_cmd)
2720             self.InstallPushButton.setEnabled(True)
2721         else:
2722             self.InstallPushButton_lock = True
2723             utils.openURL("http://hplipopensource.com/hplip-web/install/manual/index.html")
2724             QTimer.singleShot(1000, self.InstallPushButton_unlock)
2725
2726     def InstallPushButton_unlock(self):
2727         self.InstallPushButton_lock = False
2728
2729     # ***********************************************************************************
2730     #
2731     # EXIT/CHILD CLEANUP
2732     #
2733     # ***********************************************************************************
2734
2735     def closeEvent(self, event):
2736         self.Cleanup()
2737         self.request_queue.put(None)
2738         event.accept()
2739
2740
2741     def Cleanup(self):
2742         self.request_queue.put(None)
2743         self.CleanupChildren()
2744         if not self.update_thread.wait(5000):
2745             self.update_thread.terminate()
2746
2747
2748     def CleanupChildren(self):
2749         log.debug("Cleaning up child processes.")
2750         try:
2751             os.waitpid(-1, os.WNOHANG)
2752         except OSError:
2753             pass
2754
2755
2756     # ***********************************************************************************
2757     #
2758     # DEVICE SETTINGS PLUGIN
2759     #
2760     # ***********************************************************************************
2761
2762     def CheckForDeviceSettingsUI(self, dev):
2763         dev.device_settings_ui = None
2764         name = '.'.join(['plugins', dev.model])
2765         log.debug("Attempting to load plugin: %s" % name)
2766         try:
2767             mod = __import__(name, globals(), locals(), [])
2768         except ImportError:
2769             log.debug("No plugin found.")
2770             return
2771         else:
2772             components = name.split('.')
2773             for c in components[1:]:
2774                 mod = getattr(mod, c)
2775             log.debug("Loaded: %s" % repr(mod))
2776             dev.device_settings_ui = mod.settingsUI
2777
2778
2779     # ***********************************************************************************
2780     #
2781     # SETTINGS DIALOG
2782     #
2783     # ***********************************************************************************
2784
2785     def settingsConfigure_activated(self, tab_to_show=0):
2786         dlg = SettingsDialog(self)
2787         dlg.TabWidget.setCurrentPage(tab_to_show)
2788
2789         if dlg.exec_loop() == QDialog.Accepted:
2790             old_auto_refresh = self.user_settings.auto_refresh_rate
2791             self.user_settings.load()
2792
2793             if self.user_settings.auto_refresh and old_auto_refresh != self.user_settings.auto_refresh_rate:
2794                 self.refresh_timer.changeInterval(self.user_settings.auto_refresh_rate * 1000)
2795
2796             if old_auto_refresh != self.user_settings.auto_refresh:
2797                 self.autoRefresh.toggle()
2798
2799
2800     # ***********************************************************************************
2801     #
2802     # SETUP/REMOVE
2803     #
2804     # ***********************************************************************************
2805
2806     def deviceInstallAction_activated(self):
2807         if utils.which('hp-setup'):
2808             cmd = 'hp-setup -u'
2809         else:
2810             cmd = 'python ./setup.py --gui'
2811
2812         log.debug(cmd)
2813         utils.run(cmd, log_output=True, password_func=None, timeout=1)
2814         self.RescanDevices()
2815
2816
2817     def deviceRemoveAction_activated(self):
2818         if self.cur_device is not None:
2819             x = QMessageBox.critical(self,
2820            self.caption(),
2821            self.__tr("<b>Annoying Confirmation: Are you sure you want to remove this device?</b>"),
2822             QMessageBox.Yes,
2823             QMessageBox.No | QMessageBox.Default,
2824             QMessageBox.NoButton)
2825             if x == QMessageBox.Yes:
2826                 QApplication.setOverrideCursor(QApplication.waitCursor)
2827                 print_uri = self.cur_device.device_uri
2828                 fax_uri = print_uri.replace('hp:', 'hpfax:')
2829
2830                 log.debug(print_uri)
2831                 log.debug(fax_uri)
2832
2833                 self.cups_devices = device.getSupportedCUPSDevices(['hp', 'hpfax'])
2834
2835                 for d in self.cups_devices:
2836                     if d in (print_uri, fax_uri):
2837                         for p in self.cups_devices[d]:
2838                             log.debug("Removing %s" % p)
2839                             r = cups.delPrinter(p)
2840                             if r == 0:
2841                                 self.FailureUI(self.__tr("<p><b>Delete printer queue fails.</b><p>Try after add user to \"lp\" group."))
2842
2843                 self.cur_device = None
2844                 self.cur_device_uri = ''
2845                 user_conf.set('last_used', 'device_uri', '')
2846                 QApplication.restoreOverrideCursor()
2847
2848                 self.RescanDevices()
2849
2850
2851     # ***********************************************************************************
2852     #
2853     # MISC
2854     #
2855     # ***********************************************************************************
2856
2857
2858     def RunCommand(self, cmd, macro_char='%'):
2859         QApplication.setOverrideCursor(QApplication.waitCursor)
2860
2861         try:
2862             if len(cmd) == 0:
2863                 self.FailureUI(self.__tr("<p><b>Unable to run command. No command specified.</b><p>Use <pre>Configure...</pre> to specify a command to run."))
2864                 log.error("No command specified. Use settings to configure commands.")
2865             else:
2866                 log.debug("Run: %s %s (%s) %s" % ("*"*20, cmd, self.cur_device_uri, "*"*20))
2867                 log.debug(cmd)
2868
2869                 try:
2870                     cmd = ''.join([self.cur_device.device_vars.get(x, x) \
2871            for x in cmd.split(macro_char)])
2872                 except AttributeError:
2873                     pass
2874
2875                 log.debug(cmd)
2876
2877                 path = cmd.split()[0]
2878                 args = cmd.split()
2879
2880                 log.debug(path)
2881                 log.debug(args)
2882
2883                 self.CleanupChildren()
2884                 os.spawnvp(os.P_NOWAIT, path, args)
2885                 qApp.processEvents()
2886
2887         finally:
2888             QApplication.restoreOverrideCursor()
2889
2890
2891     def helpContents(self):
2892         f = "http://hplip.sf.net"
2893
2894         if prop.doc_build:
2895             g = os.path.join(sys_conf.get('dirs', 'doc'), 'index.html')
2896             if os.path.exists(g):
2897                 f = "file://%s" % g
2898
2899         log.debug(f)
2900         utils.openURL(f)
2901
2902
2903     def helpAbout(self):
2904         dlg = AboutDlg(self)
2905         dlg.VersionText.setText(prop.version)
2906         dlg.ToolboxVersionText.setText(self.toolbox_version + " (Qt3)")
2907         dlg.exec_loop()
2908
2909
2910     def FailureUI(self, error_text):
2911         QMessageBox.critical(self,
2912             self.caption(),
2913             error_text,
2914             QMessageBox.Ok,
2915             QMessageBox.NoButton,
2916             QMessageBox.NoButton)
2917
2918
2919     def WarningUI(self, msg):
2920         QMessageBox.warning(self,
2921             self.caption(),
2922             msg,
2923             QMessageBox.Ok,
2924             QMessageBox.NoButton,
2925             QMessageBox.NoButton)
2926
2927
2928     def __tr(self,s,c = None):
2929         return qApp.translate("DevMgr4",s,c)
2930
2931
2932
2933 # ***********************************************************************************
2934 #
2935 # ScrollDeviceInfoView (View Device Information)
2936 #
2937 # ***********************************************************************************
2938
2939 class ScrollDeviceInfoView(ScrollView):
2940     def __init__(self, service, parent=None, form=None, name=None, fl=0):
2941         ScrollView.__init__(self, service, parent, name, fl)
2942
2943
2944     def fillControls(self):
2945         ScrollView.fillControls(self)
2946         self.addDeviceInfo()
2947         self.maximizeControl()
2948
2949
2950     def addDeviceInfo(self):
2951         self.addGroupHeading("info_title", self.__tr("Device Information"))
2952
2953         widget = self.getWidget()
2954
2955         layout37 = QGridLayout(widget,1,1,5,10,"layout37")
2956
2957         self.infoListView = QListView(widget,"fileListView")
2958         self.infoListView.addColumn(self.__tr("Static/Dynamic"))
2959         self.infoListView.addColumn(self.__tr("Key"))
2960         self.infoListView.addColumn(self.__tr("Value"))
2961         self.infoListView.setAllColumnsShowFocus(1)
2962         self.infoListView.setShowSortIndicator(1)
2963         self.infoListView.setColumnWidth(0, 50)
2964         self.infoListView.setColumnWidth(1, 150)
2965         self.infoListView.setColumnWidth(2, 300)
2966         self.infoListView.setItemMargin(2)
2967         self.infoListView.setSorting(-1)
2968
2969         layout37.addMultiCellWidget(self.infoListView,1,1,0,3)
2970
2971         mq_keys = self.cur_device.mq.keys()
2972         mq_keys.sort()
2973         mq_keys.reverse()
2974         for key,i in zip(mq_keys, range(len(mq_keys))):
2975             QListViewItem(self.infoListView, self.__tr("Static"), key, str(self.cur_device.mq[key]))
2976
2977         dq_keys = self.cur_device.dq.keys()
2978         dq_keys.sort()
2979         dq_keys.reverse()
2980         for key,i in zip(dq_keys, range(len(dq_keys))):
2981             QListViewItem(self.infoListView, self.__tr("Dynamic"), key, str(self.cur_device.dq[key]))
2982
2983         self.addWidget(widget, "file_list", maximize=True)
2984
2985
2986     def __tr(self,s,c = None):
2987         return qApp.translate("ScrollDeviceInfoView",s,c)
2988
2989
2990
2991 # ***********************************************************************************
2992 #
2993 # ScrollTestpageView (Print Test Page)
2994 #
2995 # ***********************************************************************************
2996
2997 class ScrollTestpageView(ScrollView):
2998     def __init__(self, service, parent=None, form=None, name=None, fl=0):
2999         ScrollView.__init__(self, service, parent, name, fl)
3000         self.dialog = parent
3001
3002
3003     def fillControls(self):
3004         ScrollView.fillControls(self)
3005
3006         if self.addPrinterFaxList():
3007             self.addTestpageType()
3008
3009             self.addLoadPaper()
3010
3011             self.printButton = self.addActionButton("bottom_nav", self.__tr("Print Test Page"),
3012                 self.printButton_clicked, 'print.png', None)
3013
3014
3015     def addTestpageType(self):
3016         self.addGroupHeading("testpage_type", self.__tr("Test Page Type"))
3017         widget = self.getWidget()
3018
3019         Form4Layout = QGridLayout(widget,1,1,5,10,"Form4Layout")
3020
3021         self.buttonGroup3 = QButtonGroup(widget,"buttonGroup3")
3022         self.buttonGroup3.setLineWidth(0)
3023         self.buttonGroup3.setColumnLayout(0,Qt.Vertical)
3024         self.buttonGroup3.layout().setSpacing(5)
3025         self.buttonGroup3.layout().setMargin(10)
3026
3027         buttonGroup3Layout = QGridLayout(self.buttonGroup3.layout())
3028         buttonGroup3Layout.setAlignment(Qt.AlignTop)
3029
3030         self.radioButton6 = QRadioButton(self.buttonGroup3,"radioButton6")
3031         self.radioButton6.setEnabled(False)
3032         buttonGroup3Layout.addWidget(self.radioButton6,1,0)
3033
3034         self.radioButton5 = QRadioButton(self.buttonGroup3,"radioButton5")
3035         self.radioButton5.setChecked(1)
3036         buttonGroup3Layout.addWidget(self.radioButton5,0,0)
3037
3038         Form4Layout.addWidget(self.buttonGroup3,0,0)
3039
3040         self.radioButton6.setText(self.__tr("Printer diagnostic page (does not test print driver)"))
3041         self.radioButton5.setText(self.__tr("HPLIP test page (tests print driver)"))
3042
3043         self.addWidget(widget, "page_type")
3044
3045
3046     def printButton_clicked(self):
3047         d = self.cur_device
3048         printer_name = self.cur_printer
3049         printed = False
3050
3051         try:
3052             QApplication.setOverrideCursor(QApplication.waitCursor)
3053
3054             try:
3055                 d.open()
3056             except Error:
3057                 self.CheckDeviceUI()
3058             else:
3059                 try:
3060                     if d.isIdleAndNoError():
3061                         QApplication.restoreOverrideCursor()
3062                         d.close()
3063
3064                         d.printTestPage(printer_name)
3065                         printed = True
3066
3067                     else:
3068                         d.close()
3069                         self.CheckDeviceUI()
3070                 except Error:
3071                     self.CheckDeviceUI()
3072
3073         finally:
3074             QApplication.restoreOverrideCursor()
3075
3076         if printed:
3077             QMessageBox.information(self,
3078                 self.caption(),
3079                 self.__tr("<p><b>A test page should be printing on your printer.</b><p>If the page fails to print, please visit http://hplip.sourceforge.net for troubleshooting and support."),
3080                 QMessageBox.Ok,
3081                 QMessageBox.NoButton,
3082                 QMessageBox.NoButton)
3083
3084         self.dialog.accept()
3085
3086
3087     def CheckDeviceUI(self):
3088             self.FailureUI(self.__tr("<b>Device is busy or in an error state.</b><p>Please check device and try again."))
3089
3090
3091     def FailureUI(self, error_text):
3092         QMessageBox.critical(self,
3093             self.caption(),
3094             error_text,
3095             QMessageBox.Ok,
3096             QMessageBox.NoButton,
3097             QMessageBox.NoButton)
3098
3099
3100     def __tr(self,s,c = None):
3101         return qApp.translate("ScrollTestpageView",s,c)
3102
3103 # ***********************************************************************************
3104 #
3105 # ScrollPrinterInfoView (View Device Information)
3106 #
3107 # ***********************************************************************************
3108
3109 class ScrollPrinterInfoView(ScrollView):
3110     def __init__(self, service, parent = None, form=None, name = None,fl = 0):
3111         ScrollView.__init__(self, service, parent, name, fl)
3112
3113
3114     def fillControls(self):
3115         ScrollView.fillControls(self)
3116
3117         printers = []
3118         for p in self.printers:
3119             if p.device_uri == self.cur_device.device_uri:
3120                 printers.append(p)
3121
3122         if not printers:
3123             self.addGroupHeading("error_title", self.__tr("No printers found for this device."))
3124         else:
3125             for p in printers:
3126                 self.addPrinterInfo(p)
3127
3128         self.maximizeControl()
3129
3130
3131     def addPrinterInfo(self, p):
3132         self.addGroupHeading(p.name, p.name)
3133         widget = self.getWidget()
3134
3135         layout1 = QVBoxLayout(widget,5,10,"layout1")
3136
3137         textLabel2 = QLabel(widget,"textLabel2")
3138
3139         if p.device_uri.startswith("hpfax:"):
3140             s = self.__tr("Fax")
3141         else:
3142             s = self.__tr("Printer")
3143
3144         textLabel2.setText(self.__tr("Type: %1").arg(s))
3145         layout1.addWidget(textLabel2)
3146
3147         textLabel3 = QLabel(widget,"textLabel3")
3148         textLabel3.setText(self.__tr("Location: %1").arg(p.location))
3149         layout1.addWidget(textLabel3)
3150
3151         textLabel4 = QLabel(widget,"textLabel4")
3152         textLabel4.setText(self.__tr("Description/Info: %1").arg(p.info))
3153         layout1.addWidget(textLabel4)
3154
3155         textLabel5 = QLabel(widget,"textLabel5")
3156
3157         if p.state == cups.IPP_PRINTER_STATE_IDLE:
3158             s = self.__tr("Idle")
3159         elif p.state == cups.IPP_PRINTER_STATE_PROCESSING:
3160             s = self.__tr("Processing")
3161         elif p.state == cups.IPP_PRINTER_STATE_STOPPED:
3162             s = self.__tr("Stopped")
3163         else:
3164             s = self.__tr("Unknown")
3165
3166         textLabel5.setText(self.__tr("State: %1").arg(s))
3167         layout1.addWidget(textLabel5)
3168
3169         textLabel6 = QLabel(widget,"textLabel6")
3170         textLabel6.setText(self.__tr("PPD/Driver: %1").arg(p.makemodel))
3171         layout1.addWidget(textLabel6)
3172
3173         textLabel7 = QLabel(widget,"textLabel7")
3174         textLabel7.setText(self.__tr("CUPS/IPP Printer URI: %1").arg(p.printer_uri))
3175         layout1.addWidget(textLabel7)
3176
3177         self.addWidget(widget, p.name)
3178
3179
3180     def __tr(self,s,c = None):
3181         return qApp.translate("ScrollPrinterInfoView",s,c)
3182
3183
3184
3185
3186 # ***********************************************************************************
3187 #
3188 # Color cal type 7
3189 #
3190 # ***********************************************************************************
3191
3192 class ScrollColorCalView(ScrollView):
3193     def __init__(self, service, parent = None, form=None, name = None,fl = 0):
3194         ScrollView.__init__(self, service, parent, name, fl)
3195         self.dialog = parent
3196
3197
3198     def fillControls(self):
3199         ScrollView.fillControls(self)
3200         self.addLoadPaper(PAPER_TYPE_HP_ADV_PHOTO)
3201
3202         self.printButton = self.addActionButton("bottom_nav", self.__tr("Perform Color Calibration"),
3203             self.colorcalButton_clicked, 'print.png', None)
3204
3205
3206     def colorcalButton_clicked(self):
3207         d = self.cur_device
3208         printer_name = self.cur_printer
3209         printed = False
3210
3211         try:
3212             QApplication.setOverrideCursor(QApplication.waitCursor)
3213
3214             try:
3215                 d.open()
3216             except Error:
3217                 self.CheckDeviceUI()
3218             else:
3219                 if d.isIdleAndNoError():
3220                     QApplication.restoreOverrideCursor()
3221                     d.close()
3222
3223                     d.setPML(pml.OID_PRINT_INTERNAL_PAGE, pml.PRINT_INTERNAL_PAGE_AUTOMATIC_COLOR_CALIBRATION)
3224                     printed = True
3225
3226                 else:
3227                     d.close()
3228                     self.CheckDeviceUI()
3229
3230         finally:
3231             QApplication.restoreOverrideCursor()
3232
3233         if printed:
3234             QMessageBox.information(self,
3235                 self.caption(),
3236                 self.__tr("<p><b>A test page should be printing on your printer.</b><p>If the page fails to print, please visit http://hplip.sourceforge.net for troubleshooting and support."),
3237                 QMessageBox.Ok,
3238                 QMessageBox.NoButton,
3239                 QMessageBox.NoButton)
3240
3241         self.dialog.accept()
3242
3243
3244     def CheckDeviceUI(self):
3245             self.FailureUI(self.__tr("<b>Device is busy or in an error state.</b><p>Please check device and try again."))
3246
3247
3248     def FailureUI(self, error_text):
3249         QMessageBox.critical(self,
3250             self.caption(),
3251             error_text,
3252             QMessageBox.Ok,
3253             QMessageBox.NoButton,
3254             QMessageBox.NoButton)
3255
3256
3257     def __tr(self,s,c = None):
3258         return qApp.translate("ScrollColorCalView",s,c)