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