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