Defaulting to condensed mode for the faster startup
[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 import maeqt
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                 maeqt.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                 maeqt.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                 if not constants.IS_MAEMO:
446                         self._categoryView.setAlternatingRowColors(True)
447                 self._categoryView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
448                 self._categoryView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
449                 for catName in unit_data.UNIT_CATEGORIES:
450                         twi = QtGui.QTreeWidgetItem(self._categoryView)
451                         twi.setText(0, catName)
452                 self._categorySelection = self._categoryView.selectionModel()
453                 self._categorySelection.selectionChanged.connect(self._on_category_selection_changed)
454
455                 self._inputView = QtGui.QTreeWidget()
456                 self._inputView.setHeaderLabels(["From", "Name"])
457                 self._inputView.setHeaderHidden(False)
458                 self._inputView.header().hideSection(1)
459                 if not constants.IS_MAEMO:
460                         self._inputView.setAlternatingRowColors(True)
461                 self._inputView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
462                 self._inputView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
463                 self._inputSelection = self._inputView.selectionModel()
464                 self._inputSelection.selectionChanged.connect(self._on_input_selection_changed)
465
466                 self._outputView = QtGui.QTreeWidget()
467                 self._outputView.setHeaderLabels(["To", "Name"])
468                 self._outputView.setHeaderHidden(False)
469                 self._outputView.header().hideSection(1)
470                 if not constants.IS_MAEMO:
471                         self._outputView.setAlternatingRowColors(True)
472                 self._outputView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
473                 self._outputView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
474                 self._outputWidgets = []
475                 self._outputSelection = self._outputView.selectionModel()
476                 self._outputSelection.selectionChanged.connect(self._on_output_selection_changed)
477
478                 self._selectionLayout = QtGui.QHBoxLayout()
479                 self._selectionLayout.addWidget(self._categoryView)
480                 self._selectionLayout.addWidget(self._inputView)
481                 self._selectionLayout.addWidget(self._outputView)
482
483                 self._layout = QtGui.QVBoxLayout()
484                 self._layout.addLayout(self._conversionLayout)
485                 self._layout.addLayout(self._selectionLayout)
486
487                 centralWidget = QtGui.QWidget()
488                 centralWidget.setLayout(self._layout)
489
490                 self._window = QtGui.QMainWindow(parent)
491                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
492                 maeqt.set_autorient(self._window, True)
493                 maeqt.set_stackable(self._window, True)
494                 self._window.setWindowTitle("%s - Quick Convert" % (constants.__pretty_app_name__, ))
495                 self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
496                 self._window.setCentralWidget(centralWidget)
497
498                 self._chooseCatFavoritesAction = QtGui.QAction(None)
499                 self._chooseCatFavoritesAction.setText("Select Categories")
500                 self._chooseCatFavoritesAction.triggered.connect(self._on_choose_category_favorites)
501
502                 self._chooseUnitFavoritesAction = QtGui.QAction(None)
503                 self._chooseUnitFavoritesAction.setText("Select Units")
504                 self._chooseUnitFavoritesAction.triggered.connect(self._on_choose_unit_favorites)
505                 self._chooseUnitFavoritesAction.setEnabled(False)
506
507                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
508
509                 self._closeWindowAction = QtGui.QAction(None)
510                 self._closeWindowAction.setText("Close Window")
511                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
512                 self._closeWindowAction.triggered.connect(self._on_close_window)
513
514                 if constants.IS_MAEMO:
515                         self._window.addAction(self._closeWindowAction)
516                         self._window.addAction(self._app.quitAction)
517                         self._window.addAction(self._app.fullscreenAction)
518
519                         fileMenu = self._window.menuBar().addMenu("&Units")
520                         fileMenu.addAction(self._chooseCatFavoritesAction)
521                         fileMenu.addAction(self._chooseUnitFavoritesAction)
522
523                         viewMenu = self._window.menuBar().addMenu("&View")
524                         viewMenu.addAction(self._app.showFavoritesAction)
525                         viewMenu.addAction(self._app.condensedAction)
526                         viewMenu.addSeparator()
527                         viewMenu.addAction(self._app.jumpAction)
528                         viewMenu.addAction(self._app.recentAction)
529                 else:
530                         fileMenu = self._window.menuBar().addMenu("&Units")
531                         fileMenu.addAction(self._chooseCatFavoritesAction)
532                         fileMenu.addAction(self._chooseUnitFavoritesAction)
533                         fileMenu.addAction(self._closeWindowAction)
534                         fileMenu.addAction(self._app.quitAction)
535
536                         viewMenu = self._window.menuBar().addMenu("&View")
537                         viewMenu.addAction(self._app.showFavoritesAction)
538                         viewMenu.addAction(self._app.condensedAction)
539                         viewMenu.addSeparator()
540                         viewMenu.addAction(self._app.jumpAction)
541                         viewMenu.addAction(self._app.recentAction)
542                         viewMenu.addSeparator()
543                         viewMenu.addAction(self._app.fullscreenAction)
544
545                 self._window.addAction(self._app.logAction)
546
547                 self._update_favorites()
548                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
549                 self._window.show()
550
551         @property
552         def window(self):
553                 return self._window
554
555         def show(self):
556                 self._window.show()
557
558         def hide(self):
559                 self._window.hide()
560
561         def close(self):
562                 self._window.close()
563
564         def set_fullscreen(self, isFullscreen):
565                 if isFullscreen:
566                         self._window.showFullScreen()
567                 else:
568                         self._window.showNormal()
569
570         def select_category(self, categoryName):
571                 self._select_category(categoryName)
572
573                 i = unit_data.UNIT_CATEGORIES.index(categoryName)
574                 rootIndex = self._categoryView.rootIndex()
575                 currentIndex = self._categoryView.model().index(i, 0, rootIndex)
576                 self._categoryView.scrollTo(currentIndex)
577                 self._categoryView.setItemSelected(self._categoryView.topLevelItem(i), True)
578
579                 return self
580
581         def select_unit(self, name):
582                 self.select_input(name)
583                 return self
584
585         def select_input(self, name):
586                 self._select_input(name)
587
588                 i = self._unitNames.index(name)
589                 rootIndex = self._inputView.rootIndex()
590                 currentIndex = self._inputView.model().index(i, 0, rootIndex)
591                 self._inputView.scrollTo(currentIndex)
592                 self._inputView.setItemSelected(self._inputView.topLevelItem(i), True)
593
594         def select_output(self, name):
595                 self._select_output(name)
596
597                 i = self._unitNames.index(name)
598                 rootIndex = self._outputView.rootIndex()
599                 currentIndex = self._outputView.model().index(i, 0, rootIndex)
600                 self._outputView.scrollTo(currentIndex)
601                 self._outputView.setItemSelected(self._outputView.topLevelItem(i), True)
602
603         def _select_category(self, categoryName):
604                 self._inputUnitName = ""
605                 self._outputUnitName = ""
606                 self._inputUnitValue.setText("")
607                 self._inputUnitSymbol.setText("")
608                 self._inputView.clear()
609                 self._outputUnitValue.setText("")
610                 self._outputUnitSymbol.setText("")
611                 self._outputView.clear()
612                 self._categoryName = categoryName
613                 self._chooseUnitFavoritesAction.setEnabled(True)
614
615                 unitData = unit_data.UNIT_DESCRIPTIONS[categoryName]
616                 self._unitNames = list(unit_data.get_units(categoryName))
617                 self._unitNames.sort()
618                 for key in self._unitNames:
619                         conversion, unit, description = unitData[key]
620                         unit = key
621
622                         twi = QtGui.QTreeWidgetItem(self._inputView)
623                         twi.setText(0, unit)
624                         twi.setText(1, key)
625
626                         twi = QtGui.QTreeWidgetItem(self._outputView)
627                         twi.setText(0, unit)
628                         twi.setText(1, key)
629
630                 defaultInputUnitName = self._app.get_recent_unit(categoryName)
631                 if defaultInputUnitName:
632                         self.select_input(defaultInputUnitName)
633                         defaultOutputUnitName = self._app.get_recent_unit(categoryName, 1)
634                         assert defaultOutputUnitName
635                         self.select_output(defaultOutputUnitName)
636
637         def _select_input(self, name):
638                 self._app.add_recent(self._categoryName, name)
639                 self._inputUnitName = name
640
641                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
642                 conversion, unit, description = unitData[name]
643
644                 self._inputUnitSymbol.setText(unit if unit else name)
645
646                 if "" not in [self._categoryName, self._inputUnitName, self._outputUnitName]:
647                         self._update_output()
648
649         def _select_output(self, name):
650                 # Add the output to recent but don't make things weird by making it the most recent
651                 self._app.add_recent(self._categoryName, name)
652                 self._app.add_recent(self._categoryName, self._inputUnitName)
653                 self._outputUnitName = name
654
655                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
656                 conversion, unit, description = unitData[name]
657
658                 self._outputUnitSymbol.setText(unit if unit else name)
659
660                 if "" not in [self._categoryName, self._inputUnitName, self._outputUnitName]:
661                         self._update_output()
662
663         def _sanitize_value(self, userEntry):
664                 if self._categoryName == "Computer Numbers":
665                         if userEntry == '':
666                                 value = '0'
667                         else:
668                                 value = userEntry
669                 else:
670                         if userEntry == '':
671                                 value = 0.0
672                         else:
673                                 value = float(userEntry)
674                 return value
675
676         def _update_output(self):
677                 assert self._categoryName
678                 assert self._inputUnitName
679                 assert self._outputUnitName
680
681                 userInput = str(self._inputUnitValue.text())
682                 value = self._sanitize_value(userInput)
683
684                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
685                 inputConversion, _, _ = unitData[self._inputUnitName]
686                 outputConversion, _, _ = unitData[self._outputUnitName]
687
688                 func, arg = inputConversion
689                 base = func.to_base(value, arg)
690
691                 func, arg = outputConversion
692                 newValue = func.from_base(base, arg)
693                 self._outputUnitValue.setText(str(newValue))
694
695         def _update_input(self):
696                 assert self._categoryName
697                 assert self._inputUnitName
698                 assert self._outputUnitName
699
700                 userOutput = str(self._outputUnitValue.text())
701                 value = self._sanitize_value(userOutput)
702
703                 unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
704                 inputConversion, _, _ = unitData[self._inputUnitName]
705                 outputConversion, _, _ = unitData[self._outputUnitName]
706
707                 func, arg = outputConversion
708                 base = func.to_base(value, arg)
709
710                 func, arg = inputConversion
711                 newValue = func.from_base(base, arg)
712                 self._inputUnitValue.setText(str(newValue))
713
714         def _update_favorites(self):
715                 if self._app.showFavoritesAction.isChecked():
716                         assert self._categoryView.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
717                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
718                                 if catName in self._app.hiddenCategories:
719                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), True)
720                                 else:
721                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
722
723                         for i, unitName in enumerate(self._unitNames):
724                                 if unitName in self._app.get_hidden_units(self._categoryName):
725                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), True)
726                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), True)
727                                 else:
728                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
729                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
730                 else:
731                         for i in xrange(self._categoryView.topLevelItemCount()):
732                                 self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
733
734                         for i in xrange(len(self._unitNames)):
735                                 self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
736                                 self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
737
738         @misc_utils.log_exception(_moduleLogger)
739         def _on_close_window(self, checked = True):
740                 self.close()
741
742         @misc_utils.log_exception(_moduleLogger)
743         def _on_show_favorites(self, checked = True):
744                 if checked:
745                         assert self._categoryView.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
746                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
747                                 if catName in self._app.hiddenCategories:
748                                         self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), True)
749
750                         for i, unitName in enumerate(self._unitNames):
751                                 if unitName in self._app.get_hidden_units(self._categoryName):
752                                         self._inputView.setRowHidden(i, self._inputView.rootIndex(), True)
753                                         self._outputView.setRowHidden(i, self._outputView.rootIndex(), True)
754                 else:
755                         for i in xrange(self._categoryView.topLevelItemCount()):
756                                 self._categoryView.setRowHidden(i, self._categoryView.rootIndex(), False)
757
758                         for i in xrange(len(self._unitNames)):
759                                 self._inputView.setRowHidden(i, self._inputView.rootIndex(), False)
760                                 self._outputView.setRowHidden(i, self._outputView.rootIndex(), False)
761
762         @misc_utils.log_exception(_moduleLogger)
763         def _on_choose_category_favorites(self, obj = None):
764                 assert self._favoritesWindow is None
765                 import windows
766                 self._favoritesWindow = windows.FavoritesWindow(
767                         self._window,
768                         self._app,
769                         unit_data.UNIT_CATEGORIES,
770                         self._app.hiddenCategories
771                 )
772                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
773                 return self._favoritesWindow
774
775         @misc_utils.log_exception(_moduleLogger)
776         def _on_choose_unit_favorites(self, obj = None):
777                 assert self._favoritesWindow is None
778                 import windows
779                 self._favoritesWindow = windows.FavoritesWindow(
780                         self._window,
781                         self._app,
782                         unit_data.get_units(self._categoryName),
783                         self._app.get_hidden_units(self._categoryName)
784                 )
785                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
786                 return self._favoritesWindow
787
788         @misc_utils.log_exception(_moduleLogger)
789         def _on_close_favorites(self, obj = None):
790                 self._favoritesWindow = None
791                 self._update_favorites()
792
793         @misc_utils.log_exception(_moduleLogger)
794         def _on_value_edited(self, *args):
795                 self._update_output()
796
797         @misc_utils.log_exception(_moduleLogger)
798         def _on_output_value_edited(self, *args):
799                 self._update_input()
800
801         @misc_utils.log_exception(_moduleLogger)
802         def _on_category_selection_changed(self, selected, deselected):
803                 selectedNames = [
804                         str(item.text(0))
805                         for item in self._categoryView.selectedItems()
806                 ]
807                 assert len(selectedNames) == 1
808                 self._select_category(selectedNames[0])
809
810         @misc_utils.log_exception(_moduleLogger)
811         def _on_input_selection_changed(self, selected, deselected):
812                 selectedNames = [
813                         str(item.text(1))
814                         for item in self._inputView.selectedItems()
815                 ]
816                 if selectedNames:
817                         assert len(selectedNames) == 1
818                         name = selectedNames[0]
819                         self._select_input(name)
820                 else:
821                         pass
822
823         @misc_utils.log_exception(_moduleLogger)
824         def _on_output_selection_changed(self, selected, deselected):
825                 selectedNames = [
826                         str(item.text(1))
827                         for item in self._outputView.selectedItems()
828                 ]
829                 if selectedNames:
830                         assert len(selectedNames) == 1
831                         name = selectedNames[0]
832                         self._select_output(name)
833                 else:
834                         pass
835
836
837 class CategoryWindow(object):
838
839         def __init__(self, parent, app):
840                 self._app = app
841                 self._unitWindow = None
842                 self._favoritesWindow = None
843
844                 self._categories = QtGui.QTreeWidget()
845                 self._categories.setHeaderLabels(["Categories"])
846                 self._categories.itemClicked.connect(self._on_category_clicked)
847                 self._categories.setHeaderHidden(True)
848                 self._categories.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
849                 self._categories.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
850                 if not constants.IS_MAEMO:
851                         self._categories.setAlternatingRowColors(True)
852                 for catName in unit_data.UNIT_CATEGORIES:
853                         twi = QtGui.QTreeWidgetItem(self._categories)
854                         twi.setText(0, catName)
855
856                 self._layout = QtGui.QVBoxLayout()
857                 self._layout.addWidget(self._categories)
858
859                 centralWidget = QtGui.QWidget()
860                 centralWidget.setLayout(self._layout)
861
862                 self._window = QtGui.QMainWindow(parent)
863                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
864                 maeqt.set_autorient(self._window, True)
865                 maeqt.set_stackable(self._window, True)
866                 self._window.setWindowTitle("%s - Categories" % constants.__pretty_app_name__)
867                 self._window.setWindowIcon(QtGui.QIcon(self._app.appIconPath))
868                 self._window.setCentralWidget(centralWidget)
869
870                 self._chooseFavoritesAction = QtGui.QAction(None)
871                 self._chooseFavoritesAction.setText("Select Favorites")
872                 self._chooseFavoritesAction.setShortcut(QtGui.QKeySequence("CTRL+f"))
873                 self._chooseFavoritesAction.triggered.connect(self._on_choose_favorites)
874
875                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
876
877                 self._closeWindowAction = QtGui.QAction(None)
878                 self._closeWindowAction.setText("Close")
879                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
880                 self._closeWindowAction.triggered.connect(self._on_close_window)
881
882                 if constants.IS_MAEMO:
883                         fileMenu = self._window.menuBar().addMenu("&Units")
884                         fileMenu.addAction(self._chooseFavoritesAction)
885
886                         viewMenu = self._window.menuBar().addMenu("&View")
887                         viewMenu.addAction(self._app.showFavoritesAction)
888                         viewMenu.addAction(self._app.condensedAction)
889                         viewMenu.addSeparator()
890                         viewMenu.addAction(self._app.jumpAction)
891                         viewMenu.addAction(self._app.recentAction)
892
893                         self._window.addAction(self._closeWindowAction)
894                         self._window.addAction(self._app.quitAction)
895                         self._window.addAction(self._app.fullscreenAction)
896                 else:
897                         fileMenu = self._window.menuBar().addMenu("&Units")
898                         fileMenu.addAction(self._chooseFavoritesAction)
899                         fileMenu.addAction(self._closeWindowAction)
900                         fileMenu.addAction(self._app.quitAction)
901
902                         viewMenu = self._window.menuBar().addMenu("&View")
903                         viewMenu.addAction(self._app.showFavoritesAction)
904                         viewMenu.addAction(self._app.condensedAction)
905                         viewMenu.addSeparator()
906                         viewMenu.addAction(self._app.jumpAction)
907                         viewMenu.addAction(self._app.recentAction)
908                         viewMenu.addSeparator()
909                         viewMenu.addAction(self._app.fullscreenAction)
910
911                 self._window.addAction(self._app.logAction)
912
913                 self._update_favorites()
914                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
915                 self._window.show()
916
917         @property
918         def window(self):
919                 return self._window
920
921         def walk_children(self):
922                 if self._unitWindow is not None:
923                         yield self._unitWindow
924                 if self._favoritesWindow is not None:
925                         yield self._favoritesWindow
926
927         def show(self):
928                 self._window.show()
929                 for child in self.walk_children():
930                         child.show()
931
932         def hide(self):
933                 for child in self.walk_children():
934                         child.hide()
935                 self._window.hide()
936
937         def close(self):
938                 for child in self.walk_children():
939                         child.window.destroyed.disconnect(self._on_child_close)
940                         child.close()
941                 self._window.close()
942
943         def select_category(self, categoryName):
944                 self._select_category(categoryName)
945
946                 i = unit_data.UNIT_CATEGORIES.index(categoryName)
947                 rootIndex = self._categories.rootIndex()
948                 currentIndex = self._categories.model().index(i, 0, rootIndex)
949                 self._categories.scrollTo(currentIndex)
950                 self._categories.setItemSelected(self._categories.topLevelItem(i), True)
951                 return self._unitWindow
952
953         def set_fullscreen(self, isFullscreen):
954                 if isFullscreen:
955                         self._window.showFullScreen()
956                 else:
957                         self._window.showNormal()
958                 for child in self.walk_children():
959                         child.set_fullscreen(isFullscreen)
960
961         def _select_category(self, categoryName):
962                 for child in self.walk_children():
963                         child.window.destroyed.disconnect(self._on_child_close)
964                         child.close()
965                 self._unitWindow = UnitWindow(self._window, categoryName, self._app)
966                 self._unitWindow.window.destroyed.connect(self._on_child_close)
967
968         def _update_favorites(self):
969                 if self._app.showFavoritesAction.isChecked():
970                         assert self._categories.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
971                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
972                                 if catName in self._app.hiddenCategories:
973                                         self._categories.setRowHidden(i, self._categories.rootIndex(), True)
974                                 else:
975                                         self._categories.setRowHidden(i, self._categories.rootIndex(), False)
976                 else:
977                         for i in xrange(self._categories.topLevelItemCount()):
978                                 self._categories.setRowHidden(i, self._categories.rootIndex(), False)
979
980         @misc_utils.log_exception(_moduleLogger)
981         def _on_show_favorites(self, checked = True):
982                 if checked:
983                         assert self._categories.topLevelItemCount() == len(unit_data.UNIT_CATEGORIES)
984                         for i, catName in enumerate(unit_data.UNIT_CATEGORIES):
985                                 if catName in self._app.hiddenCategories:
986                                         self._categories.setRowHidden(i, self._categories.rootIndex(), True)
987                 else:
988                         for i in xrange(self._categories.topLevelItemCount()):
989                                 self._categories.setRowHidden(i, self._categories.rootIndex(), False)
990
991         @misc_utils.log_exception(_moduleLogger)
992         def _on_choose_favorites(self, obj = None):
993                 assert self._favoritesWindow is None
994                 import windows
995                 self._favoritesWindow = windows.FavoritesWindow(
996                         self._window,
997                         self._app,
998                         unit_data.UNIT_CATEGORIES,
999                         self._app.hiddenCategories
1000                 )
1001                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
1002                 return self._favoritesWindow
1003
1004         @misc_utils.log_exception(_moduleLogger)
1005         def _on_close_favorites(self, obj = None):
1006                 self._favoritesWindow = None
1007                 self._update_favorites()
1008
1009         @misc_utils.log_exception(_moduleLogger)
1010         def _on_child_close(self, obj = None):
1011                 self._unitWindow = None
1012
1013         @misc_utils.log_exception(_moduleLogger)
1014         def _on_close_window(self, checked = True):
1015                 self.close()
1016
1017         @misc_utils.log_exception(_moduleLogger)
1018         def _on_category_clicked(self, item, columnIndex):
1019                 categoryName = unicode(item.text(0))
1020                 self.select_category(categoryName)
1021
1022
1023 class UnitData(object):
1024
1025         HEADERS = ["Name", "Value", "", "Unit"]
1026         ALIGNMENT = [QtCore.Qt.AlignLeft, QtCore.Qt.AlignRight, QtCore.Qt.AlignLeft, QtCore.Qt.AlignLeft]
1027         NAME_COLUMN = 0
1028         VALUE_COLUMN_0 = 1
1029         VALUE_COLUMN_1 = 2
1030         UNIT_COLUMN = 3
1031
1032         __slots__ = [
1033                 "_name", "_unit", "_description", "_conversion",
1034                 "_value", "_integerDisplay", "_fractionalDisplay",
1035         ]
1036
1037         def __init__(self, name, unit, description, conversion):
1038                 self._name = name
1039                 self._unit = unit
1040                 self._description = description
1041                 self._conversion = conversion
1042
1043                 self._value = 0.0
1044                 self._integerDisplay, self._fractionalDisplay = split_number(self._value)
1045
1046         @property
1047         def name(self):
1048                 return self._name
1049
1050         @property
1051         def value(self):
1052                 return self._value
1053
1054         def update_value(self, newValue):
1055                 self._value = newValue
1056                 self._integerDisplay, self._fractionalDisplay = split_number(newValue)
1057
1058         @property
1059         def unit(self):
1060                 return self._unit
1061
1062         @property
1063         def conversion(self):
1064                 return self._conversion
1065
1066         def data(self, column):
1067                 try:
1068                         return [self._name, self._integerDisplay, self._fractionalDisplay, self._unit][column]
1069                 except IndexError:
1070                         return None
1071
1072
1073 class UnitModel(QtCore.QAbstractItemModel):
1074
1075         def __init__(self, categoryName, parent=None):
1076                 super(UnitModel, self).__init__(parent)
1077                 self._categoryName = categoryName
1078                 self._unitData = unit_data.UNIT_DESCRIPTIONS[self._categoryName]
1079                 if self._categoryName == "Computer Numbers":
1080                         self._sanitize_value = self._sanitize_alpha_value
1081                 else:
1082                         self._sanitize_value = self._sanitize_numeric_value
1083
1084                 self._children = []
1085                 for key in unit_data.get_units(self._categoryName):
1086                         conversion, unit, description = self._unitData[key]
1087                         self._children.append(UnitData(key, unit, description, conversion))
1088                 self._sortSettings = None
1089
1090         @misc_utils.log_exception(_moduleLogger)
1091         def columnCount(self, parent):
1092                 if parent.isValid():
1093                         return 0
1094                 else:
1095                         return len(UnitData.HEADERS)
1096
1097         @misc_utils.log_exception(_moduleLogger)
1098         def data(self, index, role):
1099                 #if not index.isValid():
1100                 #       return None
1101
1102                 if role == QtCore.Qt.DisplayRole:
1103                         item = index.internalPointer()
1104                         if isinstance(item, UnitData):
1105                                 return item.data(index.column())
1106                         elif item is UnitData.HEADERS:
1107                                 return item[index.column()]
1108                 elif role == QtCore.Qt.TextAlignmentRole:
1109                         return UnitData.ALIGNMENT[index.column()]
1110                 else:
1111                         return None
1112
1113         @misc_utils.log_exception(_moduleLogger)
1114         def sort(self, column, order = QtCore.Qt.AscendingOrder):
1115                 self._sortSettings = column, order
1116                 isReverse = order == QtCore.Qt.AscendingOrder
1117                 if column == UnitData.NAME_COLUMN:
1118                         key_func = lambda item: item.name
1119                 elif column in [UnitData.VALUE_COLUMN_0, UnitData.VALUE_COLUMN_1]:
1120                         key_func = lambda item: item.value
1121                 elif column == UnitData.UNIT_COLUMN:
1122                         key_func = lambda item: item.unit
1123                 self._children.sort(key=key_func, reverse = isReverse)
1124
1125                 self._all_changed()
1126
1127         @misc_utils.log_exception(_moduleLogger)
1128         def flags(self, index):
1129                 if not index.isValid():
1130                         return QtCore.Qt.NoItemFlags
1131
1132                 return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
1133
1134         @misc_utils.log_exception(_moduleLogger)
1135         def headerData(self, section, orientation, role):
1136                 if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
1137                         return UnitData.HEADERS[section]
1138
1139                 return None
1140
1141         @misc_utils.log_exception(_moduleLogger)
1142         def index(self, row, column, parent):
1143                 #if not self.hasIndex(row, column, parent):
1144                 #       return QtCore.QModelIndex()
1145                 #elif parent.isValid():
1146                 #       return QtCore.QModelIndex()
1147
1148                 parentItem = UnitData.HEADERS
1149                 childItem = self._children[row]
1150                 if childItem:
1151                         return self.createIndex(row, column, childItem)
1152                 else:
1153                         return QtCore.QModelIndex()
1154
1155         @misc_utils.log_exception(_moduleLogger)
1156         def parent(self, index):
1157                 if not index.isValid():
1158                         return QtCore.QModelIndex()
1159
1160                 childItem = index.internalPointer()
1161                 if isinstance(childItem, UnitData):
1162                         return QtCore.QModelIndex()
1163                 elif childItem is UnitData.HEADERS:
1164                         return None
1165
1166         @misc_utils.log_exception(_moduleLogger)
1167         def rowCount(self, parent):
1168                 if 0 < parent.column():
1169                         return 0
1170
1171                 if not parent.isValid():
1172                         return len(self._children)
1173                 else:
1174                         return len(self._children)
1175
1176         def get_unit(self, index):
1177                 assert 0 <= index
1178                 return self._children[index]
1179
1180         def get_unit_names(self):
1181                 for child in self._children:
1182                         yield child.name
1183
1184         def index_unit(self, unitName):
1185                 for i, child in enumerate(self._children):
1186                         if child.name == unitName:
1187                                 return i
1188                 else:
1189                         raise RuntimeError("Unit not found")
1190
1191         def update_values(self, fromIndex, userInput):
1192                 value = self._sanitize_value(userInput)
1193                 func, arg = self._children[fromIndex].conversion
1194                 base = func.to_base(value, arg)
1195                 for i, child in enumerate(self._children):
1196                         func, arg = child.conversion
1197                         newValue = func.from_base(base, arg)
1198                         child.update_value(newValue)
1199
1200                 if (
1201                         self._sortSettings is not None and
1202                         self._sortSettings[0]  in [UnitData.VALUE_COLUMN_0, UnitData.VALUE_COLUMN_1]
1203                 ):
1204                         # Sort takes care of marking everything as changed
1205                         self.sort(*self._sortSettings)
1206                         return True
1207                 else:
1208                         self._values_changed()
1209                         return False
1210
1211         def __len__(self):
1212                 return len(self._children)
1213
1214         def _values_changed(self):
1215                 topLeft = self.createIndex(0, UnitData.VALUE_COLUMN_0, self._children[0])
1216                 bottomRight = self.createIndex(len(self._children)-1, UnitData.VALUE_COLUMN_1, self._children[-1])
1217                 self.dataChanged.emit(topLeft, bottomRight)
1218
1219         def _all_changed(self):
1220                 topLeft = self.createIndex(0, 0, self._children[0])
1221                 bottomRight = self.createIndex(len(self._children)-1, len(UnitData.HEADERS)-1, self._children[-1])
1222                 self.dataChanged.emit(topLeft, bottomRight)
1223
1224         def _sanitize_alpha_value(self, userEntry):
1225                 if userEntry:
1226                         value = userEntry
1227                 else:
1228                         value = '0'
1229                 return value
1230
1231         def _sanitize_numeric_value(self, userEntry):
1232                 if userEntry:
1233                         value = float(userEntry)
1234                 else:
1235                         value = 0.0
1236                 return value
1237
1238
1239 class UnitWindow(object):
1240
1241         def __init__(self, parent, category, app):
1242                 self._app = app
1243                 self._categoryName = category
1244                 self._selectedIndex = 0
1245                 self._favoritesWindow = None
1246
1247                 self._selectedUnitName = QtGui.QLabel()
1248                 self._selectedUnitValue = QtGui.QLineEdit()
1249                 self._selectedUnitValue.textEdited.connect(self._on_value_edited)
1250                 maeqt.mark_numbers_preferred(self._selectedUnitValue)
1251                 self._selectedUnitSymbol = QtGui.QLabel()
1252                 self._updateDelayTimer = QtCore.QTimer()
1253                 self._updateDelayTimer.setInterval(100)
1254                 self._updateDelayTimer.setSingleShot(True)
1255                 self._updateDelayTimer.timeout.connect(self._on_value_edited_delayed)
1256
1257                 self._selectedUnitLayout = QtGui.QHBoxLayout()
1258                 self._selectedUnitLayout.addWidget(self._selectedUnitName)
1259                 self._selectedUnitLayout.addWidget(self._selectedUnitValue)
1260                 self._selectedUnitLayout.addWidget(self._selectedUnitSymbol)
1261
1262                 self._unitsModel = UnitModel(self._categoryName)
1263                 self._unitsView = QtGui.QTreeView()
1264                 self._unitsView.setModel(self._unitsModel)
1265                 self._unitsView.setUniformRowHeights(True)
1266                 self._unitsView.setSortingEnabled(True)
1267                 self._unitsView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
1268                 self._unitsView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
1269                 self._unitsView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
1270                 self._unitsView.setHeaderHidden(True)
1271                 self._unitsView.clicked.connect(self._on_unit_clicked)
1272                 if not constants.IS_MAEMO:
1273                         self._unitsView.setAlternatingRowColors(True)
1274
1275                 viewHeader = self._unitsView.header()
1276                 viewHeader.setSortIndicatorShown(True)
1277                 viewHeader.setClickable(True)
1278
1279                 viewHeader.setResizeMode(UnitData.NAME_COLUMN, QtGui.QHeaderView.ResizeToContents)
1280                 viewHeader.setResizeMode(UnitData.VALUE_COLUMN_0, QtGui.QHeaderView.ResizeToContents)
1281                 viewHeader.setResizeMode(UnitData.VALUE_COLUMN_1, QtGui.QHeaderView.ResizeToContents)
1282                 viewHeader.setResizeMode(UnitData.UNIT_COLUMN, QtGui.QHeaderView.ResizeToContents)
1283                 viewHeader.setStretchLastSection(False)
1284
1285                 # Trying to make things faster by locking in the initial size of the immutable columns
1286                 nameSize = min(viewHeader.sectionSize(UnitData.NAME_COLUMN), 300)
1287                 viewHeader.setResizeMode(UnitData.NAME_COLUMN, QtGui.QHeaderView.Fixed)
1288                 viewHeader.resizeSection(UnitData.NAME_COLUMN, nameSize)
1289                 unitSize = min(viewHeader.sectionSize(UnitData.UNIT_COLUMN), 150)
1290                 viewHeader.setResizeMode(UnitData.UNIT_COLUMN, QtGui.QHeaderView.Fixed)
1291                 viewHeader.resizeSection(UnitData.UNIT_COLUMN, unitSize)
1292
1293                 self._layout = QtGui.QVBoxLayout()
1294                 self._layout.addLayout(self._selectedUnitLayout)
1295                 self._layout.addWidget(self._unitsView)
1296
1297                 centralWidget = QtGui.QWidget()
1298                 centralWidget.setLayout(self._layout)
1299
1300                 self._window = QtGui.QMainWindow(parent)
1301                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1302                 maeqt.set_autorient(self._window, True)
1303                 maeqt.set_stackable(self._window, True)
1304                 self._window.setWindowTitle("%s - %s" % (constants.__pretty_app_name__, category))
1305                 self._window.setWindowIcon(QtGui.QIcon(app.appIconPath))
1306                 self._window.setCentralWidget(centralWidget)
1307
1308                 defaultUnitName = self._app.get_recent_unit(self._categoryName)
1309                 if defaultUnitName:
1310                         self.select_unit(defaultUnitName)
1311                 else:
1312                         self._select_unit(0)
1313
1314                 if self._app.sortByNameAction.isChecked():
1315                         sortColumn = UnitData.NAME_COLUMN
1316                 elif self._app.sortByValueAction.isChecked():
1317                         sortColumn = UnitData.VALUE_COLUMN_0
1318                 elif self._app.sortByUnitAction.isChecked():
1319                         sortColumn = UnitData.UNIT_COLUMN
1320                 else:
1321                         raise RuntimeError("No sort column selected")
1322                 if sortColumn != 0:
1323                         # By default it sorts by he first column (name)
1324                         self._unitsModel.sort(sortColumn)
1325
1326                 self._chooseFavoritesAction = QtGui.QAction(None)
1327                 self._chooseFavoritesAction.setText("Select Favorites")
1328                 self._chooseFavoritesAction.setShortcut(QtGui.QKeySequence("CTRL+f"))
1329                 self._chooseFavoritesAction.triggered.connect(self._on_choose_favorites)
1330
1331                 self._app.showFavoritesAction.toggled.connect(self._on_show_favorites)
1332
1333                 self._previousUnitAction = QtGui.QAction(None)
1334                 self._previousUnitAction.setText("Previous Unit")
1335                 self._previousUnitAction.setShortcut(QtGui.QKeySequence("Up"))
1336                 self._previousUnitAction.triggered.connect(self._on_previous_unit)
1337
1338                 self._nextUnitAction = QtGui.QAction(None)
1339                 self._nextUnitAction.setText("Next Unit")
1340                 self._nextUnitAction.setShortcut(QtGui.QKeySequence("Down"))
1341                 self._nextUnitAction.triggered.connect(self._on_next_unit)
1342
1343                 self._closeWindowAction = QtGui.QAction(None)
1344                 self._closeWindowAction.setText("Close Window")
1345                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
1346                 self._closeWindowAction.triggered.connect(self._on_close_window)
1347
1348                 if constants.IS_MAEMO:
1349                         self._window.addAction(self._closeWindowAction)
1350                         self._window.addAction(self._app.quitAction)
1351                         self._window.addAction(self._app.fullscreenAction)
1352
1353                         fileMenu = self._window.menuBar().addMenu("&Units")
1354                         fileMenu.addAction(self._chooseFavoritesAction)
1355
1356                         viewMenu = self._window.menuBar().addMenu("&View")
1357                         viewMenu.addAction(self._app.showFavoritesAction)
1358                         viewMenu.addAction(self._app.condensedAction)
1359                         viewMenu.addSeparator()
1360                         viewMenu.addAction(self._app.sortByNameAction)
1361                         viewMenu.addAction(self._app.sortByValueAction)
1362                         viewMenu.addAction(self._app.sortByUnitAction)
1363                         viewMenu.addSeparator()
1364                         viewMenu.addAction(self._app.jumpAction)
1365                         viewMenu.addAction(self._app.recentAction)
1366                 else:
1367                         fileMenu = self._window.menuBar().addMenu("&Units")
1368                         fileMenu.addAction(self._chooseFavoritesAction)
1369                         fileMenu.addAction(self._closeWindowAction)
1370                         fileMenu.addAction(self._app.quitAction)
1371
1372                         viewMenu = self._window.menuBar().addMenu("&View")
1373                         viewMenu.addAction(self._app.showFavoritesAction)
1374                         viewMenu.addAction(self._app.condensedAction)
1375                         viewMenu.addSeparator()
1376                         viewMenu.addAction(self._app.sortByNameAction)
1377                         viewMenu.addAction(self._app.sortByValueAction)
1378                         viewMenu.addAction(self._app.sortByUnitAction)
1379                         viewMenu.addSeparator()
1380                         viewMenu.addAction(self._app.jumpAction)
1381                         viewMenu.addAction(self._app.recentAction)
1382                         viewMenu.addSeparator()
1383                         viewMenu.addAction(self._app.fullscreenAction)
1384
1385                 self._app.sortByNameAction.triggered.connect(self._on_sort_by_name)
1386                 self._app.sortByValueAction.triggered.connect(self._on_sort_by_value)
1387                 self._app.sortByUnitAction.triggered.connect(self._on_sort_by_unit)
1388
1389                 self._window.addAction(self._app.logAction)
1390                 self._window.addAction(self._nextUnitAction)
1391                 self._window.addAction(self._previousUnitAction)
1392                 self._window.addAction(self._chooseFavoritesAction)
1393
1394                 self._update_favorites()
1395                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
1396                 self._window.show()
1397
1398         @property
1399         def window(self):
1400                 return self._window
1401
1402         def show(self):
1403                 for child in self.walk_children():
1404                         child.hide()
1405                 self._window.show()
1406
1407         def hide(self):
1408                 for child in self.walk_children():
1409                         child.hide()
1410                 self._window.hide()
1411
1412         def close(self):
1413                 for child in self.walk_children():
1414                         child.window.destroyed.disconnect(self._on_child_close)
1415                         child.close()
1416                 self._window.close()
1417
1418         def set_fullscreen(self, isFullscreen):
1419                 if isFullscreen:
1420                         self._window.showFullScreen()
1421                 else:
1422                         self._window.showNormal()
1423
1424         def select_unit(self, unitName):
1425                 index = self._unitsModel.index_unit(unitName)
1426                 self._select_unit(index)
1427
1428                 qindex = self._unitsModel.createIndex(index, 0, self._unitsModel.get_unit(index))
1429                 self._unitsView.scrollTo(qindex)
1430
1431         def walk_children(self):
1432                 if self._favoritesWindow is not None:
1433                         yield self._favoritesWindow
1434
1435         def _select_unit(self, index):
1436                 unit = self._unitsModel.get_unit(index)
1437                 self._selectedUnitName.setText(unit.name)
1438                 self._selectedUnitValue.setText(str(unit.value))
1439                 self._selectedUnitSymbol.setText(unit.unit)
1440
1441                 self._selectedIndex = index
1442                 self._app.add_recent(self._categoryName, self._unitsModel.get_unit(index).name)
1443
1444         def _update_favorites(self, force = False):
1445                 if self._app.showFavoritesAction.isChecked():
1446                         unitNames = list(self._unitsModel.get_unit_names())
1447                         hiddenUnits = self._app.get_hidden_units(self._categoryName)
1448                         for i, unitName in enumerate(unitNames):
1449                                 if unitName in hiddenUnits:
1450                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), True)
1451                                 else:
1452                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1453                 else:
1454                         if force:
1455                                 for i in xrange(len(self._unitsModel)):
1456                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1457
1458         @misc_utils.log_exception(_moduleLogger)
1459         def _on_show_favorites(self, checked = True):
1460                 if checked:
1461                         unitNames = list(self._unitsModel.get_unit_names())
1462                         hiddenUnits = self._app.get_hidden_units(self._categoryName)
1463                         for i, unitName in enumerate(unitNames):
1464                                 if unitName in hiddenUnits:
1465                                         self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), True)
1466                 else:
1467                         for i in xrange(len(self._unitsModel)):
1468                                 self._unitsView.setRowHidden(i, self._unitsView.rootIndex(), False)
1469
1470         @misc_utils.log_exception(_moduleLogger)
1471         def _on_choose_favorites(self, obj = None):
1472                 assert self._favoritesWindow is None
1473                 import windows
1474                 self._favoritesWindow = windows.FavoritesWindow(
1475                         self._window,
1476                         self._app,
1477                         unit_data.get_units(self._categoryName),
1478                         self._app.get_hidden_units(self._categoryName)
1479                 )
1480                 self._favoritesWindow.window.destroyed.connect(self._on_close_favorites)
1481                 return self._favoritesWindow
1482
1483         @misc_utils.log_exception(_moduleLogger)
1484         def _on_close_favorites(self, obj = None):
1485                 self._favoritesWindow = None
1486                 self._update_favorites(force=True)
1487
1488         @misc_utils.log_exception(_moduleLogger)
1489         def _on_previous_unit(self, checked = True):
1490                 index = self._selectedIndex - 1
1491                 unitData = self._unitsModel.get_unit(index)
1492                 unitName = unitData.name
1493
1494                 if self._app.showFavoritesAction.isChecked():
1495                         hiddenUnits = self._app.get_hidden_units(self._categoryName)
1496                         while unitName in hiddenUnits:
1497                                 index -= 1
1498                                 unitData = self._unitsModel.get_unit(index)
1499                                 unitName = unitData.name
1500
1501                 self.select_unit(unitName)
1502
1503         @misc_utils.log_exception(_moduleLogger)
1504         def _on_next_unit(self, checked = True):
1505                 index = self._selectedIndex + 1
1506                 unitData = self._unitsModel.get_unit(index)
1507                 unitName = unitData.name
1508
1509                 if self._app.showFavoritesAction.isChecked():
1510                         hiddenUnits = self._app.get_hidden_units(self._categoryName)
1511                         while unitName in hiddenUnits:
1512                                 index += 1
1513                                 unitData = self._unitsModel.get_unit(index)
1514                                 unitName = unitData.name
1515
1516                 self.select_unit(unitName)
1517
1518         @misc_utils.log_exception(_moduleLogger)
1519         def _on_close_window(self, checked = True):
1520                 self.close()
1521
1522         @misc_utils.log_exception(_moduleLogger)
1523         def _on_sort_by_name(self, checked = False):
1524                 self._unitsModel.sort(UnitData.NAME_COLUMN, QtCore.Qt.DescendingOrder)
1525
1526         @misc_utils.log_exception(_moduleLogger)
1527         def _on_sort_by_value(self, checked = False):
1528                 self._unitsModel.sort(UnitData.VALUE_COLUMN_0)
1529
1530         @misc_utils.log_exception(_moduleLogger)
1531         def _on_sort_by_unit(self, checked = False):
1532                 self._unitsModel.sort(UnitData.UNIT_COLUMN, QtCore.Qt.DescendingOrder)
1533
1534         @misc_utils.log_exception(_moduleLogger)
1535         def _on_unit_clicked(self, index):
1536                 self._select_unit(index.row())
1537
1538         @misc_utils.log_exception(_moduleLogger)
1539         def _on_value_edited(self, *args):
1540                 if not self._updateDelayTimer.isActive():
1541                         self._updateDelayTimer.start()
1542
1543         @misc_utils.log_exception(_moduleLogger)
1544         def _on_value_edited_delayed(self, *args):
1545                 userInput = str(self._selectedUnitValue.text())
1546                 orderChanged = self._unitsModel.update_values(self._selectedIndex, userInput)
1547                 if orderChanged:
1548                         self._update_favorites()
1549
1550
1551 def run_gonvert():
1552         app = QtGui.QApplication([])
1553         handle = Gonvert(app)
1554         if constants.PROFILE_STARTUP:
1555                 return 0
1556         else:
1557                 return app.exec_()
1558
1559
1560 if __name__ == "__main__":
1561         import sys
1562         logging.basicConfig(level = logging.DEBUG)
1563         try:
1564                 os.makedirs(constants._data_path_)
1565         except OSError, e:
1566                 if e.errno != 17:
1567                         raise
1568
1569         val = run_gonvert()
1570         sys.exit(val)