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