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