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