af0646371fcb7b65cd92a3511f53901838b22fde
[gonvert] / src / gonvert_qt.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3
4 from __future__ import with_statement
5
6 import sys
7 import os
8 import math
9 import simplejson
10 import logging
11
12 from PyQt4 import QtGui
13 from PyQt4 import QtCore
14
15 import constants
16 import maeqt
17 from util import misc as misc_utils
18 import unit_data
19
20
21 _moduleLogger = logging.getLogger("gonvert_glade")
22
23
24 IS_MAEMO = True
25
26
27 def change_menu_label(widgets, labelname, newtext):
28         item_label = widgets.get_widget(labelname).get_children()[0]
29         item_label.set_text(newtext)
30
31
32 def split_number(number):
33         try:
34                 fractional, integer = math.modf(number)
35         except TypeError:
36                 integerDisplay = number
37                 fractionalDisplay = ""
38         else:
39                 integerDisplay = str(integer)
40                 fractionalDisplay = str(fractional)
41                 if "e+" in integerDisplay:
42                         integerDisplay = number
43                         fractionalDisplay = ""
44                 elif "e-" in fractionalDisplay and 0.0 < integer:
45                         integerDisplay = number
46                         fractionalDisplay = ""
47                 elif "e-" in fractionalDisplay:
48                         integerDisplay = ""
49                         fractionalDisplay = number
50                 else:
51                         integerDisplay = integerDisplay.split(".", 1)[0] + "."
52                         fractionalDisplay = fractionalDisplay.rsplit(".", 1)[-1]
53
54         return integerDisplay, fractionalDisplay
55
56
57 class Gonvert(object):
58
59         _DATA_PATHS = [
60                 os.path.dirname(__file__),
61                 os.path.join(os.path.dirname(__file__), "../share"),
62                 os.path.join(os.path.dirname(__file__), "../data"),
63                 '/usr/share/gonvert',
64                 '/opt/gonvert/share',
65         ]
66
67         def __init__(self, app):
68                 self._dataPath = ""
69                 for dataPath in self._DATA_PATHS:
70                         appIconPath = os.path.join(dataPath, "pixmaps", "gonvert.png")
71                         if os.path.isfile(appIconPath):
72                                 self._dataPath = dataPath
73                                 break
74                 else:
75                         raise RuntimeError("UI Descriptor not found!")
76                 self._app = app
77                 self._appIconPath = appIconPath
78                 self._recent = []
79                 self._hiddenCategories = set()
80                 self._hiddenUnits = {}
81                 self._clipboard = QtGui.QApplication.clipboard()
82
83                 self._jumpWindow = None
84                 self._recentWindow = None
85                 self._catWindow = None
86
87                 self._condensedAction = QtGui.QAction(None)
88                 self._condensedAction.setText("Condensed View")
89                 self._condensedAction.setCheckable(True)
90                 self._condensedAction.triggered.connect(self._on_condensed_start)
91
92                 self._jumpAction = QtGui.QAction(None)
93                 self._jumpAction.setText("Quick Jump")
94                 self._jumpAction.setStatusTip("Search for a unit and jump straight to it")
95                 self._jumpAction.setToolTip("Search for a unit and jump straight to it")
96                 self._jumpAction.setShortcut(QtGui.QKeySequence("CTRL+j"))
97                 self._jumpAction.triggered.connect(self._on_jump_start)
98
99                 self._recentAction = QtGui.QAction(None)
100                 self._recentAction.setText("Recent Units")
101                 self._recentAction.setStatusTip("View the recent units")
102                 self._recentAction.setToolTip("View the recent units")
103                 self._recentAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
104                 self._recentAction.triggered.connect(self._on_recent_start)
105
106                 self._fullscreenAction = QtGui.QAction(None)
107                 self._fullscreenAction.setText("Fullscreen")
108                 self._fullscreenAction.setCheckable(True)
109                 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
110                 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
111
112                 self._showFavoritesAction = QtGui.QAction(None)
113                 self._showFavoritesAction.setCheckable(True)
114                 self._showFavoritesAction.setText("Favorites Only")
115
116                 self._logAction = QtGui.QAction(None)
117                 self._logAction.setText("Log")
118                 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
119                 self._logAction.triggered.connect(self._on_log)
120
121                 self._quitAction = QtGui.QAction(None)
122                 self._quitAction.setText("Quit")
123                 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
124                 self._quitAction.triggered.connect(self._on_quit)
125
126                 self._app.lastWindowClosed.connect(self._on_app_quit)
127                 self.load_settings()
128
129                 self.request_category()
130                 if self._recent:
131                         self._catWindow.select_category(self._recent[-1][0])
132
133         def request_category(self):
134                 if self._catWindow is not None:
135                         self._catWindow.close()
136                         self._catWindow = None
137                 if self._condensedAction.isChecked():
138                         self._catWindow = QuickConvert(None, self)
139                         self._catWindow.window.destroyed.connect(lambda obj = None: self._on_child_close("_catWindow", obj))
140                 else:
141                         self._catWindow = CategoryWindow(None, self)
142                         self._catWindow.window.destroyed.connect(lambda obj = None: self._on_child_close("_catWindow", obj))
143                 return self._catWindow
144
145         def search_units(self):
146                 self._close_windows()
147                 self._jumpWindow = QuickJump(None, self)
148                 self._jumpWindow.window.destroyed.connect(lambda obj = None: self._on_child_close("_jumpWindow", obj))
149                 return self._jumpWindow
150
151         def show_recent(self):
152                 self._close_windows()
153                 self._recentWindow = Recent(None, self)
154                 self._recentWindow.window.destroyed.connect(lambda obj = None: self._on_child_close("_recentWindow", obj))
155                 return self._recentWindow
156
157         def add_recent(self, categoryName, unitName):
158                 catUnit = categoryName, unitName
159                 try:
160                         self._recent.remove(catUnit)
161                 except ValueError:
162                         pass # ignore if its not already in the recent history
163                 assert catUnit not in self._recent
164                 self._recent.append(catUnit)
165
166         def get_recent_unit(self, categoryName, fromMostRecent = 0):
167                 recentUnitName = ""
168                 for catName, unitName in reversed(self._recent):
169                         if catName == categoryName:
170                                 recentUnitName = unitName
171                                 if fromMostRecent <= 0:
172                                         break
173                                 else:
174                                         fromMostRecent -= 1
175                 return recentUnitName
176
177         def get_recent(self):
178                 return reversed(self._recent)
179
180         @property
181         def hiddenCategories(self):
182                 return self._hiddenCategories
183
184         def get_hidden_units(self, categoryName):
185                 try:
186                         return self._hiddenUnits[categoryName]
187                 except KeyError:
188                         self._hiddenUnits[categoryName] = set()
189                         return self._hiddenUnits[categoryName]
190
191         def load_settings(self):
192                 try:
193                         with open(constants._user_settings_, "r") as settingsFile:
194                                 settings = simplejson.load(settingsFile)
195                 except IOError, e:
196                         _moduleLogger.info("No settings")
197                         settings = {}
198                 except ValueError:
199                         _moduleLogger.info("Settings were corrupt")
200                         settings = {}
201
202                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
203
204                 recent = settings.get("recent", self._recent)
205                 for category, unit in recent:
206                         self.add_recent(category, unit)
207
208                 self._hiddenCategories = set(settings.get("hiddenCategories", set()))
209                 self._hiddenUnits = dict(
210                         (catName, set(units))
211                         for (catName, units) in settings.get("hiddenUnits", {}).iteritems()
212                 )
213
214                 self._showFavoritesAction.setChecked(settings.get("showFavorites", True))
215
216                 self._condensedAction.setChecked(settings.get("useQuick", self._condensedAction.isChecked()))
217
218         def save_settings(self):
219                 settings = {
220                         "isFullScreen": self._fullscreenAction.isChecked(),
221                         "recent": self._recent,
222                         "hiddenCategories": list(self._hiddenCategories),
223                         "hiddenUnits": dict(
224                                 (catName, list(units))
225                                 for (catName, units) in self._hiddenUnits.iteritems()
226                         ),
227                         "showFavorites": self._showFavoritesAction.isChecked(),
228                         "useQuick": self._condensedAction.isChecked(),
229                 }
230                 with open(constants._user_settings_, "w") as settingsFile:
231                         simplejson.dump(settings, settingsFile)
232
233         @property
234         def appIconPath(self):
235                 return self._appIconPath
236
237         @property
238         def jumpAction(self):
239                 return self._jumpAction
240
241         @property
242         def recentAction(self):
243                 return self._recentAction
244
245         @property
246         def fullscreenAction(self):
247                 return self._fullscreenAction
248
249         @property
250         def condensedAction(self):
251                 return self._condensedAction
252
253         @property
254         def logAction(self):
255                 return self._logAction
256
257         @property
258         def quitAction(self):
259                 return self._quitAction
260
261         @property
262         def showFavoritesAction(self):
263                 return self._showFavoritesAction
264
265         def _walk_children(self):
266                 if self._catWindow is not None:
267                         yield self._catWindow
268                 if self._jumpWindow is not None:
269                         yield self._jumpWindow
270                 if self._recentWindow is not None:
271                         yield self._recentWindow
272
273         def _close_windows(self):
274                 for window in self._walk_children():
275                         window.close()
276                 self._catWindow = None
277                 self._jumpWindow = None
278                 self._recentWindow = None
279
280         @misc_utils.log_exception(_moduleLogger)
281         def _on_app_quit(self, checked = False):
282                 self.save_settings()
283
284         @misc_utils.log_exception(_moduleLogger)
285         def _on_child_close(self, name, obj = None):
286                 if not hasattr(self, name):
287                         _moduleLogger.info("Something weird going on when we don't have a %s" % name)
288                         return
289                 setattr(self, name, None)
290
291         @misc_utils.log_exception(_moduleLogger)
292         def _on_toggle_fullscreen(self, checked = False):
293                 for window in self._walk_children():
294                         window.set_fullscreen(checked)
295
296         @misc_utils.log_exception(_moduleLogger)
297         def _on_condensed_start(self, checked = False):
298                 self.request_category()
299
300         @misc_utils.log_exception(_moduleLogger)
301         def _on_jump_start(self, checked = False):
302                 self.search_units()
303
304         @misc_utils.log_exception(_moduleLogger)
305         def _on_recent_start(self, checked = False):
306                 self.show_recent()
307
308         @misc_utils.log_exception(_moduleLogger)
309         def _on_log(self, checked = False):
310                 with open(constants._user_logpath_, "r") as f:
311                         logLines = f.xreadlines()
312                         log = "".join(logLines)
313                         self._clipboard.setText(log)
314
315         @misc_utils.log_exception(_moduleLogger)
316         def _on_quit(self, checked = False):
317                 self._close_windows()
318
319
320 class QuickJump(object):
321
322         MINIMAL_ENTRY = 3
323
324         def __init__(self, parent, app):
325                 self._app = app
326
327                 self._searchLabel = QtGui.QLabel("Search:")
328                 self._searchEntry = QtGui.QLineEdit("")
329                 self._searchEntry.textEdited.connect(self._on_search_edited)
330
331                 self._entryLayout = QtGui.QHBoxLayout()
332                 self._entryLayout.addWidget(self._searchLabel)
333                 self._entryLayout.addWidget(self._searchEntry)
334
335                 self._resultsBox = QtGui.QTreeWidget()
336                 self._resultsBox.setHeaderLabels(["Categories", "Units"])
337                 self._resultsBox.setHeaderHidden(True)
338                 if not IS_MAEMO:
339                         self._resultsBox.setAlternatingRowColors(True)
340                 self._resultsBox.itemClicked.connect(self._on_result_clicked)
341
342                 self._layout = QtGui.QVBoxLayout()
343                 self._layout.addLayout(self._entryLayout)
344                 self._layout.addWidget(self._resultsBox)
345
346                 centralWidget = QtGui.QWidget()
347                 centralWidget.setLayout(self._layout)
348
349                 self._window = QtGui.QMainWindow(parent)
350                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
351                 maeqt.set_autorient(self._window, True)
352                 maeqt.set_stackable(self._window, True)
353                 if parent is not None:
354                         self._window.setWindowModality(QtCore.Qt.WindowModal)
355                 self._window.setWindowTitle("%s - Quick Jump" % constants.__pretty_app_name__)
356                 self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
357                 self._window.setCentralWidget(centralWidget)
358
359                 self._closeWindowAction = QtGui.QAction(None)
360                 self._closeWindowAction.setText("Close")
361                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
362                 self._closeWindowAction.triggered.connect(self._on_close_window)
363
364                 if IS_MAEMO:
365                         self._window.addAction(self._closeWindowAction)
366                         self._window.addAction(self._app.quitAction)
367                         self._window.addAction(self._app.fullscreenAction)
368                 else:
369                         fileMenu = self._window.menuBar().addMenu("&Units")
370                         fileMenu.addAction(self._closeWindowAction)
371                         fileMenu.addAction(self._app.quitAction)
372
373                         viewMenu = self._window.menuBar().addMenu("&View")
374                         viewMenu.addAction(self._app.fullscreenAction)
375
376                 self._window.addAction(self._app.logAction)
377
378                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
379                 self._window.show()
380
381         @property
382         def window(self):
383                 return self._window
384
385         def close(self):
386                 self._window.close()
387
388         def set_fullscreen(self, isFullscreen):
389                 if isFullscreen:
390                         self._window.showFullScreen()
391                 else:
392                         self._window.showNormal()
393
394         @misc_utils.log_exception(_moduleLogger)
395         def _on_close_window(self, checked = True):
396                 self.close()
397
398         @misc_utils.log_exception(_moduleLogger)
399         def _on_result_clicked(self, item, columnIndex):
400                 categoryName = unicode(item.text(0))
401                 unitName = unicode(item.text(1))
402                 catWindow = self._app.request_category()
403                 unitsWindow = catWindow.select_category(categoryName)
404                 unitsWindow.select_unit(unitName)
405                 self.close()
406
407         @misc_utils.log_exception(_moduleLogger)
408         def _on_search_edited(self, *args):
409                 userInput = self._searchEntry.text()
410                 if len(userInput) <  self.MINIMAL_ENTRY:
411                         return
412
413                 self._resultsBox.clear()
414                 lowerInput = str(userInput).lower()
415                 for catIndex, category in enumerate(unit_data.UNIT_CATEGORIES):
416                         units = unit_data.get_units(category)
417                         for unitIndex, unit in enumerate(units):
418                                 loweredUnit = unit.lower()
419                                 if lowerInput in loweredUnit:
420                                         twi = QtGui.QTreeWidgetItem(self._resultsBox)
421                                         twi.setText(0, category)
422                                         twi.setText(1, unit)
423
424
425 class Recent(object):
426
427         def __init__(self, parent, app):
428                 self._app = app
429
430                 self._resultsBox = QtGui.QTreeWidget()
431                 self._resultsBox.setHeaderLabels(["Categories", "Units"])
432                 self._resultsBox.setHeaderHidden(True)
433                 if not IS_MAEMO:
434                         self._resultsBox.setAlternatingRowColors(True)
435                 self._resultsBox.itemClicked.connect(self._on_result_clicked)
436
437                 self._layout = QtGui.QVBoxLayout()
438                 self._layout.addWidget(self._resultsBox)
439
440                 centralWidget = QtGui.QWidget()
441                 centralWidget.setLayout(self._layout)
442
443                 self._window = QtGui.QMainWindow(parent)
444                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
445                 maeqt.set_autorient(self._window, True)
446                 maeqt.set_stackable(self._window, True)
447                 if parent is not None:
448                         self._window.setWindowModality(QtCore.Qt.WindowModal)
449                 self._window.setWindowTitle("%s - Recent" % constants.__pretty_app_name__)
450                 self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
451                 self._window.setCentralWidget(centralWidget)
452
453                 for cat, unit in self._app.get_recent():
454                         twi = QtGui.QTreeWidgetItem(self._resultsBox)
455                         twi.setText(0, cat)
456                         twi.setText(1, unit)
457
458                 self._closeWindowAction = QtGui.QAction(None)
459                 self._closeWindowAction.setText("Close")
460                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
461                 self._closeWindowAction.triggered.connect(self._on_close_window)
462
463                 if IS_MAEMO:
464                         self._window.addAction(self._closeWindowAction)
465                         self._window.addAction(self._app.quitAction)
466                         self._window.addAction(self._app.fullscreenAction)
467                 else:
468                         fileMenu = self._window.menuBar().addMenu("&Units")
469                         fileMenu.addAction(self._closeWindowAction)
470                         fileMenu.addAction(self._app.quitAction)
471
472                         viewMenu = self._window.menuBar().addMenu("&View")
473                         viewMenu.addAction(self._app.fullscreenAction)
474
475                 self._window.addAction(self._app.logAction)
476
477                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
478                 self._window.show()
479
480         @property
481         def window(self):
482                 return self._window
483
484         def close(self):
485                 self._window.close()
486
487         def set_fullscreen(self, isFullscreen):
488                 if isFullscreen:
489                         self._window.showFullScreen()
490                 else:
491                         self._window.showNormal()
492
493         @misc_utils.log_exception(_moduleLogger)
494         def _on_close_window(self, checked = True):
495                 self.close()
496
497         @misc_utils.log_exception(_moduleLogger)
498         def _on_result_clicked(self, item, columnIndex):
499                 categoryName = unicode(item.text(0))
500                 unitName = unicode(item.text(1))
501                 catWindow = self._app.request_category()
502                 unitsWindow = catWindow.select_category(categoryName)
503                 unitsWindow.select_unit(unitName)
504                 self.close()
505
506
507 class QuickConvert(object):
508
509         def __init__(self, parent, app):
510                 self._app = app
511                 self._categoryName = ""
512                 self._inputUnitName = ""
513                 self._outputUnitName = ""
514                 self._unitNames = []
515                 self._favoritesWindow = None
516
517                 self._inputUnitValue = QtGui.QLineEdit()
518                 self._inputUnitValue.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
519                 self._inputUnitValue.textEdited.connect(self._on_value_edited)
520                 self._inputUnitSymbol = QtGui.QLabel()
521
522                 self._outputUnitValue = QtGui.QLabel()
523                 self._outputUnitSymbol = QtGui.QLabel()
524
525                 self._conversionLayout = QtGui.QHBoxLayout()
526                 self._conversionLayout.addWidget(self._inputUnitValue)
527                 self._conversionLayout.addWidget(self._inputUnitSymbol)
528                 self._conversionLayout.addWidget(self._outputUnitValue)
529                 self._conversionLayout.addWidget(self._outputUnitSymbol)
530
531                 self._categoryView = QtGui.QTreeWidget()
532                 self._categoryView.setHeaderLabels(["Categories"])
533                 self._categoryView.setHeaderHidden(True)
534                 if not IS_MAEMO:
535                         self._categoryView.setAlternatingRowColors(True)
536                 self._categoryView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
537                 self._categoryView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
538                 for catName in unit_data.UNIT_CATEGORIES:
539                         twi = QtGui.QTreeWidgetItem(self._categoryView)
540                         twi.setText(0, catName)
541                 self._categorySelection = self._categoryView.selectionModel()
542                 self._categorySelection.selectionChanged.connect(self._on_category_selection_changed)
543
544                 self._inputView = QtGui.QTreeWidget()
545                 self._inputView.setHeaderLabels(["Input", "Name"])
546                 self._inputView.setHeaderHidden(True)
547                 self._inputView.header().hideSection(1)
548                 if not IS_MAEMO:
549                         self._inputView.setAlternatingRowColors(True)
550                 self._inputView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
551                 self._inputView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
552                 self._inputSelection = self._inputView.selectionModel()
553                 self._inputSelection.selectionChanged.connect(self._on_input_selection_changed)
554
555                 self._outputView = QtGui.QTreeWidget()
556                 self._outputView.setHeaderLabels(["Output", "Name"])
557                 self._outputView.setHeaderHidden(True)
558                 self._outputView.header().hideSection(1)
559                 if not IS_MAEMO:
560                         self._outputView.setAlternatingRowColors(True)
561                 self._outputView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
562                 self._outputView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
563                 self._outputWidgets = []
564                 self._outputSelection = self._outputView.selectionModel()
565                 self._outputSelection.selectionChanged.connect(self._on_output_selection_changed)
566
567                 self._selectionLayout = QtGui.QHBoxLayout()
568                 self._selectionLayout.addWidget(self._categoryView)
569                 self._selectionLayout.addWidget(self._inputView)
570                 self._selectionLayout.addWidget(self._outputView)
571
572                 self._layout = QtGui.QVBoxLayout()
573                 self._layout.addLayout(self._conversionLayout)
574                 self._layout.addLayout(self._selectionLayout)
575
576                 centralWidget = QtGui.QWidget()
577                 centralWidget.setLayout(self._layout)
578
579                 self._window = QtGui.QMainWindow(parent)
580                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
581                 maeqt.set_autorient(self._window, True)
582                 maeqt.set_stackable(self._window, True)
583                 if parent is not None:
584                         self._window.setWindowModality(QtCore.Qt.WindowModal)
585                 self._window.setWindowTitle("%s - Quick Convert" % (constants.__pretty_app_name__, ))
586                 self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
587                 self._window.setCentralWidget(centralWidget)
588
589                 self._chooseCatFavoritesAction = QtGui.QAction(None)
590                 self._chooseCatFavoritesAction.setText("Select Categories")
591                 self._chooseCatFavoritesAction.triggered.connect(self._on_choose_category_favorites)
592
593                 self._chooseUnitFavoritesAction = QtGui.QAction(None)
594                 self._chooseUnitFavoritesAction.setText("Select Units")
595                 self._chooseUnitFavoritesAction.triggered.connect(self._on_choose_unit_favorites)
596
597                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
598
599                 self._closeWindowAction = QtGui.QAction(None)
600                 self._closeWindowAction.setText("Close Window")
601                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
602                 self._closeWindowAction.triggered.connect(self._on_close_window)
603
604                 if IS_MAEMO:
605                         self._window.addAction(self._closeWindowAction)
606                         self._window.addAction(self._app.quitAction)
607                         self._window.addAction(self._app.fullscreenAction)
608
609                         fileMenu = self._window.menuBar().addMenu("&Units")
610                         fileMenu.addAction(self._chooseCatFavoritesAction)
611                         fileMenu.addAction(self._chooseUnitFavoritesAction)
612
613                         viewMenu = self._window.menuBar().addMenu("&View")
614                         viewMenu.addAction(self._app.showFavoritesAction)
615                         viewMenu.addAction(self._app.condensedAction)
616                         viewMenu.addSeparator()
617                         viewMenu.addAction(self._app.jumpAction)
618                         viewMenu.addAction(self._app.recentAction)
619                 else:
620                         fileMenu = self._window.menuBar().addMenu("&Units")
621                         fileMenu.addAction(self._chooseCatFavoritesAction)
622                         fileMenu.addAction(self._chooseUnitFavoritesAction)
623                         fileMenu.addAction(self._closeWindowAction)
624                         fileMenu.addAction(self._app.quitAction)
625
626                         viewMenu = self._window.menuBar().addMenu("&View")
627                         viewMenu.addAction(self._app.showFavoritesAction)
628                         viewMenu.addAction(self._app.condensedAction)
629                         viewMenu.addSeparator()
630                         viewMenu.addAction(self._app.jumpAction)
631                         viewMenu.addAction(self._app.recentAction)
632                         viewMenu.addSeparator()
633                         viewMenu.addAction(self._app.fullscreenAction)
634
635                 self._window.addAction(self._app.logAction)
636
637                 self._update_favorites()
638                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
639                 self._window.show()
640
641         @property
642         def window(self):
643                 return self._window
644
645         def close(self):
646                 self._window.close()
647
648         def set_fullscreen(self, isFullscreen):
649                 if isFullscreen:
650                         self._window.showFullScreen()
651                 else:
652                         self._window.showNormal()
653
654         def select_category(self, categoryName):
655                 self._inputUnitValue.setText("")
656                 self._inputView.clear()
657                 self._outputView.clear()
658                 self._categoryName = categoryName
659
660                 unitData = unit_data.UNIT_DESCRIPTIONS[categoryName]
661                 self._unitNames = list(unit_data.get_units(categoryName))
662                 self._unitNames.sort()
663                 for key in self._unitNames:
664                         conversion, unit, description = unitData[key]
665                         if not unit:
666                                 unit = key
667
668                         twi = QtGui.QTreeWidgetItem(self._inputView)
669                         twi.setText(0, unit)
670                         twi.setText(1, key)
671
672                         twi = QtGui.QTreeWidgetItem(self._outputView)
673                         twi.setText(0, unit)
674                         twi.setText(1, key)
675
676                 i = unit_data.UNIT_CATEGORIES.index(categoryName)
677                 rootIndex = self._categoryView.rootIndex()
678                 currentIndex = self._categoryView.model().index(i, 0, rootIndex)
679                 self._categoryView.scrollTo(currentIndex)
680
681                 defaultInputUnitName = self._app.get_recent_unit(categoryName)
682                 if defaultInputUnitName:
683                         self.select_input(defaultInputUnitName)
684                         defaultOutputUnitName = self._app.get_recent_unit(categoryName, 1)
685                         assert defaultOutputUnitName
686                         self.select_output(defaultOutputUnitName)
687
688                 return self
689
690         def select_unit(self, name):
691                 self.select_input(name)
692
693         def select_input(self, name):
694                 self._app.add_recent(self._categoryName, name)
695                 self._inputUnitName = name
696
697                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
698                 conversion, unit, description = unitData[name]
699
700                 self._inputUnitSymbol.setText(unit if unit else name)
701
702                 i = self._unitNames.index(name)
703                 rootIndex = self._inputView.rootIndex()
704                 currentIndex = self._inputView.model().index(i, 0, rootIndex)
705                 self._inputView.scrollTo(currentIndex)
706
707         def select_output(self, name):
708                 # Add the output to recent but don't make things weird by making it the most recent
709                 self._app.add_recent(self._categoryName, name)
710                 self._app.add_recent(self._categoryName, self._inputUnitName)
711                 self._outputUnitName = name
712
713                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
714                 conversion, unit, description = unitData[name]
715
716                 self._outputUnitSymbol.setText(unit if unit else name)
717
718                 i = self._unitNames.index(name)
719                 rootIndex = self._outputView.rootIndex()
720                 currentIndex = self._outputView.model().index(i, 0, rootIndex)
721                 self._outputView.scrollTo(currentIndex)
722
723         def _sanitize_value(self, userEntry):
724                 if self._categoryName == "Computer Numbers":
725                         if userEntry == '':
726                                 value = '0'
727                         else:
728                                 value = userEntry
729                 else:
730                         if userEntry == '':
731                                 value = 0.0
732                         else:
733                                 value = float(userEntry)
734                 return value
735
736         def _update_favorites(self):
737                 if self._app.showFavoritesAction.isChecked():
738                         assert self._categoryView.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
739                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
740                                 if catName in self._app.hiddenCategories:
741                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), True)
742                                 else:
743                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
744
745                         for i, unitName in enumerate(self._unitNames):
746                                 if unitName in self._app.get_hidden_units(self._categoryName):
747                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), True)
748                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), True)
749                                 else:
750                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
751                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
752                 else:
753                         for i in xrange(self._categoryView.topLevelItemCount()):
754                                 self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
755
756                         for i in xrange(len(self._unitNames)):
757                                 self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
758                                 self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
759
760         @misc_utils.log_exception(_moduleLogger)
761         def _on_close_window(self, checked = True):
762                 self.close()
763
764         @misc_utils.log_exception(_moduleLogger)
765         def _on_show_favorites(self, checked = True):
766                 if checked:
767                         assert self._categoryView.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
768                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
769                                 if catName in self._app.hiddenCategories:
770                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), True)
771
772                         for i, unitName in enumerate(self._unitNames):
773                                 if unitName in self._app.get_hidden_units(self._categoryName):
774                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), True)
775                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), True)
776                 else:
777                         for i in xrange(self._categoryView.topLevelItemCount()):
778                                 self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
779
780                         for i in xrange(len(self._unitNames)):
781                                 self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
782                                 self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
783
784         @misc_utils.log_exception(_moduleLogger)
785         def _on_choose_category_favorites(self, obj = None):
786                 assert self._favoritesWindow is None
787                 self._favoritesWindow = FavoritesWindow(
788                         self._window,
789                         self._app,
790                         unit_data.UNIT_CATEGORIES,
791                         self._app.hiddenCategories
792                 )
793                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
794                 return self._favoritesWindow
795
796         @misc_utils.log_exception(_moduleLogger)
797         def _on_choose_unit_favorites(self, obj = None):
798                 assert self._favoritesWindow is None
799                 self._favoritesWindow = FavoritesWindow(
800                         self._window,
801                         self._app,
802                         unit_data.get_units(self._categoryName),
803                         self._app.get_hidden_units(self._categoryName)
804                 )
805                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
806                 return self._favoritesWindow
807
808         @misc_utils.log_exception(_moduleLogger)
809         def _on_close_favorites(self, obj = None):
810                 self._favoritesWindow = None
811                 self._update_favorites()
812
813         @misc_utils.log_exception(_moduleLogger)
814         def _on_value_edited(self, *args):
815                 assert self._categoryName
816                 assert self._inputUnitName
817                 assert self._outputUnitName
818
819                 userInput = str(self._inputUnitValue.text())
820                 value = self._sanitize_value(userInput)
821
822                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
823                 inputConversion, _, _ = unitData[self._inputUnitName]
824                 outputConversion, _, _ = unitData[self._outputUnitName]
825
826                 func, arg = inputConversion
827                 base = func.to_base(value, arg)
828
829                 func, arg = outputConversion
830                 newValue = func.from_base(base, arg)
831                 self._outputUnitValue.setText(str(newValue))
832
833         @misc_utils.log_exception(_moduleLogger)
834         def _on_category_selection_changed(self, selected, deselected):
835                 selectedNames = [
836                         str(item.text(0))
837                         for item in self._categoryView.selectedItems()
838                 ]
839                 assert len(selectedNames) == 1
840                 self.select_category(selectedNames[0])
841
842         @misc_utils.log_exception(_moduleLogger)
843         def _on_input_selection_changed(self, selected, deselected):
844                 selectedNames = [
845                         str(item.text(1))
846                         for item in self._inputView.selectedItems()
847                 ]
848                 if selectedNames:
849                         assert len(selectedNames) == 1
850                         name = selectedNames[0]
851                         self.select_input(name)
852                 else:
853                         pass
854
855         @misc_utils.log_exception(_moduleLogger)
856         def _on_output_selection_changed(self, selected, deselected):
857                 selectedNames = [
858                         str(item.text(1))
859                         for item in self._outputView.selectedItems()
860                 ]
861                 if selectedNames:
862                         assert len(selectedNames) == 1
863                         name = selectedNames[0]
864                         self.select_output(name)
865                 else:
866                         pass
867
868
869 class FavoritesWindow(object):
870
871         def __init__(self, parent, app, source, hidden):
872                 self._app = app
873                 self._source = list(source)
874                 self._hidden = hidden
875
876                 self._categories = QtGui.QTreeWidget()
877                 self._categories.setHeaderLabels(["Categories"])
878                 self._categories.setHeaderHidden(True)
879                 if not IS_MAEMO:
880                         self._categories.setAlternatingRowColors(True)
881                 self._categories.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
882                 self._categories.setSelectionMode(QtGui.QAbstractItemView.MultiSelection)
883                 self._childWidgets = []
884                 for catName in self._source:
885                         twi = QtGui.QTreeWidgetItem(self._categories)
886                         twi.setText(0, catName)
887                         self._childWidgets.append(twi)
888                         if catName not in self._hidden:
889                                 self._categories.setItemSelected(twi, True)
890                 self._selection = self._categories.selectionModel()
891                 self._selection.selectionChanged.connect(self._on_selection_changed)
892
893                 self._allButton = QtGui.QPushButton("All")
894                 self._allButton.clicked.connect(self._on_select_all)
895                 self._invertButton = QtGui.QPushButton("Invert")
896                 self._invertButton.clicked.connect(self._on_invert_selection)
897                 self._noneButton = QtGui.QPushButton("None")
898                 self._noneButton.clicked.connect(self._on_select_none)
899
900                 self._buttonLayout = QtGui.QHBoxLayout()
901                 self._buttonLayout.addWidget(self._allButton)
902                 self._buttonLayout.addWidget(self._invertButton)
903                 self._buttonLayout.addWidget(self._noneButton)
904
905                 self._layout = QtGui.QVBoxLayout()
906                 self._layout.addWidget(self._categories)
907                 self._layout.addLayout(self._buttonLayout)
908
909                 centralWidget = QtGui.QWidget()
910                 centralWidget.setLayout(self._layout)
911
912                 self._window = QtGui.QMainWindow(parent)
913                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
914                 maeqt.set_autorient(self._window, True)
915                 maeqt.set_stackable(self._window, True)
916                 if parent is not None:
917                         self._window.setWindowModality(QtCore.Qt.WindowModal)
918                 self._window.setWindowTitle("%s - Favorites" % constants.__pretty_app_name__)
919                 self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
920                 self._window.setCentralWidget(centralWidget)
921
922                 self._closeWindowAction = QtGui.QAction(None)
923                 self._closeWindowAction.setText("Close")
924                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
925                 self._closeWindowAction.triggered.connect(self._on_close_window)
926
927                 if IS_MAEMO:
928                         self._window.addAction(self._closeWindowAction)
929                         self._window.addAction(self._app.quitAction)
930                         self._window.addAction(self._app.fullscreenAction)
931                 else:
932                         fileMenu = self._window.menuBar().addMenu("&Units")
933                         fileMenu.addAction(self._closeWindowAction)
934                         fileMenu.addAction(self._app.quitAction)
935
936                         viewMenu = self._window.menuBar().addMenu("&View")
937                         viewMenu.addAction(self._app.fullscreenAction)
938
939                 self._window.addAction(self._app.logAction)
940
941                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
942                 self._window.show()
943
944         @property
945         def window(self):
946                 return self._window
947
948         def close(self):
949                 self._window.close()
950
951         def set_fullscreen(self, isFullscreen):
952                 if isFullscreen:
953                         self._window.showFullScreen()
954                 else:
955                         self._window.showNormal()
956
957         @misc_utils.log_exception(_moduleLogger)
958         def _on_select_all(self, checked = False):
959                 for child in self._childWidgets:
960                         self._categories.setItemSelected(child, True)
961
962         @misc_utils.log_exception(_moduleLogger)
963         def _on_invert_selection(self, checked = False):
964                 for child in self._childWidgets:
965                         isSelected = self._categories.isItemSelected(child)
966                         self._categories.setItemSelected(child, not isSelected)
967
968         @misc_utils.log_exception(_moduleLogger)
969         def _on_select_none(self, checked = False):
970                 for child in self._childWidgets:
971                         self._categories.setItemSelected(child, False)
972
973         @misc_utils.log_exception(_moduleLogger)
974         def _on_selection_changed(self, selected, deselected):
975                 self._hidden.clear()
976                 selectedNames = set(
977                         str(item.text(0))
978                         for item in self._categories.selectedItems()
979                 )
980                 for name in self._source:
981                         if name not in selectedNames:
982                                 self._hidden.add(name)
983
984         @misc_utils.log_exception(_moduleLogger)
985         def _on_close_window(self, checked = True):
986                 self.close()
987
988
989 class CategoryWindow(object):
990
991         def __init__(self, parent, app):
992                 self._app = app
993                 self._unitWindow = None
994                 self._favoritesWindow = None
995
996                 self._categories = QtGui.QTreeWidget()
997                 self._categories.setHeaderLabels(["Categories"])
998                 self._categories.itemClicked.connect(self._on_category_clicked)
999                 self._categories.setHeaderHidden(True)
1000                 if not IS_MAEMO:
1001                         self._categories.setAlternatingRowColors(True)
1002                 for catName in unit_data.UNIT_CATEGORIES:
1003                         twi = QtGui.QTreeWidgetItem(self._categories)
1004                         twi.setText(0, catName)
1005
1006                 self._layout = QtGui.QVBoxLayout()
1007                 self._layout.addWidget(self._categories)
1008
1009                 centralWidget = QtGui.QWidget()
1010                 centralWidget.setLayout(self._layout)
1011
1012                 self._window = QtGui.QMainWindow(parent)
1013                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1014                 maeqt.set_autorient(self._window, True)
1015                 maeqt.set_stackable(self._window, True)
1016                 if parent is not None:
1017                         self._window.setWindowModality(QtCore.Qt.WindowModal)
1018                 self._window.setWindowTitle("%s - Categories" % constants.__pretty_app_name__)
1019                 self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
1020                 self._window.setCentralWidget(centralWidget)
1021
1022                 self._chooseFavoritesAction = QtGui.QAction(None)
1023                 self._chooseFavoritesAction.setText("Select Favorites")
1024                 self._chooseFavoritesAction.setShortcut(QtGui.QKeySequence("CTRL+f"))
1025                 self._chooseFavoritesAction.triggered.connect(self._on_choose_favorites)
1026
1027                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
1028
1029                 self._closeWindowAction = QtGui.QAction(None)
1030                 self._closeWindowAction.setText("Close")
1031                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
1032                 self._closeWindowAction.triggered.connect(self._on_close_window)
1033
1034                 if IS_MAEMO:
1035                         fileMenu = self._window.menuBar().addMenu("&Units")
1036                         fileMenu.addAction(self._chooseFavoritesAction)
1037
1038                         viewMenu = self._window.menuBar().addMenu("&View")
1039                         viewMenu.addAction(self._app.showFavoritesAction)
1040                         viewMenu.addAction(self._app.condensedAction)
1041                         viewMenu.addSeparator()
1042                         viewMenu.addAction(self._app.jumpAction)
1043                         viewMenu.addAction(self._app.recentAction)
1044
1045                         self._window.addAction(self._closeWindowAction)
1046                         self._window.addAction(self._app.quitAction)
1047                         self._window.addAction(self._app.fullscreenAction)
1048                 else:
1049                         fileMenu = self._window.menuBar().addMenu("&Units")
1050                         fileMenu.addAction(self._chooseFavoritesAction)
1051                         fileMenu.addAction(self._closeWindowAction)
1052                         fileMenu.addAction(self._app.quitAction)
1053
1054                         viewMenu = self._window.menuBar().addMenu("&View")
1055                         viewMenu.addAction(self._app.showFavoritesAction)
1056                         viewMenu.addAction(self._app.condensedAction)
1057                         viewMenu.addSeparator()
1058                         viewMenu.addAction(self._app.jumpAction)
1059                         viewMenu.addAction(self._app.recentAction)
1060                         viewMenu.addSeparator()
1061                         viewMenu.addAction(self._app.fullscreenAction)
1062
1063                 self._window.addAction(self._app.logAction)
1064
1065                 self._update_favorites()
1066                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
1067                 self._window.show()
1068
1069         @property
1070         def window(self):
1071                 return self._window
1072
1073         def walk_children(self):
1074                 if self._unitWindow is not None:
1075                         yield self._unitWindow
1076                 if self._favoritesWindow is not None:
1077                         yield self._favoritesWindow
1078
1079         def close(self):
1080                 for child in self.walk_children():
1081                         child.close()
1082                 self._window.close()
1083
1084         def select_category(self, categoryName):
1085                 for child in self.walk_children():
1086                         child.close()
1087                 self._unitWindow = UnitWindow(self._window, categoryName, self._app)
1088                 self._unitWindow.window.destroyed.connect(self._on_child_close)
1089
1090                 i = unit_data.UNIT_CATEGORIES.index(categoryName)
1091                 rootIndex = self._categories.rootIndex()
1092                 currentIndex = self._categories.model().index(i, 0, rootIndex)
1093                 self._categories.scrollTo(currentIndex)
1094                 return self._unitWindow
1095
1096         def set_fullscreen(self, isFullscreen):
1097                 if isFullscreen:
1098                         self._window.showFullScreen()
1099                 else:
1100                         self._window.showNormal()
1101                 for child in self.walk_children():
1102                         child.set_fullscreen(isFullscreen)
1103
1104         def _update_favorites(self):
1105                 if self._app.showFavoritesAction.isChecked():
1106                         assert self._categories.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
1107                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
1108                                 if catName in self._app.hiddenCategories:
1109                                         self._categories.setRowHidden(i, self._categories.rootIndex(), True)
1110                                 else:
1111                                         self._categories.setRowHidden(i, self._categories.rootIndex(), False)
1112                 else:
1113                         for i in xrange(self._categories.topLevelItemCount()):
1114                                 self._categories.setRowHidden(i, self._categories.rootIndex(), False)
1115
1116         @misc_utils.log_exception(_moduleLogger)
1117         def _on_show_favorites(self, checked = True):
1118                 if checked:
1119                         assert self._categories.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
1120                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
1121                                 if catName in self._app.hiddenCategories:
1122                                         self._categories.setRowHidden(i, self._categories.rootIndex(), True)
1123                 else:
1124                         for i in xrange(self._categories.topLevelItemCount()):
1125                                 self._categories.setRowHidden(i, self._categories.rootIndex(), False)
1126
1127         @misc_utils.log_exception(_moduleLogger)
1128         def _on_choose_favorites(self, obj = None):
1129                 assert self._favoritesWindow is None
1130                 self._favoritesWindow = FavoritesWindow(
1131                         self._window,
1132                         self._app,
1133                         unit_data.UNIT_CATEGORIES,
1134                         self._app.hiddenCategories
1135                 )
1136                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
1137                 return self._favoritesWindow
1138
1139         @misc_utils.log_exception(_moduleLogger)
1140         def _on_close_favorites(self, obj = None):
1141                 self._favoritesWindow = None
1142                 self._update_favorites()
1143
1144         @misc_utils.log_exception(_moduleLogger)
1145         def _on_child_close(self, obj = None):
1146                 self._unitWindow = None
1147
1148         @misc_utils.log_exception(_moduleLogger)
1149         def _on_close_window(self, checked = True):
1150                 self.close()
1151
1152         @misc_utils.log_exception(_moduleLogger)
1153         def _on_category_clicked(self, item, columnIndex):
1154                 categoryName = unicode(item.text(0))
1155                 self.select_category(categoryName)
1156
1157
1158 class UnitData(object):
1159
1160         HEADERS = ["Name", "Value", "", "Unit"]
1161         ALIGNMENT = [QtCore.Qt.AlignLeft, QtCore.Qt.AlignRight, QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft]
1162         NAME_COLUMN = 0
1163         VALUE_COLUMN_0 = 1
1164         VALUE_COLUMN_1 = 2
1165         UNIT_COLUMN = 3
1166
1167         def __init__(self, name, unit, description, conversion):
1168                 self._name = name
1169                 self._unit = unit
1170                 self._description = description
1171                 self._conversion = conversion
1172
1173                 self._value = 0.0
1174                 self._integerDisplay, self._fractionalDisplay = split_number(self._value)
1175
1176         @property
1177         def name(self):
1178                 return self._name
1179
1180         @property
1181         def value(self):
1182                 return self._value
1183
1184         def update_value(self, newValue):
1185                 self._value = newValue
1186                 self._integerDisplay, self._fractionalDisplay = split_number(newValue)
1187
1188         @property
1189         def unit(self):
1190                 return self._unit
1191
1192         @property
1193         def conversion(self):
1194                 return self._conversion
1195
1196         def data(self, column):
1197                 try:
1198                         return [self._name, self._integerDisplay, self._fractionalDisplay, self._unit][column]
1199                 except IndexError:
1200                         return None
1201
1202
1203 class UnitModel(QtCore.QAbstractItemModel):
1204
1205         def __init__(self, categoryName, parent=None):
1206                 super(UnitModel, self).__init__(parent)
1207                 self._categoryName = categoryName
1208                 self._unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
1209
1210                 self._children = []
1211                 for key in unit_data.get_units(self._categoryName):
1212                         conversion, unit, description = self._unitData[key]
1213                         self._children.append(UnitData(key, unit, description, conversion))
1214                 self._sortSettings = None
1215
1216         @misc_utils.log_exception(_moduleLogger)
1217         def columnCount(self, parent):
1218                 if parent.isValid():
1219                         return 0
1220                 else:
1221                         return len(UnitData.HEADERS)
1222
1223         @misc_utils.log_exception(_moduleLogger)
1224         def data(self, index, role):
1225                 if not index.isValid():
1226                         return None
1227                 elif role == QtCore.Qt.TextAlignmentRole:
1228                         return UnitData.ALIGNMENT[index.column()]
1229                 elif role != QtCore.Qt.DisplayRole:
1230                         return None
1231
1232                 item = index.internalPointer()
1233                 if isinstance(item, UnitData):
1234                         return item.data(index.column())
1235                 elif item is UnitData.HEADERS:
1236                         return item[index.column()]
1237
1238         @misc_utils.log_exception(_moduleLogger)
1239         def sort(self, column, order = QtCore.Qt.AscendingOrder):
1240                 self._sortSettings = column, order
1241                 isReverse = order == QtCore.Qt.AscendingOrder
1242                 if column == UnitData.NAME_COLUMN:
1243                         key_func = lambda item: item.name
1244                 elif column in [UnitData.VALUE_COLUMN_0, UnitData.VALUE_COLUMN_1]:
1245                         key_func = lambda item: item.value
1246                 elif column == UnitData.UNIT_COLUMN:
1247                         key_func = lambda item: item.unit
1248                 self._children.sort(key=key_func, reverse = isReverse)
1249
1250                 self._all_changed()
1251
1252         @misc_utils.log_exception(_moduleLogger)
1253         def flags(self, index):
1254                 if not index.isValid():
1255                         return QtCore.Qt.NoItemFlags
1256
1257                 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
1258
1259         @misc_utils.log_exception(_moduleLogger)
1260         def headerData(self, section, orientation, role):
1261                 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
1262                         return UnitData.HEADERS[section]
1263
1264                 return None
1265
1266         @misc_utils.log_exception(_moduleLogger)
1267         def index(self, row, column, parent):
1268                 if not self.hasIndex(row, column, parent):
1269                         return QtCore.QModelIndex()
1270
1271                 if parent.isValid():
1272                         return QtCore.QModelIndex()
1273
1274                 parentItem = UnitData.HEADERS
1275                 childItem = self._children[row]
1276                 if childItem:
1277                         return self.createIndex(row, column, childItem)
1278                 else:
1279                         return QtCore.QModelIndex()
1280
1281         @misc_utils.log_exception(_moduleLogger)
1282         def parent(self, index):
1283                 if not index.isValid():
1284                         return QtCore.QModelIndex()
1285
1286                 childItem = index.internalPointer()
1287                 if isinstance(childItem, UnitData):
1288                         return QtCore.QModelIndex()
1289                 elif childItem is UnitData.HEADERS:
1290                         return None
1291
1292         @misc_utils.log_exception(_moduleLogger)
1293         def rowCount(self, parent):
1294                 if 0 < parent.column():
1295                         return 0
1296
1297                 if not parent.isValid():
1298                         return len(self._children)
1299                 else:
1300                         return len(self._children)
1301
1302         def get_unit(self, index):
1303                 assert 0 <= index
1304                 return self._children[index]
1305
1306         def get_unit_names(self):
1307                 for child in self._children:
1308                         yield child.name
1309
1310         def index_unit(self, unitName):
1311                 for i, child in enumerate(self._children):
1312                         if child.name == unitName:
1313                                 return i
1314                 else:
1315                         raise RuntimeError("Unit not found")
1316
1317         def update_values(self, fromIndex, userInput):
1318                 value = self._sanitize_value(userInput)
1319                 func, arg = self._children[fromIndex].conversion
1320                 base = func.to_base(value, arg)
1321                 for i, child in enumerate(self._children):
1322                         if i == fromIndex:
1323                                 continue
1324                         func, arg = child.conversion
1325                         newValue = func.from_base(base, arg)
1326                         child.update_value(newValue)
1327
1328                 if (
1329                         self._sortSettings is not None and
1330                         self._sortSettings[0]  in [UnitData.VALUE_COLUMN_0, UnitData.VALUE_COLUMN_1]
1331                 ):
1332                         self.sort(*self._sortSettings)
1333                         self._all_changed()
1334                 else:
1335                         self._values_changed()
1336
1337         def __len__(self):
1338                 return len(self._children)
1339
1340         def _values_changed(self):
1341                 topLeft = self.createIndex(0, UnitData.VALUE_COLUMN_0, self._children[0])
1342                 bottomRight = self.createIndex(len(self._children)-1, UnitData.VALUE_COLUMN_1, self._children[-1])
1343                 self.dataChanged.emit(topLeft, bottomRight)
1344
1345         def _all_changed(self):
1346                 topLeft = self.createIndex(0, 0, self._children[0])
1347                 bottomRight = self.createIndex(len(self._children)-1, len(UnitData.HEADERS)-1, self._children[-1])
1348                 self.dataChanged.emit(topLeft, bottomRight)
1349
1350         def _sanitize_value(self, userEntry):
1351                 if self._categoryName == "Computer Numbers":
1352                         if userEntry == '':
1353                                 value = '0'
1354                         else:
1355                                 value = userEntry
1356                 else:
1357                         if userEntry == '':
1358                                 value = 0.0
1359                         else:
1360                                 value = float(userEntry)
1361                 return value
1362
1363
1364 class UnitWindow(object):
1365
1366         def __init__(self, parent, category, app):
1367                 self._app = app
1368                 self._categoryName = category
1369                 self._selectedIndex = 0
1370                 self._favoritesWindow = None
1371
1372                 self._selectedUnitName = QtGui.QLabel()
1373                 self._selectedUnitValue = QtGui.QLineEdit()
1374                 self._selectedUnitValue.textEdited.connect(self._on_value_edited)
1375                 maeqt.mark_numbers_preferred(self._selectedUnitValue)
1376                 self._selectedUnitSymbol = QtGui.QLabel()
1377
1378                 self._selectedUnitLayout = QtGui.QHBoxLayout()
1379                 self._selectedUnitLayout.addWidget(self._selectedUnitName)
1380                 self._selectedUnitLayout.addWidget(self._selectedUnitValue)
1381                 self._selectedUnitLayout.addWidget(self._selectedUnitSymbol)
1382
1383                 self._unitsModel = UnitModel(self._categoryName)
1384                 self._unitsView = QtGui.QTreeView()
1385                 self._unitsView.setModel(self._unitsModel)
1386                 self._unitsView.clicked.connect(self._on_unit_clicked)
1387                 self._unitsView.setUniformRowHeights(True)
1388                 self._unitsView.setSortingEnabled(True)
1389                 self._unitsView.setTextElideMode(QtCore.Qt.ElideNone)
1390                 self._unitsView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
1391                 if not IS_MAEMO:
1392                         self._unitsView.setAlternatingRowColors(True)
1393                 if True:
1394                         self._unitsView.setHeaderHidden(True)
1395
1396                 viewHeader = self._unitsView.header()
1397                 viewHeader.setSortIndicatorShown(True)
1398                 viewHeader.setClickable(True)
1399
1400                 viewHeader.setResizeMode(UnitData.NAME_COLUMN, QtGui.QHeaderView.ResizeToContents)
1401                 viewHeader.setResizeMode(UnitData.VALUE_COLUMN_0, QtGui.QHeaderView.ResizeToContents)
1402                 viewHeader.setResizeMode(UnitData.VALUE_COLUMN_1, QtGui.QHeaderView.ResizeToContents)
1403                 viewHeader.setResizeMode(UnitData.UNIT_COLUMN, QtGui.QHeaderView.ResizeToContents)
1404                 viewHeader.setStretchLastSection(False)
1405
1406                 # Trying to make things faster by locking in the initial size of the immutable columns
1407                 nameSize = viewHeader.sectionSize(UnitData.NAME_COLUMN)
1408                 viewHeader.setResizeMode(UnitData.NAME_COLUMN, QtGui.QHeaderView.Fixed)
1409                 viewHeader.resizeSection(UnitData.NAME_COLUMN, nameSize)
1410                 unitSize = viewHeader.sectionSize(UnitData.UNIT_COLUMN)
1411                 viewHeader.setResizeMode(UnitData.UNIT_COLUMN, QtGui.QHeaderView.Fixed)
1412                 viewHeader.resizeSection(UnitData.UNIT_COLUMN, unitSize)
1413
1414                 self._layout = QtGui.QVBoxLayout()
1415                 self._layout.addLayout(self._selectedUnitLayout)
1416                 self._layout.addWidget(self._unitsView)
1417
1418                 centralWidget = QtGui.QWidget()
1419                 centralWidget.setLayout(self._layout)
1420
1421                 self._window = QtGui.QMainWindow(parent)
1422                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1423                 maeqt.set_autorient(self._window, True)
1424                 maeqt.set_stackable(self._window, True)
1425                 if parent is not None:
1426                         self._window.setWindowModality(QtCore.Qt.WindowModal)
1427                 self._window.setWindowTitle("%s - %s" % (constants.__pretty_app_name__, category))
1428                 self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
1429                 self._window.setCentralWidget(centralWidget)
1430
1431                 defaultUnitName = self._app.get_recent_unit(self._categoryName)
1432                 if defaultUnitName:
1433                         self.select_unit(defaultUnitName)
1434                 else:
1435                         self._select_unit(0)
1436                 self._unitsModel.sort(UnitData.NAME_COLUMN)
1437
1438                 self._sortActionGroup = QtGui.QActionGroup(None)
1439                 self._sortByNameAction = QtGui.QAction(self._sortActionGroup)
1440                 self._sortByNameAction.setText("Sort By Name")
1441                 self._sortByNameAction.setStatusTip("Sort the units by name")
1442                 self._sortByNameAction.setToolTip("Sort the units by name")
1443                 self._sortByNameAction.setCheckable(True)
1444                 self._sortByValueAction = QtGui.QAction(self._sortActionGroup)
1445                 self._sortByValueAction.setText("Sort By Value")
1446                 self._sortByValueAction.setStatusTip("Sort the units by value")
1447                 self._sortByValueAction.setToolTip("Sort the units by value")
1448                 self._sortByValueAction.setCheckable(True)
1449                 self._sortByUnitAction = QtGui.QAction(self._sortActionGroup)
1450                 self._sortByUnitAction.setText("Sort By Unit")
1451                 self._sortByUnitAction.setStatusTip("Sort the units by unit")
1452                 self._sortByUnitAction.setToolTip("Sort the units by unit")
1453                 self._sortByUnitAction.setCheckable(True)
1454
1455                 self._sortByValueAction.setChecked(True)
1456
1457                 self._chooseFavoritesAction = QtGui.QAction(None)
1458                 self._chooseFavoritesAction.setText("Select Favorites")
1459                 self._chooseFavoritesAction.setShortcut(QtGui.QKeySequence("CTRL+f"))
1460                 self._chooseFavoritesAction.triggered.connect(self._on_choose_favorites)
1461
1462                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
1463
1464                 self._previousUnitAction = QtGui.QAction(None)
1465                 self._previousUnitAction.setText("Previous Unit")
1466                 self._previousUnitAction.setShortcut(QtGui.QKeySequence("Up"))
1467                 self._previousUnitAction.triggered.connect(self._on_previous_unit)
1468
1469                 self._nextUnitAction = QtGui.QAction(None)
1470                 self._nextUnitAction.setText("Next Unit")
1471                 self._nextUnitAction.setShortcut(QtGui.QKeySequence("Down"))
1472                 self._nextUnitAction.triggered.connect(self._on_next_unit)
1473
1474                 self._closeWindowAction = QtGui.QAction(None)
1475                 self._closeWindowAction.setText("Close Window")
1476                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
1477                 self._closeWindowAction.triggered.connect(self._on_close_window)
1478
1479                 if IS_MAEMO:
1480                         self._window.addAction(self._closeWindowAction)
1481                         self._window.addAction(self._app.quitAction)
1482                         self._window.addAction(self._app.fullscreenAction)
1483
1484                         fileMenu = self._window.menuBar().addMenu("&Units")
1485                         fileMenu.addAction(self._chooseFavoritesAction)
1486
1487                         viewMenu = self._window.menuBar().addMenu("&View")
1488                         viewMenu.addAction(self._app.showFavoritesAction)
1489                         viewMenu.addAction(self._app.condensedAction)
1490                         viewMenu.addSeparator()
1491                         viewMenu.addAction(self._sortByNameAction)
1492                         viewMenu.addAction(self._sortByValueAction)
1493                         viewMenu.addAction(self._sortByUnitAction)
1494                         viewMenu.addSeparator()
1495                         viewMenu.addAction(self._app.jumpAction)
1496                         viewMenu.addAction(self._app.recentAction)
1497                 else:
1498                         fileMenu = self._window.menuBar().addMenu("&Units")
1499                         fileMenu.addAction(self._chooseFavoritesAction)
1500                         fileMenu.addAction(self._closeWindowAction)
1501                         fileMenu.addAction(self._app.quitAction)
1502
1503                         viewMenu = self._window.menuBar().addMenu("&View")
1504                         viewMenu.addAction(self._app.showFavoritesAction)
1505                         viewMenu.addAction(self._app.condensedAction)
1506                         viewMenu.addSeparator()
1507                         viewMenu.addAction(self._sortByNameAction)
1508                         viewMenu.addAction(self._sortByValueAction)
1509                         viewMenu.addAction(self._sortByUnitAction)
1510                         viewMenu.addSeparator()
1511                         viewMenu.addAction(self._app.jumpAction)
1512                         viewMenu.addAction(self._app.recentAction)
1513                         viewMenu.addSeparator()
1514                         viewMenu.addAction(self._app.fullscreenAction)
1515
1516                 self._sortByNameAction.triggered.connect(self._on_sort_by_name)
1517                 self._sortByValueAction.triggered.connect(self._on_sort_by_value)
1518                 self._sortByUnitAction.triggered.connect(self._on_sort_by_unit)
1519
1520                 self._window.addAction(self._app.logAction)
1521                 self._window.addAction(self._nextUnitAction)
1522                 self._window.addAction(self._previousUnitAction)
1523                 self._window.addAction(self._chooseFavoritesAction)
1524
1525                 self._update_favorites()
1526                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
1527                 self._window.show()
1528
1529         @property
1530         def window(self):
1531                 return self._window
1532
1533         def close(self):
1534                 for child in self.walk_children():
1535                         child.close()
1536                 self._window.close()
1537
1538         def set_fullscreen(self, isFullscreen):
1539                 if isFullscreen:
1540                         self._window.showFullScreen()
1541                 else:
1542                         self._window.showNormal()
1543
1544         def select_unit(self, unitName):
1545                 index = self._unitsModel.index_unit(unitName)
1546                 self._select_unit(index)
1547
1548         def walk_children(self):
1549                 if self._favoritesWindow is not None:
1550                         yield self._favoritesWindow
1551
1552         def _update_favorites(self, force = False):
1553                 if self._app.showFavoritesAction.isChecked():
1554                         unitNames = list(self._unitsModel.get_unit_names())
1555                         for i, unitName in enumerate(unitNames):
1556                                 if unitName in self._app.get_hidden_units(self._categoryName):
1557                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), True)
1558                                 else:
1559                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1560                 else:
1561                         if force:
1562                                 for i in xrange(len(self._unitsModel)):
1563                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1564
1565         @misc_utils.log_exception(_moduleLogger)
1566         def _on_show_favorites(self, checked = True):
1567                 if checked:
1568                         unitNames = list(self._unitsModel.get_unit_names())
1569                         for i, unitName in enumerate(unitNames):
1570                                 if unitName in self._app.get_hidden_units(self._categoryName):
1571                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), True)
1572                 else:
1573                         for i in xrange(len(self._unitsModel)):
1574                                 self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1575
1576         @misc_utils.log_exception(_moduleLogger)
1577         def _on_choose_favorites(self, obj = None):
1578                 assert self._favoritesWindow is None
1579                 self._favoritesWindow = FavoritesWindow(
1580                         self._window,
1581                         self._app,
1582                         unit_data.get_units(self._categoryName),
1583                         self._app.get_hidden_units(self._categoryName)
1584                 )
1585                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
1586                 return self._favoritesWindow
1587
1588         @misc_utils.log_exception(_moduleLogger)
1589         def _on_close_favorites(self, obj = None):
1590                 self._favoritesWindow = None
1591                 self._update_favorites(force=True)
1592
1593         @misc_utils.log_exception(_moduleLogger)
1594         def _on_previous_unit(self, checked = True):
1595                 self._select_unit(self._selectedIndex - 1)
1596
1597         @misc_utils.log_exception(_moduleLogger)
1598         def _on_next_unit(self, checked = True):
1599                 self._select_unit(self._selectedIndex + 1)
1600
1601         @misc_utils.log_exception(_moduleLogger)
1602         def _on_close_window(self, checked = True):
1603                 self.close()
1604
1605         @misc_utils.log_exception(_moduleLogger)
1606         def _on_sort_by_name(self, checked = False):
1607                 self._unitsModel.sort(UnitData.NAME_COLUMN, QtCore.Qt.DescendingOrder)
1608
1609         @misc_utils.log_exception(_moduleLogger)
1610         def _on_sort_by_value(self, checked = False):
1611                 self._unitsModel.sort(UnitData.VALUE_COLUMN_0)
1612
1613         @misc_utils.log_exception(_moduleLogger)
1614         def _on_sort_by_unit(self, checked = False):
1615                 self._unitsModel.sort(UnitData.UNIT_COLUMN, QtCore.Qt.DescendingOrder)
1616
1617         @misc_utils.log_exception(_moduleLogger)
1618         def _on_unit_clicked(self, index):
1619                 self._select_unit(index.row())
1620
1621         @misc_utils.log_exception(_moduleLogger)
1622         def _on_value_edited(self, *args):
1623                 userInput = self._selectedUnitValue.text()
1624                 self._unitsModel.update_values(self._selectedIndex, str(userInput))
1625                 self._update_favorites()
1626
1627         def _select_unit(self, index):
1628                 unit = self._unitsModel.get_unit(index)
1629                 self._selectedUnitName.setText(unit.name)
1630                 self._selectedUnitValue.setText(str(unit.value))
1631                 self._selectedUnitSymbol.setText(unit.unit)
1632
1633                 self._selectedIndex = index
1634                 qindex = self._unitsModel.createIndex(index, 0, self._unitsModel.get_unit(index))
1635                 self._unitsView.scrollTo(qindex)
1636                 self._app.add_recent(self._categoryName, self._unitsModel.get_unit(index).name)
1637
1638
1639 def run_gonvert():
1640         app = QtGui.QApplication([])
1641         handle = Gonvert(app)
1642         return app.exec_()
1643
1644
1645 if __name__ == "__main__":
1646         logging.basicConfig(level = logging.DEBUG)
1647         try:
1648                 os.makedirs(constants._data_path_)
1649         except OSError, e:
1650                 if e.errno != 17:
1651                         raise
1652
1653         val = run_gonvert()
1654         sys.exit(val)