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