Defaulting all columns
[gonvert] / src / gonvert_glade.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3
4 """
5 @todo Look into using two columns for displaying the value, split by the
6 decimal place.  The left one would be right aligned and the right would be left
7 aligned (only if not in exponential notation
8 OR display everything in engineering notation
9
10 @tood Add a unit description dialog for when hildonized
11
12 @todo Add support for custom units
13
14 @todo Add support for compound units
15 """
16
17 import os
18 import math
19 import pickle
20 import logging
21
22 import pango
23 import gobject
24 import gtk
25 import gtk.glade
26 import gtk.gdk
27
28 import constants
29 import hildonize
30 import unit_data
31
32 try:
33         import gettext
34 except ImportError:
35         _ = lambda x: x
36         gettext = None
37 else:
38         _ = gettext.gettext
39
40
41 _moduleLogger = logging.getLogger("gonvert_glade")
42 PROFILE_STARTUP = False
43 FORCE_HILDON_LIKE = False
44
45 if gettext is not None:
46         gettext.bindtextdomain('gonvert', '/usr/share/locale')
47         gettext.textdomain('gonvert')
48
49
50 def change_menu_label(widgets, labelname, newtext):
51         item_label = widgets.get_widget(labelname).get_children()[0]
52         item_label.set_text(newtext)
53
54
55 def split_number(number):
56         try:
57                 fractional, integer = math.modf(number)
58         except TypeError:
59                 integerDisplay = number
60                 fractionalDisplay = ""
61         else:
62                 integerDisplay = str(integer)
63                 fractionalDisplay = str(fractional)
64                 if "e+" in integerDisplay:
65                         integerDisplay = number
66                         fractionalDisplay = ""
67                 elif "e-" in fractionalDisplay and 0.0 < integer:
68                         integerDisplay = number
69                         fractionalDisplay = ""
70                 elif "e-" in fractionalDisplay:
71                         integerDisplay = ""
72                         fractionalDisplay = number
73                 else:
74                         integerDisplay = integerDisplay.split(".", 1)[0] + "."
75                         fractionalDisplay = fractionalDisplay.rsplit(".", 1)[-1]
76
77         return integerDisplay, fractionalDisplay
78
79
80 class Gonvert(object):
81
82         _glade_files = [
83                 os.path.join(os.path.dirname(__file__), "gonvert.glade"),
84                 os.path.join(os.path.dirname(__file__), "../data/gonvert.glade"),
85                 os.path.join(os.path.dirname(__file__), "../lib/gonvert.glade"),
86                 '/usr/share/gonvert/gonvert.glade',
87                 '/usr/lib/gonvert/gonvert.glade',
88         ]
89
90         UNITS_NAME_IDX = 0
91         UNITS_VALUE_IDX = 1
92         UNITS_SYMBOL_IDX = 2
93         UNITS_INTEGER_IDX = 3
94         UNITS_FRACTION_IDX = 4
95
96         def __init__(self):
97                 self._unitDataInCategory = None
98                 self._unit_sort_direction = False
99                 self._value_sort_direction = False
100                 self._units_sort_direction = False
101                 self._isFullScreen = False
102
103                 self._find_result = [] # empty find result list
104                 self._findIndex = 0 # default to find result number zero
105
106                 self._selectedCategoryName = '' # preset to no selected category
107                 self._defaultUnitForCategory = {} # empty dictionary for later use
108
109                 #check to see if glade file is in current directory (user must be
110                 # running from download untar directory)
111                 for gladePath in self._glade_files:
112                         if os.path.isfile(gladePath):
113                                 homepath = os.path.dirname(gladePath)
114                                 pixmapspath = "/".join((homepath, "pixmaps"))
115                                 widgets = gtk.glade.XML(gladePath)
116                                 break
117                 else:
118                         _moduleLogger.error("UI Descriptor not found!")
119                         gtk.main_quit()
120                         return
121
122                 self._mainWindow = widgets.get_widget('mainWindow')
123                 self._app = hildonize.get_app_class()()
124                 self._mainWindow = hildonize.hildonize_window(self._app, self._mainWindow)
125
126                 change_menu_label(widgets, 'fileMenuItem', _('File'))
127                 change_menu_label(widgets, 'exitMenuItem', _('Exit'))
128                 change_menu_label(widgets, 'toolsMenuItem', _('Tools'))
129                 change_menu_label(widgets, 'clearSelectionMenuItem', _('Clear selections'))
130                 change_menu_label(widgets, 'helpMenuItem', _('Help'))
131                 change_menu_label(widgets, 'aboutMenuItem', _('About'))
132                 change_menu_label(widgets, 'findButton', _('Find'))
133
134                 self._shortlistcheck = widgets.get_widget('shortlistcheck')
135                 self._toggleShortList = widgets.get_widget('toggleShortList')
136
137                 self._categorySelectionButton = widgets.get_widget("categorySelectionButton")
138                 self._categoryView = widgets.get_widget('categoryView')
139
140                 self._unitsView = widgets.get_widget('unitsView')
141                 self._unitsView.set_property('rules_hint', 1)
142                 self._unitsView_selection = self._unitsView.get_selection()
143
144                 self._unitName = widgets.get_widget('unitName')
145                 self._unitValue = widgets.get_widget('unitValue')
146                 self._previousUnitName = widgets.get_widget('previousUnitName')
147                 self._previousUnitValue = widgets.get_widget('previousUnitValue')
148
149                 self._unitSymbol = widgets.get_widget('unitSymbol')
150                 self._previousUnitSymbol = widgets.get_widget('previousUnitSymbol')
151
152                 self._unitDescription = widgets.get_widget('unitDescription')
153
154                 self._searchLayout = widgets.get_widget('searchLayout')
155                 self._searchLayout.hide()
156                 self._findEntry = widgets.get_widget('findEntry')
157                 self._findLabel = widgets.get_widget('findLabel')
158                 self._findButton = widgets.get_widget('findButton')
159
160                 #insert a self._categoryColumnumn into the units list even though the heading will not be seen
161                 renderer = gtk.CellRendererText()
162                 renderer.set_property("ellipsize", pango.ELLIPSIZE_END)
163                 renderer.set_property("width-chars", len("grams per cubic cm plus some"))
164                 self._unitNameColumn = gtk.TreeViewColumn(_('Name'), renderer)
165                 self._unitNameColumn.set_property('resizable', True)
166                 self._unitNameColumn.add_attribute(renderer, 'text', self.UNITS_NAME_IDX)
167                 self._unitNameColumn.set_clickable(True)
168                 self._unitNameColumn.connect("clicked", self._on_click_unit_column)
169                 self._unitsView.append_column(self._unitNameColumn)
170
171                 renderer = gtk.CellRendererText()
172                 renderer.set_property("xalign", 1.0)
173                 renderer.set_property("alignment", pango.ALIGN_RIGHT)
174                 hildonize.set_cell_thumb_selectable(renderer)
175                 self._unitIntegerColumn = gtk.TreeViewColumn(_('Value'), renderer)
176                 self._unitIntegerColumn.set_property('resizable', True)
177                 self._unitIntegerColumn.add_attribute(renderer, 'text', self.UNITS_INTEGER_IDX)
178                 self._unitIntegerColumn.set_clickable(True)
179                 self._unitIntegerColumn.connect("clicked", self._on_click_unit_column)
180                 self._unitsView.append_column(self._unitIntegerColumn)
181
182                 renderer = gtk.CellRendererText()
183                 renderer.set_property("xalign", 0.0)
184                 renderer.set_property("alignment", pango.ALIGN_LEFT)
185                 self._unitFractionalColumn = gtk.TreeViewColumn(_(''), renderer)
186                 self._unitFractionalColumn.set_property('resizable', True)
187                 self._unitFractionalColumn.add_attribute(renderer, 'text', self.UNITS_FRACTION_IDX)
188                 self._unitFractionalColumn.set_clickable(True)
189                 self._unitFractionalColumn.connect("clicked", self._on_click_unit_column)
190                 self._unitsView.append_column(self._unitFractionalColumn)
191
192                 renderer = gtk.CellRendererText()
193                 renderer.set_property("ellipsize", pango.ELLIPSIZE_END)
194                 renderer.set_property("width-chars", len("G ohm plus some"))
195                 self._unitSymbolColumn = gtk.TreeViewColumn(_('Units'), renderer)
196                 self._unitSymbolColumn.set_property('resizable', True)
197                 self._unitSymbolColumn.add_attribute(renderer, 'text', self.UNITS_SYMBOL_IDX)
198                 self._unitSymbolColumn.set_clickable(True)
199                 self._unitSymbolColumn.connect("clicked", self._on_click_unit_column)
200                 self._unitsView.append_column(self._unitSymbolColumn)
201
202                 self._unitModel = gtk.ListStore(
203                         gobject.TYPE_STRING, # UNITS_NAME_IDX
204                         gobject.TYPE_STRING, # UNITS_VALUE_IDX
205                         gobject.TYPE_STRING, # UNITS_SYMBOL_IDX
206                         gobject.TYPE_STRING, # UNITS_INTEGER_IDX
207                         gobject.TYPE_STRING, # UNITS_FRACTION_IDX
208                 )
209                 self._sortedUnitModel = gtk.TreeModelSort(self._unitModel)
210                 columns = self._get_column_sort_stuff()
211                 for columnIndex, (column, sortDirection, col_cmp) in enumerate(columns):
212                         self._sortedUnitModel.set_sort_func(columnIndex, col_cmp)
213                 self._unitsView.set_model(self._sortedUnitModel)
214
215                 #Insert a column into the category list even though the heading will not be seen
216                 renderer = gtk.CellRendererText()
217                 self._categoryColumn = gtk.TreeViewColumn('Title', renderer)
218                 self._categoryColumn.set_property('resizable', 1)
219                 self._categoryColumn.add_attribute(renderer, 'text', 0)
220                 self._categoryView.append_column(self._categoryColumn)
221
222                 self._categoryModel = gtk.ListStore(gobject.TYPE_STRING)
223                 self._categoryView.set_model(self._categoryModel)
224                 #colourize each row differently for easier reading
225                 self._categoryView.set_property('rules_hint', 1)
226
227                 #Populate the catagories list
228                 for key in unit_data.UNIT_CATEGORIES:
229                         row = (key, )
230                         self._categoryModel.append(row)
231
232                 #--------- connections to GUI ----------------
233                 self._mainWindow.connect("delete-event", self._on_user_exit)
234                 self._mainWindow.connect("key-press-event", self._on_key_press)
235                 self._mainWindow.connect("window-state-event", self._on_window_state_change)
236                 self._categorySelectionButton.connect("clicked", self._on_category_selector_clicked)
237                 self._categoryView.connect("cursor-changed", self._on_click_category)
238                 self._findButton.connect("clicked", self._on_find_activate)
239                 self._findEntry.connect("activate", self._on_find_activate)
240                 self._findEntry.connect("changed", self._on_findEntry_changed)
241                 self._previousUnitValue.connect("changed", self._on_previous_unit_value_changed)
242                 self._shortlistcheck.connect("toggled", self._on_shortlist_changed)
243                 self._unitValue.connect("changed", self._on_unit_value_changed)
244                 self._unitsView.connect("cursor-changed", self._on_click_unit)
245                 if hildonize.GTK_MENU_USED:
246                         widgets.get_widget("aboutMenuItem").connect("activate", self._on_about_clicked)
247                         widgets.get_widget("clearSelectionMenuItem").connect("activate", self._on_user_clear_selections)
248                         widgets.get_widget("editShortListMenuItem").connect("activate", self._on_edit_shortlist)
249                         widgets.get_widget("exitMenuItem").connect("activate", self._on_user_exit)
250
251                 for scrollingWidgetName in (
252                         "unitsViewScrolledWindow",
253                 ):
254                         scrollingWidget = widgets.get_widget(scrollingWidgetName)
255                         assert scrollingWidget is not None, scrollingWidgetName
256                         hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
257
258                 if hildonize.IS_HILDON_SUPPORTED or FORCE_HILDON_LIKE:
259                         self._categoryView.get_parent().hide()
260                         self._unitsView.set_headers_visible(False)
261                         self._previousUnitName.get_parent().hide()
262                         self._unitDescription.get_parent().get_parent().hide()
263                 else:
264                         self._categorySelectionButton.hide()
265
266                 replacementButtons = []
267                 menu = hildonize.hildonize_menu(
268                         self._mainWindow,
269                         widgets.get_widget("mainMenuBar"),
270                         replacementButtons
271                 )
272
273                 if not hildonize.IS_HILDON_SUPPORTED:
274                         _moduleLogger.info("No hildonization support")
275
276                 hildonize.set_application_title(
277                         self._mainWindow, "%s - Unit Conversion Utility" % constants.__pretty_app_name__
278                 )
279                 iconPath = pixmapspath + '/gonvert.png'
280                 if os.path.exists(iconPath):
281                         self._mainWindow.set_icon(gtk.gdk.pixbuf_new_from_file(iconPath))
282                 else:
283                         _moduleLogger.warn("Error: Could not find gonvert icon: %s" % iconPath)
284
285                 self._load_settings()
286                 self._mainWindow.show()
287
288         def _load_settings(self):
289                 #Restore window size from previously saved settings if it exists and is valid.
290                 windowDatPath = "/".join((constants._data_path_, "window.dat"))
291                 if os.path.exists(windowDatPath):
292                         saved_window = pickle.load(open(windowDatPath, "r"))
293                         try:
294                                 a, b = saved_window['size']
295                         except KeyError:
296                                 pass
297                         else:
298                                 self._mainWindow.resize(a, b)
299
300                 #Restore selections from previously saved settings if it exists and is valid.
301                 categoryIndex = 0
302                 selectedCategoryName = unit_data.UNIT_CATEGORIES[0]
303                 selectionsDatPath = "/".join((constants._data_path_, "selections.dat"))
304                 if os.path.exists(selectionsDatPath):
305                         selections = pickle.load(open(selectionsDatPath, 'r'))
306                         try:
307                                 self._defaultUnitForCategory = selections['selected_units']
308                         except KeyError:
309                                 pass
310
311                         try:
312                                 selectedCategoryName = selections['selected_category']
313                         except KeyError:
314                                 pass
315                         else:
316                                 try:
317                                         categoryIndex = unit_data.UNIT_CATEGORIES.index(selectedCategoryName)
318                                 except ValueError:
319                                         _moduleLogger.warn("Unknown category: %s" % selectedCategoryName)
320
321                 self._categorySelectionButton.get_child().set_markup("<big>%s</big>" % selectedCategoryName)
322                 self._categoryView.set_cursor(categoryIndex, self._categoryColumn, False)
323                 self._categoryView.grab_focus()
324
325                 self._select_default_unit()
326
327         def _save_settings(self):
328                 """
329                 This routine saves the selections to a file, and
330                 should therefore only be called when exiting the program.
331
332                 Update selections dictionary which consists of the following keys:
333                 'self._selectedCategoryName': full name of selected category
334                 'self._defaultUnitForCategory': self._defaultUnitForCategory dictionary which contains:
335                 [categoryname: #1 displayed unit, #2 displayed unit]
336                 """
337                 #Determine the contents of the selected category row
338                 selected, iter = self._categoryView.get_selection().get_selected()
339                 self._selectedCategoryName = self._categoryModel.get_value(iter, 0)
340
341                 selections = {
342                         'selected_category': self._selectedCategoryName,
343                         'selected_units': self._defaultUnitForCategory
344                 }
345                 selectionsDatPath = "/".join((constants._data_path_, "selections.dat"))
346                 pickle.dump(selections, open(selectionsDatPath, 'w'))
347
348                 #Get last size of app and save it
349                 window_settings = {
350                         'size': self._mainWindow.get_size()
351                 }
352                 windowDatPath = "/".join((constants._data_path_, "window.dat"))
353                 pickle.dump(window_settings, open(windowDatPath, 'w'))
354
355         def _clear_find(self):
356                 # switch to "new find" state
357                 self._find_result = []
358                 self._findIndex = 0
359
360                 # Clear our user message
361                 self._findLabel.set_text('')
362
363         def _find_first(self):
364                 assert len(self._find_result) == 0
365                 assert self._findIndex == 0
366                 findString = self._findEntry.get_text().strip().lower()
367                 if not findString:
368                         return
369
370                 # Gather info on all the matching units from all categories
371                 for catIndex, category in enumerate(unit_data.UNIT_CATEGORIES):
372                         units = unit_data.get_units(category)
373                         for unitIndex, unit in enumerate(units):
374                                 loweredUnit = unit.lower()
375                                 if loweredUnit in findString or findString in loweredUnit:
376                                         self._find_result.append((category, unit, catIndex, unitIndex))
377
378         def _update_find_selection(self):
379                 assert 0 < len(self._find_result)
380
381                 #check if next find is in a new category (prevent category changes when unnecessary
382                 searchCategoryName = self._find_result[self._findIndex][0]
383                 if self._selectedCategoryName != searchCategoryName:
384                         self._categorySelectionButton.get_child().set_markup("<big>%s</big>" % searchCategoryName)
385                         self._categoryView.set_cursor(
386                                 self._find_result[self._findIndex][2], self._categoryColumn, False
387                         )
388
389                 self._unitsView.set_cursor(
390                         self._find_result[self._findIndex][3], self._unitNameColumn, True
391                 )
392
393         def _find_next(self):
394                 if len(self._find_result) == 0:
395                         self._find_first()
396                 else:
397                         if self._findIndex == len(self._find_result)-1:
398                                 self._findIndex = 0
399                         else:
400                                 self._findIndex += 1
401
402                 if not self._find_result:
403                         self._findLabel.set_text('Text not found')
404                 else:
405                         self._update_find_selection()
406                         resultsLeft = len(self._find_result) - self._findIndex - 1
407                         self._findLabel.set_text(
408                                 '%s result(s) left' % (resultsLeft, )
409                         )
410
411         def _find_previous(self):
412                 if len(self._find_result) == 0:
413                         self._find_first()
414                 else:
415                         if self._findIndex == 0:
416                                 self._findIndex = len(self._find_result)-1
417                         else:
418                                 self._findIndex -= 1
419
420                 if not self._find_result:
421                         self._findLabel.set_text('Text not found')
422                 else:
423                         self._update_find_selection()
424                         resultsLeft = len(self._find_result) - self._findIndex - 1
425                         self._findLabel.set_text(
426                                 '%s result(s) left' % (resultsLeft, )
427                         )
428
429         def _toggle_find(self):
430                 if self._searchLayout.get_property("visible"):
431                         self._searchLayout.hide()
432                         self._unitsView.grab_focus()
433                 else:
434                         self._searchLayout.show()
435                         self._findEntry.grab_focus()
436
437         def _unit_model_cmp(self, sortedModel, leftItr, rightItr):
438                 leftUnitText = self._unitModel.get_value(leftItr, self.UNITS_NAME_IDX)
439                 rightUnitText = self._unitModel.get_value(rightItr, self.UNITS_NAME_IDX)
440                 return cmp(leftUnitText, rightUnitText)
441
442         def _symbol_model_cmp(self, sortedModel, leftItr, rightItr):
443                 leftSymbolText = self._unitModel.get_value(leftItr, self.UNITS_SYMBOL_IDX)
444                 rightSymbolText = self._unitModel.get_value(rightItr, self.UNITS_SYMBOL_IDX)
445                 return cmp(leftSymbolText, rightSymbolText)
446
447         def _value_model_cmp(self, sortedModel, leftItr, rightItr):
448                 #special sorting exceptions for ascii values (instead of float values)
449                 if self._selectedCategoryName == "Computer Numbers":
450                         leftValue = self._unitModel.get_value(leftItr, self.UNITS_VALUE_IDX)
451                         rightValue = self._unitModel.get_value(rightItr, self.UNITS_VALUE_IDX)
452                 else:
453                         leftValueText = self._unitModel.get_value(leftItr, self.UNITS_VALUE_IDX)
454                         leftValue = float(leftValueText) if leftValueText else 0.0
455
456                         rightValueText = self._unitModel.get_value(rightItr, self.UNITS_VALUE_IDX)
457                         rightValue = float(rightValueText) if rightValueText else 0.0
458                 return cmp(leftValue, rightValue)
459
460         def _get_column_sort_stuff(self):
461                 columns = (
462                         (self._unitNameColumn, "_unit_sort_direction", self._unit_model_cmp),
463                         (self._unitIntegerColumn, "_value_sort_direction", self._value_model_cmp),
464                         (self._unitFractionalColumn, "_value_sort_direction", self._value_model_cmp),
465                         (self._unitSymbolColumn, "_units_sort_direction", self._symbol_model_cmp),
466                 )
467                 return columns
468
469         def _switch_category(self, category):
470                 self._selectedCategoryName = category
471                 self._unitDataInCategory = unit_data.UNIT_DESCRIPTIONS[self._selectedCategoryName]
472
473                 #Fill up the units descriptions and clear the value cells
474                 self._clear_visible_unit_data()
475                 for key in unit_data.get_units(self._selectedCategoryName):
476                         row = key, '0.0', self._unitDataInCategory[key][1], '0.', '0'
477                         self._unitModel.append(row)
478                 self._sortedUnitModel.sort_column_changed()
479
480                 self._select_default_unit()
481
482         def _clear_visible_unit_data(self):
483                 self._unitDescription.get_buffer().set_text("")
484                 self._unitName.set_text('')
485                 self._unitValue.set_text('')
486                 self._unitSymbol.set_text('')
487
488                 self._previousUnitName.set_text('')
489                 self._previousUnitValue.set_text('')
490                 self._previousUnitSymbol.set_text('')
491
492                 self._unitModel.clear()
493
494         def _select_default_unit(self):
495                 # Restore the previous historical settings of previously selected units
496                 # in this newly selected category
497                 defaultPrimary = unit_data.get_base_unit(self._selectedCategoryName)
498                 defaultSecondary = ""
499                 if self._selectedCategoryName in self._defaultUnitForCategory:
500                         if self._defaultUnitForCategory[self._selectedCategoryName][0]:
501                                 defaultPrimary = self._defaultUnitForCategory[self._selectedCategoryName][0]
502                         if self._defaultUnitForCategory[self._selectedCategoryName][1]:
503                                 defaultSecondary = self._defaultUnitForCategory[self._selectedCategoryName][1]
504
505                 units = unit_data.get_units(self._selectedCategoryName)
506
507                 #Restore oldest selection first.
508                 if defaultPrimary:
509                         try:
510                                 unitIndex = units.index(defaultPrimary)
511                         except ValueError:
512                                 unitIndex = 0
513                         self._unitsView.set_cursor(unitIndex, self._unitNameColumn, True)
514
515                 #Restore newest selection second.
516                 if defaultSecondary:
517                         try:
518                                 unitIndex = units.index(defaultSecondary)
519                         except ValueError:
520                                 unitIndex = 0
521                         self._unitsView.set_cursor(unitIndex, self._unitNameColumn, True)
522
523                 # select the text so user can start typing right away
524                 self._unitValue.grab_focus()
525                 self._unitValue.select_region(0, -1)
526
527         def _sanitize_value(self, userEntry):
528                 if self._selectedCategoryName == "Computer Numbers":
529                         if userEntry == '':
530                                 value = '0'
531                         else:
532                                 value = userEntry
533                 else:
534                         if userEntry == '':
535                                 value = 0.0
536                         else:
537                                 value = float(userEntry)
538                 return value
539
540         def _on_shortlist_changed(self, *args):
541                 try:
542                         raise NotImplementedError("%s" % self._shortlistcheck.get_active())
543                 except Exception:
544                         _moduleLogger.exception("_on_shortlist_changed")
545
546         def _on_edit_shortlist(self, *args):
547                 try:
548                         raise NotImplementedError("%s" % self._toggleShortList.get_active())
549                 except Exception:
550                         _moduleLogger.exception("_on_edit_shortlist")
551
552         def _on_user_clear_selections(self, *args):
553                 try:
554                         selectionsDatPath = "/".join((constants._data_path_, "selections.dat"))
555                         os.remove(selectionsDatPath)
556                         self._defaultUnitForCategory = {}
557                 except Exception:
558                         _moduleLogger.exception("_on_user_clear_selections")
559
560         def _on_key_press(self, widget, event, *args):
561                 """
562                 @note Hildon specific
563                 """
564                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
565                 try:
566                         if (
567                                 event.keyval == gtk.keysyms.F6 or
568                                 event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
569                         ):
570                                 if self._isFullScreen:
571                                         self._mainWindow.unfullscreen()
572                                 else:
573                                         self._mainWindow.fullscreen()
574                         elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK:
575                                 self._toggle_find()
576                         elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK:
577                                 self._find_previous()
578                         elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK:
579                                 self._find_next()
580                 except Exception, e:
581                         _moduleLogger.exception("_on_key_press")
582
583         def _on_window_state_change(self, widget, event, *args):
584                 """
585                 @note Hildon specific
586                 """
587                 try:
588                         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
589                                 self._isFullScreen = True
590                         else:
591                                 self._isFullScreen = False
592                 except Exception, e:
593                         _moduleLogger.exception("_on_window_state_change")
594
595         def _on_findEntry_changed(self, *args):
596                 """
597                 Clear out find results since the user wants to look for something new
598                 """
599                 try:
600                         self._clear_find()
601                 except Exception:
602                         _moduleLogger.exception("_on_findEntry_changed")
603
604         def _on_find_activate(self, *args):
605                 try:
606                         self._find_next()
607                         self._findButton.grab_focus()
608                 except Exception:
609                         _moduleLogger.exception("_on_find_activate")
610
611         def _on_click_unit_column(self, col):
612                 """
613                 Sort the contents of the col when the user clicks on the title.
614                 """
615                 try:
616                         #Determine which column requires sorting
617                         columns = self._get_column_sort_stuff()
618                         for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns):
619                                 if col is maybeCol:
620                                         direction = getattr(self, directionName)
621                                         gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING
622
623                                         # cause a sort
624                                         self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection)
625
626                                         # set the visual for sorting
627                                         col.set_sort_indicator(True)
628                                         col.set_sort_order(not direction)
629
630                                         setattr(self, directionName, not direction)
631                                         break
632                                 else:
633                                         maybeCol.set_sort_indicator(False)
634                         else:
635                                 assert False, "Unknown column: %s" % (col.get_title(), )
636                 except Exception:
637                         _moduleLogger.exception("_on_click_unit_column")
638
639         def _on_category_selector_clicked(self, *args):
640                 try:
641                         currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName)
642                         newIndex = hildonize.touch_selector(
643                                 self._mainWindow,
644                                 "Categories",
645                                 unit_data.UNIT_CATEGORIES,
646                                 currenntIndex,
647                         )
648
649                         selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex]
650                         self._categorySelectionButton.set_label(selectedCategoryName)
651                         self._categoryView.set_cursor(newIndex, self._categoryColumn, False)
652                         self._categoryView.grab_focus()
653                 except Exception:
654                         _moduleLogger.exception("_on_category_selector_clicked")
655
656         def _on_click_category(self, *args):
657                 try:
658                         selected, iter = self._categoryView.get_selection().get_selected()
659                         if iter is None:
660                                 # User is typing in an invalid string, not selecting any category
661                                 return
662                         selectedCategory = self._categoryModel.get_value(iter, 0)
663                         self._switch_category(selectedCategory)
664                 except Exception:
665                         _moduleLogger.exception("_on_click_category")
666
667         def _on_click_unit(self, *args):
668                 try:
669                         selected, iter = self._unitsView.get_selection().get_selected()
670                         selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX)
671                         unit_spec = self._unitDataInCategory[selected_unit]
672
673                         showSymbol = False
674
675                         if self._unitName.get_text() != selected_unit:
676                                 self._previousUnitName.set_text(self._unitName.get_text())
677                                 self._previousUnitValue.set_text(self._unitValue.get_text())
678                                 self._previousUnitSymbol.set_text(self._unitSymbol.get_text())
679                                 if self._unitSymbol.get_text():
680                                         showSymbol = True
681
682                         self._unitName.set_text(selected_unit)
683                         self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX))
684                         buffer = self._unitDescription.get_buffer()
685                         buffer.set_text(unit_spec[2])
686                         self._unitSymbol.set_text(unit_spec[1]) # put units into label text
687                         if unit_spec[1]:
688                                 showSymbol = True
689                         else:
690                                 showSymbol = False
691
692                         if showSymbol:
693                                 self._unitSymbol.show()
694                                 self._previousUnitSymbol.show()
695                         else:
696                                 self._unitSymbol.hide()
697                                 self._previousUnitSymbol.hide()
698
699                         if self._unitValue.get_text() == '':
700                                 if self._selectedCategoryName == "Computer Numbers":
701                                         self._unitValue.set_text("0")
702                                 else:
703                                         self._unitValue.set_text("0.0")
704
705                         self._defaultUnitForCategory[self._selectedCategoryName] = [
706                                 self._unitName.get_text(), self._previousUnitName.get_text()
707                         ]
708
709                         # select the text so user can start typing right away
710                         self._unitValue.grab_focus()
711                         self._unitValue.select_region(0, -1)
712                 except Exception:
713                         _moduleLogger.exception("_on_click_unit")
714
715         def _on_unit_value_changed(self, *args):
716                 try:
717                         if self._unitName.get_text() == '':
718                                 return
719                         if not self._unitValue.is_focus():
720                                 return
721
722                         #retrieve the conversion function and value from the selected unit
723                         value = self._sanitize_value(self._unitValue.get_text())
724                         func, arg = self._unitDataInCategory[self._unitName.get_text()][0]
725                         base = func.to_base(value, arg)
726
727                         #point to the first row
728                         for row in self._unitModel:
729                                 func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0]
730                                 newValue = func.from_base(base, arg)
731
732                                 newValueDisplay = str(newValue)
733                                 integerDisplay, fractionalDisplay = split_number(newValue)
734
735                                 row[self.UNITS_VALUE_IDX] = newValueDisplay
736                                 row[self.UNITS_INTEGER_IDX] = integerDisplay
737                                 row[self.UNITS_FRACTION_IDX] = fractionalDisplay
738
739                         # Update the secondary unit entry
740                         if self._previousUnitName.get_text() != '':
741                                 func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0]
742                                 self._previousUnitValue.set_text(str(func.from_base(base, arg, )))
743
744                         self._sortedUnitModel.sort_column_changed()
745                 except Exception:
746                         _moduleLogger.exception("_on_unit_value_changed")
747
748         def _on_previous_unit_value_changed(self, *args):
749                 try:
750                         if self._previousUnitName.get_text() == '':
751                                 return
752                         if not self._previousUnitValue.is_focus():
753                                 return
754
755                         #retrieve the conversion function and value from the selected unit
756                         value = self._sanitize_value(self._previousUnitValue.get_text())
757                         func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0]
758                         base = func.to_base(value, arg)
759
760                         #point to the first row
761                         for row in self._unitModel:
762                                 func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0]
763                                 newValue = func.from_base(base, arg)
764
765                                 newValueDisplay = str(newValue)
766                                 integerDisplay, fractionalDisplay = split_number(newValue)
767
768                                 row[self.UNITS_VALUE_IDX] = newValueDisplay
769                                 row[self.UNITS_INTEGER_IDX] = integerDisplay
770                                 row[self.UNITS_FRACTION_IDX] = fractionalDisplay
771
772                         # Update the primary unit entry
773                         func, arg = self._unitDataInCategory[self._unitName.get_text()][0]
774                         self._unitValue.set_text(str(func.from_base(base, arg, )))
775
776                         self._sortedUnitModel.sort_column_changed()
777                 except Exception:
778                         _moduleLogger.exception("_on_previous_unit_value_changed")
779
780         def _on_about_clicked(self, a):
781                 dlg = gtk.AboutDialog()
782                 dlg.set_name(constants.__pretty_app_name__)
783                 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
784                 dlg.set_copyright("Copyright 2009 - GPL")
785                 dlg.set_comments("")
786                 dlg.set_website("http://unihedron.com/projects/gonvert/gonvert.php")
787                 dlg.set_authors(["Anthony Tekatch <anthony@unihedron.com>", "Ed Page <edpage@byu.net>"])
788                 dlg.run()
789                 dlg.destroy()
790
791         def _on_user_exit(self, *args):
792                 try:
793                         self._save_settings()
794                 except Exception:
795                         _moduleLogger.exception("_on_user_exit")
796                 finally:
797                         gtk.main_quit()
798
799
800 def run_gonvert():
801         gtk.gdk.threads_init()
802         if hildonize.IS_HILDON_SUPPORTED:
803                 gtk.set_application_name(constants.__pretty_app_name__)
804         handle = Gonvert()
805         if not PROFILE_STARTUP:
806                 gtk.main()
807
808
809 if __name__ == "__main__":
810         logging.basicConfig(level = logging.DEBUG)
811         try:
812                 os.makedirs(constants._data_path_)
813         except OSError, e:
814                 if e.errno != 17:
815                         raise
816
817         run_gonvert()