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