Tizen 2.1 base
[platform/upstream/hplip.git] / ui4 / ui_utils.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 # Author: Don Welch
20 #
21
22 # Std Lib
23 import os.path
24 import re
25 import os
26 import time
27
28 # Local
29 from base.g import *
30 from base.codes import *
31 from base import utils
32 from prnt import cups
33
34 # Qt
35 from PyQt4.QtCore import *
36 from PyQt4.QtGui import *
37
38 pat_html_remove = re.compile("(?is)<.*?>", re.I)
39
40 # databaseChanged signal values (for FABWindow)
41 FAB_NAME_ADD = 0  # s1 - new name
42 FAB_NAME_RENAME = 1 # s1 - old name, s2 - new name
43 FAB_NAME_REMOVE = 2 # s1 - removed name
44 FAB_NAME_DETAILS_CHANGED = 3 # s1 - name
45 FAB_GROUP_ADD = 4 # s1 - new group
46 FAB_GROUP_RENAME = 5 # s1 - old group, s2 - new group
47 FAB_GROUP_REMOVE = 6 # s1 - removed group
48 FAB_GROUP_MEMBERSHIP_CHANGED = 7 # s1 - group
49
50
51 def __translate(t):
52     return QApplication.translate("ui_utils", t, None, QApplication.UnicodeUTF8)
53
54
55 def beginWaitCursor():
56     QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
57
58
59 def endWaitCursor():
60     QApplication.restoreOverrideCursor()
61
62
63 # TODO: Cache pixmaps
64 def load_pixmap(name, subdir=None, resize_to=None):
65     name = ''.join([os.path.splitext(name)[0], '.png'])
66
67     if subdir is None:
68         dir = prop.image_dir
69         ldir = os.path.join(os.getcwd(), 'data', 'images')
70     else:
71         dir = os.path.join(prop.image_dir, subdir)
72         ldir = os.path.join(os.getcwd(), 'data', 'images', subdir)
73
74     for d in [dir, ldir]:
75         f = os.path.join(d, name)
76         if os.path.exists(f):
77             if resize_to is not None:
78                 img = QImage(f)
79                 x, y = resize_to
80                 return QPixmap.fromImage(img.scaled(x, y, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
81             else:
82                 return QPixmap(f)
83
84         for w in utils.walkFiles(dir, recurse=True, abs_paths=True, return_folders=False, pattern=name):
85             if resize_to is not None:
86                 img = QImage(w)
87                 x, y = resize_to
88                 return QPixmap.fromImage(img.scaled(x, y, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
89             else:
90                 return QPixmap(w)
91
92     log.error("Pixmap '%s' not found!" % name)
93     return QPixmap()
94
95 loadPixmap = load_pixmap
96
97
98 def getPynotifyIcon(name, subdir='32x32'):
99     name = ''.join([os.path.splitext(name)[0], '.png'])
100     return "file://" + os.path.join(prop.image_dir, subdir, name)
101
102
103
104 class UserSettings(QSettings):
105     def __init__(self):
106         if prop.user_dir is None:
107             QSettings.__init__(self)
108         else:
109             QSettings.__init__(self, os.path.join(prop.user_dir,  'hplip.conf'),  QSettings.IniFormat)
110
111         self.systray_visible = SYSTRAY_VISIBLE_SHOW_ALWAYS
112         self.systray_messages = SYSTRAY_MESSAGES_SHOW_ALL
113         self.last_used_device_uri = ''
114         self.last_used_printer = ''
115         self.version = ''
116         self.date_time = ''
117         self.auto_refresh = False
118         self.auto_refresh_rate = 30
119         self.auto_refresh_type = 1
120         self.polling_interval = 5
121         self.polling = True
122         self.device_list = []
123         self.working_dir = '.'
124         self.voice_phone = ''
125         self.email_address = ''
126         self.upgrade_notify=True
127         self.upgrade_last_update_time=0
128         self.upgrade_pending_update_time=0
129         self.latest_available_version=""
130         self.loadDefaults()
131
132
133     def __setup(self,  cmds):
134         for c in cmds:
135             basename = c.split()[0]
136             path = utils.which(basename)
137             if path:
138                 return ' '.join([os.path.join(path, basename), ' '.join(c.split()[1:])])
139
140         return ''
141
142
143     def loadDefaults(self):
144         self.cmd_scan = self.__setup(['xsane -V %SANE_URI%', 'kooka', 'xscanimage'])
145         self.cmd_fab = self.__setup(['hp-fab'])
146
147
148     def load(self):
149         log.debug("Loading user settings...")
150         self.sync()
151
152         self.beginGroup("settings")
153         i, ok = self.value("systray_visible").toInt()
154         if ok:
155             self.systray_visible = i
156
157         i, ok = self.value("systray_messages").toInt()
158         if ok:
159             self.systray_messages = i
160
161         self.endGroup()
162
163         self.beginGroup("last_used")
164         self.last_used_device_uri = unicode(self.value("device_uri").toString()) or self.last_used_device_uri
165         self.last_used_printer = unicode(self.value("printer_name").toString()) or self.last_used_printer
166         self.working_dir = unicode(self.value("working_dir").toString()) or self.working_dir
167         self.endGroup()
168
169         self.beginGroup("commands")
170         self.cmd_scan = unicode(self.value("scan").toString()) or self.cmd_scan
171         self.endGroup()
172
173         self.beginGroup("refresh")
174         self.auto_refresh_rate = int(self.value("rate").toString() or self.auto_refresh_rate)
175         self.auto_refresh = bool(self.value("enable").toBool())
176         self.auto_refresh_type = int(self.value("type").toString() or self.auto_refresh_type)
177         self.endGroup()
178
179         self.beginGroup("installation")
180         self.version = unicode(self.value("version").toString())
181         self.date_time = unicode(self.value("date_time").toString())
182         self.endGroup()
183
184         self.beginGroup("polling")
185         self.polling = bool(self.value("enable").toBool())
186         self.polling_interval = int(self.value("interval").toString() or self.polling_interval)
187         self.polling_device_list = unicode(self.value("device_list").toString() or '').split(u',')
188         self.endGroup()
189
190         self.beginGroup("fax")
191         self.voice_phone = unicode(self.value("voice_phone").toString())
192         self.email_address = unicode(self.value("email_address").toString())
193         self.endGroup()
194         
195         self.beginGroup("upgrade")
196         self.upgrade_notify= bool(self.value("notify_upgrade").toBool())
197         self.latest_available_version=str(self.value("latest_available_version").toString())
198             
199         i, Ok = self.value("last_upgraded_time").toInt()
200         if Ok and i >0:
201             self.upgrade_last_update_time =i
202         else:
203             self.upgrade_last_update_time = 0
204             
205         i, Ok = self.value("pending_upgrade_time").toInt()
206         if Ok and i >0 :
207             self.upgrade_pending_update_time = i
208         else:
209             self.upgrade_pending_update_time = 0
210             
211         self.endGroup()
212
213
214     def save(self):
215         log.debug("Saving user settings...")
216
217         self.beginGroup("settings")
218         self.setValue("systray_visible", QVariant(self.systray_visible))
219         self.setValue("systray_messages", QVariant(self.systray_messages))
220         self.endGroup()
221
222         self.beginGroup("last_used")
223         self.setValue("device_uri",  QVariant(self.last_used_device_uri))
224         self.setValue("printer_name", QVariant(self.last_used_printer))
225         self.setValue("working_dir", QVariant(self.working_dir))
226         self.endGroup()
227
228         self.beginGroup("commands")
229         self.setValue("scan",  QVariant(self.cmd_scan))
230         self.endGroup()
231
232         self.beginGroup("refresh")
233         self.setValue("rate", QVariant(self.auto_refresh_rate))
234         self.setValue("enable", QVariant(self.auto_refresh))
235         self.setValue("type", QVariant(self.auto_refresh_type))
236         self.endGroup()
237
238         self.beginGroup("polling")
239         self.setValue("enable", QVariant(self.polling))
240         self.setValue("interval", QVariant(self.polling_interval))
241         self.setValue("device_list", QVariant(u','.join(self.polling_device_list)))
242         self.endGroup()
243
244         self.beginGroup("fax")
245         self.setValue("voice_phone", QVariant(self.voice_phone))
246         self.setValue("email_address", QVariant(self.email_address))
247         self.endGroup()
248         
249         self.beginGroup("upgrade")
250         self.setValue("notify_upgrade", QVariant(self.upgrade_notify))
251         if self.upgrade_last_update_time <1:
252             self.upgrade_last_update_time = time.time()         # <---Need to verify code once
253             
254         self.setValue("last_upgraded_time", QVariant(self.upgrade_last_update_time))
255         self.setValue("pending_upgrade_time", QVariant(self.upgrade_pending_update_time))
256         self.endGroup()
257
258
259         self.sync()
260
261
262     def debug(self):
263         log.debug("FAB command: %s" % self.cmd_fab)
264         log.debug("Scan command: %s" % self.cmd_scan)
265         log.debug("Auto refresh: %s" % self.auto_refresh)
266         log.debug("Auto refresh rate: %s" % self.auto_refresh_rate)
267         log.debug("Auto refresh type: %s" % self.auto_refresh_type)
268         log.debug("Systray visible: %d" % self.systray_visible)
269         log.debug("Systray messages: %d" % self.systray_messages)
270         log.debug("Last used device URI: %s" % self.last_used_device_uri)
271         log.debug("Last used printer: %s" % self.last_used_printer)
272         log.debug("Working directory: %s" % self.working_dir)
273
274
275 DEFAULT_TITLE =  __translate("HP Device Manager")
276
277
278 def FailureUI(parent, error_text, title_text=None):
279     log.error(pat_html_remove.sub(' ', unicode(error_text)))
280
281     if title_text is None:
282         if parent is not None:
283             title_text = parent.windowTitle()
284         else:
285             title_text = DEFAULT_TITLE
286
287     QMessageBox.critical(parent,
288         title_text,
289         error_text,
290         QMessageBox.Ok,
291         QMessageBox.NoButton,
292         QMessageBox.NoButton)
293
294 showFailureUi = FailureUI
295
296
297 def WarningUI(parent,  warn_text, title_text=None):
298     log.warn(pat_html_remove.sub(' ', unicode(warn_text)))
299
300     if title_text is None:
301         if parent is not None:
302             title_text = parent.windowTitle()
303         else:
304             title_text = DEFAULT_TITLE
305
306
307     QMessageBox.warning(parent,
308         title_text,
309         warn_text,
310         QMessageBox.Ok,
311         QMessageBox.NoButton,
312         QMessageBox.NoButton)
313
314 showWarningUi = WarningUI
315
316
317 def SuccessUI(parent, text, title_text=None):
318     log.info(pat_html_remove.sub(' ', unicode(text)))
319
320     if title_text is None:
321         if parent is not None:
322             title_text = parent.windowTitle()
323         else:
324             title_text = DEFAULT_TITLE
325
326
327     QMessageBox.information(parent,
328         title_text,
329         text,
330         QMessageBox.Ok,
331         QMessageBox.NoButton,
332         QMessageBox.NoButton)
333
334 showSuccessUi = SuccessUI
335
336
337 def CheckDeviceUI(parent, title_text=None):
338     text = __translate("<b>Unable to communicate with device or device is in an error state.</b><p>Please check device setup and try again.</p>")
339     return FailureUI(parent, text, title_text)
340
341 checkDeviceUi = CheckDeviceUI
342
343
344 class PrinterNameValidator(QValidator):
345     def __init__(self, parent=None):
346         QValidator.__init__(self, parent)
347
348     def validate(self, input, pos):
349         input = unicode(input)
350
351         if not input:
352             return QValidator.Acceptable, pos
353
354         if input[pos-1] in cups.INVALID_PRINTER_NAME_CHARS:
355             return QValidator.Invalid, pos
356
357         # TODO: How to determine if unicode char is "printable" and acceptable
358         # to CUPS?
359         #elif input != utils.printable(input):
360         #    return QValidator.Invalid, pos
361
362         return QValidator.Acceptable, pos
363
364
365
366 class PhoneNumValidator(QValidator):
367     def __init__(self, parent=None):
368         QValidator.__init__(self, parent)
369
370     def validate(self, input, pos):
371         input = unicode(input)
372
373         if not input:
374             return QValidator.Acceptable, pos
375
376         if input[pos-1] not in u'0123456789-(+).,#* ':
377             return QValidator.Invalid, pos
378
379         return QValidator.Acceptable, pos
380
381
382 class AddressBookNameValidator(QValidator):
383     def __init__(self, db, parent=None):
384         QValidator.__init__(self, parent)
385         self.db = db
386
387     def validate(self, input, pos):
388         input = unicode(input)
389
390         if not input:
391             return QValidator.Acceptable, pos
392
393         if input in self.db.get_all_names():
394             return QValidator.Invalid, pos
395
396         if input[pos-1] in u'''|\\/"''': # | is the drag 'n drop separator
397             return QValidator.Invalid, pos
398
399         return QValidator.Acceptable, pos
400
401
402
403 MIME_TYPES_DESC = \
404 {
405     "application/pdf" : (__translate("PDF Document"), '.pdf'),
406     "application/postscript" : (__translate("Postscript Document"), '.ps'),
407     "application/vnd.hp-HPGL" : (__translate("HP Graphics Language File"), '.hgl, .hpg, .plt, .prn'),
408     "application/x-cshell" : (__translate("C Shell Script"), '.csh, .sh'),
409     "application/x-csource" : (__translate("C Source Code"), '.c'),
410     "text/cpp": (__translate("C/C++ Source Code"), '.c, .cpp, .cxx'),
411     "application/x-perl" : (__translate("Perl Script"), '.pl'),
412     "application/x-python" : (__translate("Python Program"), '.py'),
413     "application/x-shell" : (__translate("Shell Script"), '.sh'),
414     "application/x-sh" : (__translate("Shell Script"), '.sh'),
415     "text/plain" : (__translate("Plain Text"), '.txt, .log'),
416     "text/html" : (__translate("HTML Dcoument"), '.htm, .html'),
417     "image/gif" : (__translate("GIF Image"), '.gif'),
418     "image/png" : (__translate("PNG Image"), '.png'),
419     "image/jpeg" : (__translate("JPEG Image"), '.jpg, .jpeg'),
420     "image/tiff" : (__translate("TIFF Image"), '.tif, .tiff'),
421     "image/x-bitmap" : (__translate("Bitmap (BMP) Image"), '.bmp'),
422     "image/x-bmp" : (__translate("Bitmap (BMP) Image"), '.bmp'),
423     "image/x-photocd" : (__translate("Photo CD Image"), '.pcd'),
424     "image/x-portable-anymap" : (__translate("Portable Image (PNM)"), '.pnm'),
425     "image/x-portable-bitmap" : (__translate("Portable B&W Image (PBM)"), '.pbm'),
426     "image/x-portable-graymap" : (__translate("Portable Grayscale Image (PGM)"), '.pgm'),
427     "image/x-portable-pixmap" : (__translate("Portable Color Image (PPM)"), '.ppm'),
428     "image/x-sgi-rgb" : (__translate("SGI RGB"), '.rgb'),
429     "image/x-xbitmap" : (__translate("X11 Bitmap (XBM)"), '.xbm'),
430     "image/x-xpixmap" : (__translate("X11 Pixmap (XPM)"), '.xpm'),
431     "image/x-sun-raster" : (__translate("Sun Raster Format"), '.ras'),
432     "application/hplip-fax" : (__translate("HPLIP Fax File"), '.g3, .g4'),
433 }
434
435 # pixmaps for status list(s) (inkjet, laserjet)
436 status_icons = None
437
438 def getStatusListIcon(error_state):
439     global status_icons
440     if status_icons is None:
441         status_icons = {
442           ERROR_STATE_CLEAR : (load_pixmap('idle', '16x16'), load_pixmap('idle', '16x16')),
443           ERROR_STATE_BUSY : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
444           ERROR_STATE_ERROR : (load_pixmap('error', '16x16'), load_pixmap('error', '16x16')),
445           ERROR_STATE_LOW_SUPPLIES : (load_pixmap('inkdrop', '16x16'), load_pixmap('toner', '16x16')),
446           ERROR_STATE_OK : (load_pixmap('ok', '16x16'), load_pixmap('ok', '16x16')),
447           ERROR_STATE_WARNING : (load_pixmap('warning', '16x16'), load_pixmap('warning', '16x16')),
448           ERROR_STATE_LOW_PAPER: (load_pixmap('paper', '16x16'), load_pixmap('paper', '16x16')),
449           ERROR_STATE_PRINTING : (load_pixmap("print", '16x16'), load_pixmap("print", '16x16')),
450           ERROR_STATE_SCANNING : (load_pixmap("scan", '16x16'), load_pixmap("scan", '16x16')),
451           ERROR_STATE_PHOTOCARD : (load_pixmap("pcard", '16x16'), load_pixmap("pcard", '16x16')),
452           ERROR_STATE_FAXING : (load_pixmap("fax", '16x16'), load_pixmap("fax", '16x16')),
453           ERROR_STATE_COPYING :  (load_pixmap("makecopies", '16x16'), load_pixmap("makecopies", '16x16')),
454         }
455
456     return status_icons.get(error_state, status_icons[ERROR_STATE_CLEAR])
457
458 # pixmaps for device icons (inkjet, laserjet)
459 overlay_icons = None
460
461 def getStatusOverlayIcon(error_state):
462     global overlay_icons
463     if overlay_icons is None:
464         overlay_icons = {
465             ERROR_STATE_CLEAR : (None, None),
466             ERROR_STATE_BUSY : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
467             ERROR_STATE_ERROR : (load_pixmap('error', '16x16'), load_pixmap('error', '16x16')),
468             ERROR_STATE_LOW_SUPPLIES : (load_pixmap('inkdrop', '16x16'), load_pixmap('toner', '16x16')),
469             ERROR_STATE_OK : (load_pixmap('ok', '16x16'), load_pixmap('ok', '16x16')),
470             ERROR_STATE_WARNING : (load_pixmap('warning', '16x16'), load_pixmap('warning', '16x16')),
471             ERROR_STATE_LOW_PAPER: (load_pixmap('paper', '16x16'), load_pixmap('paper', '16x16')),
472             ERROR_STATE_PRINTING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
473             ERROR_STATE_SCANNING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
474             ERROR_STATE_PHOTOCARD : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
475             ERROR_STATE_FAXING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
476             ERROR_STATE_COPYING : (load_pixmap('busy', '16x16'), load_pixmap('busy', '16x16')),
477             ERROR_STATE_REFRESHING : (load_pixmap('refresh1', '16x16'), load_pixmap('refresh1', '16x16')),
478         }
479
480     return overlay_icons.get(error_state, overlay_icons[ERROR_STATE_CLEAR])
481
482
483 NUM_REPRS = {
484       1 : __translate("one"),
485       2 : __translate("two"),
486       3 : __translate("three"),
487       4 : __translate("four"),
488       5 : __translate("five"),
489       6 : __translate("six"),
490       7 : __translate("seven"),
491       8 : __translate("eight"),
492       9 : __translate("nine"),
493       10 : __translate("ten"),
494       11 : __translate("eleven"),
495       12 : __translate("twelve")
496 }
497
498 UNIT_NAMES = {
499     "year" : (__translate("year"), __translate("years")),
500     "month" : (__translate("month"), __translate("months")),
501     "week" : (__translate("week"), __translate("weeks")),
502     "day" : (__translate("day"), __translate("days")),
503     "hour" : (__translate("hour"), __translate("hours")),
504     "minute" : (__translate("minute"), __translate("minutes")),
505     "second" : (__translate("second"), __translate("seconds")),
506 }
507
508
509 def getTimeDeltaDesc(past):
510     t1 = QDateTime()
511     t1.setTime_t(int(past))
512     t2 = QDateTime.currentDateTime()
513     delta = t1.secsTo(t2)
514     return __translate("(%1 ago)").arg(stringify(delta))
515
516
517 # "Nicely readable timedelta"
518 # Credit: Bjorn Lindqvist
519 # ASPN Python Recipe 498062
520 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/498062
521 # Note: Modified from recipe
522 def getSecondsInUnits(seconds):
523     unit_limits = [("year", 31536000),
524                    ("month", 2592000),
525                    ("week", 604800),
526                    ("day", 86400),
527                    ("hour", 3600),
528                    ("minute", 60)]
529
530     for unit_name, limit in unit_limits:
531         if seconds >= limit:
532             amount = int(round(float(seconds) / limit))
533             return amount, unit_name
534
535     return seconds, "second"
536
537
538 def stringify(seconds):
539     amount, unit_name = getSecondsInUnits(seconds)
540
541     try:
542         i18n_amount = NUM_REPRS[amount]
543     except KeyError:
544         i18n_amount = unicode(amount)
545
546     if amount == 1:
547         i18n_unit = UNIT_NAMES[unit_name][0]
548     else:
549         i18n_unit = UNIT_NAMES[unit_name][1]
550
551     return QString("%1 %2").arg(i18n_amount).arg(i18n_unit)
552
553