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