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