Pulling in my skeleton
authorEd Page <eopage@byu.net>
Mon, 31 May 2010 15:00:14 +0000 (10:00 -0500)
committerEd Page <eopage@byu.net>
Mon, 31 May 2010 15:00:14 +0000 (10:00 -0500)
1  2 
Makefile
src/gonvert_glade.py
src/gtk_toolbox.py
src/hildonize.py
support/builddeb.py

diff --combined Makefile
+++ b/Makefile
@@@ -1,10 -1,9 +1,10 @@@
 -PROJECT_NAME=REPLACEME
 +PROJECT_NAME=gonvert
  SOURCE_PATH=src
  SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py")
  PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py
 +DATA_PATH=data
  DATA_TYPES=*.ini *.map *.glade *.png
 -DATA=$(foreach type, $(DATA_TYPES), $(shell find $(SOURCE_PATH) -iname "$(type)"))
 +DATA=$(foreach type, $(DATA_TYPES), $(shell find $(DATA_PATH) -iname "$(type)"))
  OBJ=$(SOURCE:.py=.pyc)
  BUILD_PATH=./build
  TAG_FILE=~/.ctags/$(PROJECT_NAME).tags
@@@ -26,7 -25,7 +26,7 @@@ CTAGS=ctags-exuberan
  all: test
  
  run: $(OBJ)
 -      $(SOURCE_PATH)/$(PROJECT_NAME).py
 +      $(SOURCE_PATH)/$(PROJECT_NAME)_glade.py
  
  profile: $(OBJ)
        $(PROFILE_GEN) $(PROGRAM)
@@@ -46,10 -45,14 +46,10 @@@ package: $(OBJ
        cp $(SOURCE_PATH)/$(PROJECT_NAME).py  $(BUILD_PATH)/generic
        $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
        $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
--      #$(foreach file, $(OBJ), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
        cp support/$(PROJECT_NAME).desktop $(BUILD_PATH)/generic
 -      cp support/icons/hicolor/26x26/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/26x26-$(PROJECT_NAME).png
 -      cp support/icons/hicolor/64x64/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/64x64-$(PROJECT_NAME).png
 -      cp support/icons/hicolor/scalable/hildon/$(PROJECT_NAME).png $(BUILD_PATH)/generic/scale-$(PROJECT_NAME).png
        cp support/builddeb.py $(BUILD_PATH)/generic
        cp support/py2deb.py $(BUILD_PATH)/generic
+       cp support/fake_py2deb.py $(BUILD_PATH)/generic
  
        mkdir -p $(BUILD_PATH)/diablo
        cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/diablo
@@@ -64,7 -67,7 +64,7 @@@
  upload:
        dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes
        dput diablo-extras-builder $(BUILD_PATH)/diablo/$(PROJECT_NAME)*.changes
-       cp $(BUILD_PATH)/debian/*.deb www/$(PROJECT_NAME).deb
+       cp $(BUILD_PATH)/debian/*.deb ./www/$(PROJECT_NAME).deb
  
  lint: $(OBJ)
        $(foreach file, $(SOURCE), $(LINT) $(file) ; )
diff --combined src/gonvert_glade.py
index 1ffc3ae,0000000..e42b1e4
mode 100755,000000..100755
--- /dev/null
@@@ -1,892 -1,0 +1,892 @@@
 +#!/usr/bin/env python
 +# -*- coding: UTF8 -*-
 +
 +from __future__ import with_statement
 +
 +import os
 +import math
 +import pickle
 +import logging
 +
 +import pango
 +import gobject
 +import gtk
 +import gtk.glade
 +import gtk.gdk
 +
 +import constants
 +import hildonize
 +import gtk_toolbox
 +import unit_data
 +
 +try:
 +      import gettext
 +except ImportError:
 +      _ = lambda x: x
 +      gettext = None
 +else:
 +      _ = gettext.gettext
 +
 +
 +_moduleLogger = logging.getLogger("gonvert_glade")
 +
 +if gettext is not None:
 +      gettext.bindtextdomain('gonvert', '/usr/share/locale')
 +      gettext.textdomain('gonvert')
 +
 +
 +def change_menu_label(widgets, labelname, newtext):
 +      item_label = widgets.get_widget(labelname).get_children()[0]
 +      item_label.set_text(newtext)
 +
 +
 +def split_number(number):
 +      try:
 +              fractional, integer = math.modf(number)
 +      except TypeError:
 +              integerDisplay = number
 +              fractionalDisplay = ""
 +      else:
 +              integerDisplay = str(integer)
 +              fractionalDisplay = str(fractional)
 +              if "e+" in integerDisplay:
 +                      integerDisplay = number
 +                      fractionalDisplay = ""
 +              elif "e-" in fractionalDisplay and 0.0 < integer:
 +                      integerDisplay = number
 +                      fractionalDisplay = ""
 +              elif "e-" in fractionalDisplay:
 +                      integerDisplay = ""
 +                      fractionalDisplay = number
 +              else:
 +                      integerDisplay = integerDisplay.split(".", 1)[0] + "."
 +                      fractionalDisplay = fractionalDisplay.rsplit(".", 1)[-1]
 +
 +      return integerDisplay, fractionalDisplay
 +
 +
 +class Gonvert(object):
 +
 +      _glade_files = [
 +              os.path.join(os.path.dirname(__file__), "gonvert.glade"),
 +              os.path.join(os.path.dirname(__file__), "../data/gonvert.glade"),
 +              os.path.join(os.path.dirname(__file__), "../lib/gonvert.glade"),
 +              '/usr/share/gonvert/gonvert.glade',
 +              '/usr/lib/gonvert/gonvert.glade',
 +      ]
 +
 +      UNITS_NAME_IDX = 0
 +      UNITS_VALUE_IDX = 1
 +      UNITS_SYMBOL_IDX = 2
 +      UNITS_INTEGER_IDX = 3
 +      UNITS_FRACTION_IDX = 4
 +
 +      def __init__(self):
 +              self._unitDataInCategory = None
 +              self._unit_sort_direction = False
 +              self._value_sort_direction = False
 +              self._units_sort_direction = False
 +              self.__isPortrait = False
 +              self._isFullScreen = False
 +              self._clipboard = gtk.clipboard_get()
 +
 +              self._find_result = [] # empty find result list
 +              self._findIndex = 0 # default to find result number zero
 +
 +              self._selectedCategoryName = '' # preset to no selected category
 +              self._defaultUnitForCategory = {} # empty dictionary for later use
 +
 +              #check to see if glade file is in current directory (user must be
 +              # running from download untar directory)
 +              for gladePath in self._glade_files:
 +                      if os.path.isfile(gladePath):
 +                              homepath = os.path.dirname(gladePath)
 +                              pixmapspath = "/".join((homepath, "pixmaps"))
 +                              widgets = gtk.glade.XML(gladePath)
 +                              break
 +              else:
 +                      _moduleLogger.error("UI Descriptor not found!")
 +                      gtk.main_quit()
 +                      return
 +
 +              self._mainWindow = widgets.get_widget('mainWindow')
 +              self._app = hildonize.get_app_class()()
 +              self._mainWindow = hildonize.hildonize_window(self._app, self._mainWindow)
 +
 +              change_menu_label(widgets, 'fileMenuItem', _('File'))
 +              change_menu_label(widgets, 'exitMenuItem', _('Exit'))
 +              change_menu_label(widgets, 'helpMenuItem', _('Help'))
 +              change_menu_label(widgets, 'aboutMenuItem', _('About'))
 +
 +              self._categorySelectionButton = widgets.get_widget("categorySelectionButton")
 +              self._categoryView = widgets.get_widget('categoryView')
 +
 +              self._unitsView = widgets.get_widget('unitsView')
 +              self._unitsView.set_property('rules_hint', 1)
 +              self._unitsView_selection = self._unitsView.get_selection()
 +
 +              self._unitName = widgets.get_widget('unitName')
 +              self._unitValue = widgets.get_widget('unitValue')
 +              self._previousUnitName = widgets.get_widget('previousUnitName')
 +              self._previousUnitValue = widgets.get_widget('previousUnitValue')
 +
 +              self._unitSymbol = widgets.get_widget('unitSymbol')
 +              self._previousUnitSymbol = widgets.get_widget('previousUnitSymbol')
 +
 +              self._unitDescription = widgets.get_widget('unitDescription')
 +
 +              self._searchLayout = widgets.get_widget('searchLayout')
 +              self._searchLayout.hide()
 +              self._findEntry = widgets.get_widget('findEntry')
 +              self._findLabel = widgets.get_widget('findLabel')
 +              self._findButton = widgets.get_widget('findButton')
 +              self._closeSearchButton = widgets.get_widget('closeSearchButton')
 +
 +              self._unitsNameRenderer = gtk.CellRendererText()
 +              self._unitsNameRenderer.set_property("scale", 0.75)
 +              if constants.FORCE_HILDON_LIKE:
 +                      self._unitsNameRenderer.set_property("ellipsize", pango.ELLIPSIZE_END)
 +                      self._unitsNameRenderer.set_property("width-chars", 5)
 +              self._unitNameColumn = gtk.TreeViewColumn(_('Name'), self._unitsNameRenderer)
 +              self._unitNameColumn.set_property('resizable', True)
 +              self._unitNameColumn.add_attribute(self._unitsNameRenderer, 'text', self.UNITS_NAME_IDX)
 +              self._unitNameColumn.set_clickable(True)
 +              self._unitNameColumn.connect("clicked", self._on_click_unit_column)
 +              self._unitsView.append_column(self._unitNameColumn)
 +
 +              renderer = gtk.CellRendererText()
 +              renderer.set_property("xalign", 1.0)
 +              renderer.set_property("alignment", pango.ALIGN_RIGHT)
 +              hildonize.set_cell_thumb_selectable(renderer)
 +              self._unitIntegerColumn = gtk.TreeViewColumn(_('Value'), renderer)
 +              self._unitIntegerColumn.set_property('resizable', True)
 +              self._unitIntegerColumn.add_attribute(renderer, 'text', self.UNITS_INTEGER_IDX)
 +              self._unitIntegerColumn.set_clickable(True)
 +              self._unitIntegerColumn.connect("clicked", self._on_click_unit_column)
 +              self._unitsView.append_column(self._unitIntegerColumn)
 +
 +              renderer = gtk.CellRendererText()
 +              renderer.set_property("xalign", 0.0)
 +              renderer.set_property("alignment", pango.ALIGN_LEFT)
 +              renderer.set_property("scale", 0.75)
 +              self._unitFractionalColumn = gtk.TreeViewColumn(_(''), renderer)
 +              self._unitFractionalColumn.set_property('resizable', True)
 +              self._unitFractionalColumn.add_attribute(renderer, 'text', self.UNITS_FRACTION_IDX)
 +              self._unitFractionalColumn.set_clickable(True)
 +              self._unitFractionalColumn.connect("clicked", self._on_click_unit_column)
 +              self._unitsView.append_column(self._unitFractionalColumn)
 +
 +              renderer = gtk.CellRendererText()
 +              renderer.set_property("ellipsize", pango.ELLIPSIZE_END)
 +              #renderer.set_property("scale", 0.5)
 +              self._unitSymbolColumn = gtk.TreeViewColumn(_('Units'), renderer)
 +              self._unitSymbolColumn.set_property('resizable', True)
 +              self._unitSymbolColumn.add_attribute(renderer, 'text', self.UNITS_SYMBOL_IDX)
 +              self._unitSymbolColumn.set_clickable(True)
 +              self._unitSymbolColumn.connect("clicked", self._on_click_unit_column)
 +              self._unitsView.append_column(self._unitSymbolColumn)
 +
 +              self._unitModel = gtk.ListStore(
 +                      gobject.TYPE_STRING, # UNITS_NAME_IDX
 +                      gobject.TYPE_STRING, # UNITS_VALUE_IDX
 +                      gobject.TYPE_STRING, # UNITS_SYMBOL_IDX
 +                      gobject.TYPE_STRING, # UNITS_INTEGER_IDX
 +                      gobject.TYPE_STRING, # UNITS_FRACTION_IDX
 +              )
 +              self._sortedUnitModel = gtk.TreeModelSort(self._unitModel)
 +              columns = self._get_column_sort_stuff()
 +              for columnIndex, (column, sortDirection, col_cmp) in enumerate(columns):
 +                      self._sortedUnitModel.set_sort_func(columnIndex, col_cmp)
 +              self._unitsView.set_model(self._sortedUnitModel)
 +
 +              #Insert a column into the category list even though the heading will not be seen
 +              renderer = gtk.CellRendererText()
 +              self._categoryColumn = gtk.TreeViewColumn('Title', renderer)
 +              self._categoryColumn.set_property('resizable', 1)
 +              self._categoryColumn.add_attribute(renderer, 'text', 0)
 +              self._categoryView.append_column(self._categoryColumn)
 +
 +              self._categoryModel = gtk.ListStore(gobject.TYPE_STRING)
 +              self._categoryView.set_model(self._categoryModel)
 +              #colourize each row differently for easier reading
 +              self._categoryView.set_property('rules_hint', 1)
 +
 +              #Populate the catagories list
 +              for key in unit_data.UNIT_CATEGORIES:
 +                      row = (key, )
 +                      self._categoryModel.append(row)
 +
 +              #--------- connections to GUI ----------------
 +              self._mainWindow.connect("destroy", self._on_user_exit)
 +              self._mainWindow.connect("key-press-event", self._on_key_press)
 +              self._mainWindow.connect("window-state-event", self._on_window_state_change)
 +              self._categorySelectionButton.connect("clicked", self._on_category_selector_clicked)
 +              self._categoryView.connect("cursor-changed", self._on_click_category)
 +              self._findButton.connect("clicked", self._on_find_activate)
 +              self._findEntry.connect("activate", self._on_find_activate)
 +              self._findEntry.connect("changed", self._on_findEntry_changed)
 +              self._closeSearchButton.connect("clicked", self._on_toggle_search)
 +              self._previousUnitValue.connect("changed", self._on_previous_unit_value_changed)
 +              self._unitValue.connect("changed", self._on_unit_value_changed)
 +              self._unitValue.connect("key-press-event", self._on_browse_key_press)
 +              self._unitsView.connect("cursor-changed", self._on_click_unit)
 +              self._unitsView.connect("key-press-event", self._on_browse_key_press)
 +              if hildonize.GTK_MENU_USED:
 +                      widgets.get_widget("aboutMenuItem").connect("activate", self._on_about_clicked)
 +                      widgets.get_widget("searchMenuItem").connect("activate", self._on_toggle_search)
 +                      widgets.get_widget("exitMenuItem").connect("activate", self._on_user_exit)
 +
 +              for scrollingWidgetName in (
 +                      "unitsViewScrolledWindow",
 +              ):
 +                      scrollingWidget = widgets.get_widget(scrollingWidgetName)
 +                      assert scrollingWidget is not None, scrollingWidgetName
 +                      scroller = hildonize.hildonize_scrollwindow(scrollingWidget)
 +                      scroller.show_all()
 +
 +              # Simplify the UI
 +              if hildonize.IS_HILDON_SUPPORTED or constants.FORCE_HILDON_LIKE:
 +                      self._categoryView.get_parent().hide()
 +                      self._unitsView.set_headers_visible(False)
 +                      self._previousUnitName.get_parent().hide()
 +                      self._unitDescription.get_parent().get_parent().hide()
 +              else:
 +                      self._categorySelectionButton.hide()
 +
 +              menu = hildonize.hildonize_menu(
 +                      self._mainWindow,
 +                      widgets.get_widget("mainMenuBar"),
 +              )
 +              if not hildonize.GTK_MENU_USED:
 +                      button = gtk.Button("Search")
 +                      button.connect("clicked", self._on_toggle_search)
 +                      menu.append(button)
 +
 +                      button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, None)
 +                      button.set_label("Name")
 +                      menu.add_filter(button)
 +                      button.connect("clicked", self._on_click_menu_filter, self._unitNameColumn)
 +                      button.set_mode(False)
 +                      filterGroup = button
 +
 +                      button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
 +                      button.set_label("Value")
 +                      menu.add_filter(button)
 +                      button.connect("clicked", self._on_click_menu_filter, self._unitIntegerColumn)
 +                      button.set_mode(False)
 +
 +                      button = hildonize.hildon.GtkRadioButton(gtk.HILDON_SIZE_AUTO, filterGroup)
 +                      button.set_label("Unit")
 +                      menu.add_filter(button)
 +                      button.connect("clicked", self._on_click_menu_filter, self._unitSymbolColumn)
 +                      button.set_mode(False)
 +
 +                      menu.show_all()
 +
 +              if not hildonize.IS_HILDON_SUPPORTED:
 +                      _moduleLogger.info("No hildonization support")
 +
-               hildonize.set_application_title(
-                       self._mainWindow, "%s - Unit Conversion Utility" % constants.__pretty_app_name__
++              hildonize.set_application_name(
++                      "%s - Unit Conversion Utility" % constants.__pretty_app_name__
 +              )
 +              iconPath = pixmapspath + '/gonvert.png'
 +              if os.path.exists(iconPath):
 +                      self._mainWindow.set_icon(gtk.gdk.pixbuf_new_from_file(iconPath))
 +              else:
 +                      _moduleLogger.warn("Error: Could not find gonvert icon: %s" % iconPath)
 +
 +              self._load_settings()
 +              self._mainWindow.show()
 +
 +      def _load_settings(self):
 +              #Restore window size from previously saved settings if it exists and is valid.
 +              windowDatPath = "/".join((constants._data_path_, "window.dat"))
 +              if os.path.exists(windowDatPath):
 +                      saved_window = pickle.load(open(windowDatPath, "r"))
 +                      try:
 +                              a, b = saved_window['size']
 +                      except KeyError:
 +                              pass
 +                      else:
 +                              self._mainWindow.resize(a, b)
 +                      try:
 +                              isFullscreen = saved_window["isFullscreen"]
 +                      except KeyError:
 +                              pass
 +                      else:
 +                              if isFullscreen:
 +                                      self._mainWindow.fullscreen()
 +                      try:
 +                              isPortrait = saved_window["isPortrait"]
 +                      except KeyError:
 +                              pass
 +                      else:
 +                              if isPortrait ^ self.__isPortrait:
 +                                      if isPortrait:
 +                                              orientation = gtk.ORIENTATION_VERTICAL
 +                                      else:
 +                                              orientation = gtk.ORIENTATION_HORIZONTAL
 +                                      self.set_orientation(orientation)
 +
 +              #Restore selections from previously saved settings if it exists and is valid.
 +              categoryIndex = 0
 +              selectedCategoryName = unit_data.UNIT_CATEGORIES[0]
 +              selectionsDatPath = "/".join((constants._data_path_, "selections.dat"))
 +              if os.path.exists(selectionsDatPath):
 +                      selections = pickle.load(open(selectionsDatPath, 'r'))
 +                      try:
 +                              self._defaultUnitForCategory = selections['selected_units']
 +                      except KeyError:
 +                              pass
 +
 +                      try:
 +                              selectedCategoryName = selections['selected_category']
 +                      except KeyError:
 +                              pass
 +                      else:
 +                              try:
 +                                      categoryIndex = unit_data.UNIT_CATEGORIES.index(selectedCategoryName)
 +                              except ValueError:
 +                                      _moduleLogger.warn("Unknown category: %s" % selectedCategoryName)
 +
 +              self._categorySelectionButton.get_child().set_markup("<big>%s</big>" % selectedCategoryName)
 +              self._categoryView.set_cursor(categoryIndex, self._categoryColumn, False)
 +              self._categoryView.grab_focus()
 +
 +              self._select_default_unit()
 +
 +      def _save_settings(self):
 +              """
 +              This routine saves the selections to a file, and
 +              should therefore only be called when exiting the program.
 +
 +              Update selections dictionary which consists of the following keys:
 +              'self._selectedCategoryName': full name of selected category
 +              'self._defaultUnitForCategory': self._defaultUnitForCategory dictionary which contains:
 +              [categoryname: #1 displayed unit, #2 displayed unit]
 +              """
 +              #Determine the contents of the selected category row
 +              selected, iter = self._categoryView.get_selection().get_selected()
 +              self._selectedCategoryName = self._categoryModel.get_value(iter, 0)
 +
 +              selections = {
 +                      'selected_category': self._selectedCategoryName,
 +                      'selected_units': self._defaultUnitForCategory
 +              }
 +              selectionsDatPath = "/".join((constants._data_path_, "selections.dat"))
 +              pickle.dump(selections, open(selectionsDatPath, 'w'))
 +
 +              #Get last size of app and save it
 +              window_settings = {
 +                      'size': self._mainWindow.get_size(),
 +                      "isFullscreen": self._isFullScreen,
 +                      "isPortrait": self.__isPortrait,
 +              }
 +              windowDatPath = "/".join((constants._data_path_, "window.dat"))
 +              pickle.dump(window_settings, open(windowDatPath, 'w'))
 +
 +      def _refresh_columns(self):
 +              self._unitsView.remove_column(self._unitNameColumn)
 +              self._unitsView.remove_column(self._unitIntegerColumn)
 +              self._unitsView.remove_column(self._unitFractionalColumn)
 +              self._unitsView.remove_column(self._unitSymbolColumn)
 +
 +              self._unitsView.append_column(self._unitNameColumn)
 +              self._unitsView.append_column(self._unitIntegerColumn)
 +              self._unitsView.append_column(self._unitFractionalColumn)
 +              self._unitsView.append_column(self._unitSymbolColumn)
 +
 +      def _clear_find(self):
 +              # switch to "new find" state
 +              self._find_result = []
 +              self._findIndex = 0
 +
 +              # Clear our user message
 +              self._findLabel.set_text('')
 +
 +      def _find_first(self):
 +              assert len(self._find_result) == 0
 +              assert self._findIndex == 0
 +              findString = self._findEntry.get_text().strip().lower()
 +              if not findString:
 +                      return
 +
 +              # Gather info on all the matching units from all categories
 +              for catIndex, category in enumerate(unit_data.UNIT_CATEGORIES):
 +                      units = unit_data.get_units(category)
 +                      for unitIndex, unit in enumerate(units):
 +                              loweredUnit = unit.lower()
 +                              if loweredUnit in findString or findString in loweredUnit:
 +                                      self._find_result.append((category, unit, catIndex, unitIndex))
 +
 +      def _update_find_selection(self):
 +              assert 0 < len(self._find_result)
 +
 +              #check if next find is in a new category (prevent category changes when unnecessary
 +              searchCategoryName = self._find_result[self._findIndex][0]
 +              if self._selectedCategoryName != searchCategoryName:
 +                      self._categorySelectionButton.get_child().set_markup("<big>%s</big>" % searchCategoryName)
 +                      self._categoryView.set_cursor(
 +                              self._find_result[self._findIndex][2], self._categoryColumn, False
 +                      )
 +
 +              self._unitsView.set_cursor(
 +                      self._find_result[self._findIndex][3], self._unitNameColumn, True
 +              )
 +
 +      def _find_next(self):
 +              if len(self._find_result) == 0:
 +                      self._find_first()
 +              else:
 +                      if self._findIndex == len(self._find_result)-1:
 +                              self._findIndex = 0
 +                      else:
 +                              self._findIndex += 1
 +
 +              if not self._find_result:
 +                      self._findLabel.set_text('Text not found')
 +              else:
 +                      self._update_find_selection()
 +                      resultsLeft = len(self._find_result) - self._findIndex - 1
 +                      self._findLabel.set_text(
 +                              '%s result(s) left' % (resultsLeft, )
 +                      )
 +
 +      def _find_previous(self):
 +              if len(self._find_result) == 0:
 +                      self._find_first()
 +              else:
 +                      if self._findIndex == 0:
 +                              self._findIndex = len(self._find_result)-1
 +                      else:
 +                              self._findIndex -= 1
 +
 +              if not self._find_result:
 +                      self._findLabel.set_text('Text not found')
 +              else:
 +                      self._update_find_selection()
 +                      resultsLeft = len(self._find_result) - self._findIndex - 1
 +                      self._findLabel.set_text(
 +                              '%s result(s) left' % (resultsLeft, )
 +                      )
 +
 +      def _toggle_find(self):
 +              if self._searchLayout.get_property("visible"):
 +                      self._searchLayout.hide()
 +                      self._unitsView.grab_focus()
 +              else:
 +                      self._searchLayout.show()
 +                      self._findEntry.grab_focus()
 +
 +      def _unit_model_cmp(self, sortedModel, leftItr, rightItr):
 +              leftUnitText = self._unitModel.get_value(leftItr, self.UNITS_NAME_IDX)
 +              rightUnitText = self._unitModel.get_value(rightItr, self.UNITS_NAME_IDX)
 +              return cmp(leftUnitText, rightUnitText)
 +
 +      def _symbol_model_cmp(self, sortedModel, leftItr, rightItr):
 +              leftSymbolText = self._unitModel.get_value(leftItr, self.UNITS_SYMBOL_IDX)
 +              rightSymbolText = self._unitModel.get_value(rightItr, self.UNITS_SYMBOL_IDX)
 +              return cmp(leftSymbolText, rightSymbolText)
 +
 +      def _value_model_cmp(self, sortedModel, leftItr, rightItr):
 +              #special sorting exceptions for ascii values (instead of float values)
 +              if self._selectedCategoryName == "Computer Numbers":
 +                      leftValue = self._unitModel.get_value(leftItr, self.UNITS_VALUE_IDX)
 +                      rightValue = self._unitModel.get_value(rightItr, self.UNITS_VALUE_IDX)
 +              else:
 +                      leftValueText = self._unitModel.get_value(leftItr, self.UNITS_VALUE_IDX)
 +                      leftValue = float(leftValueText) if leftValueText else 0.0
 +
 +                      rightValueText = self._unitModel.get_value(rightItr, self.UNITS_VALUE_IDX)
 +                      rightValue = float(rightValueText) if rightValueText else 0.0
 +              return cmp(leftValue, rightValue)
 +
 +      def _get_column_sort_stuff(self):
 +              columns = (
 +                      (self._unitNameColumn, "_unit_sort_direction", self._unit_model_cmp),
 +                      (self._unitIntegerColumn, "_value_sort_direction", self._value_model_cmp),
 +                      (self._unitFractionalColumn, "_value_sort_direction", self._value_model_cmp),
 +                      (self._unitSymbolColumn, "_units_sort_direction", self._symbol_model_cmp),
 +              )
 +              return columns
 +
 +      def _switch_category(self, category):
 +              self._selectedCategoryName = category
 +              self._unitDataInCategory = unit_data.UNIT_DESCRIPTIONS[self._selectedCategoryName]
 +
 +              #Fill up the units descriptions and clear the value cells
 +              self._clear_visible_unit_data()
 +              nameLength = 0
 +              for key in unit_data.get_units(self._selectedCategoryName):
 +                      row = key, '0.0', self._unitDataInCategory[key][1], '0.', '0'
 +                      self._unitModel.append(row)
 +                      nameLength = max(nameLength, len(key))
 +              self._sortedUnitModel.sort_column_changed()
 +
 +              if constants.FORCE_HILDON_LIKE:
 +                      maxCatCharWidth = int(nameLength * 0.75)
 +                      maxCharWidth = int(len("nibble | hexit | quadbit") * 0.75)
 +                      charWidth = min(maxCatCharWidth, maxCharWidth)
 +                      self._unitsNameRenderer.set_property("width-chars", charWidth)
 +
 +              self._select_default_unit()
 +
 +      def _clear_visible_unit_data(self):
 +              self._unitDescription.get_buffer().set_text("")
 +              self._unitName.set_text('')
 +              self._unitValue.set_text('')
 +              self._unitSymbol.set_text('')
 +
 +              self._previousUnitName.set_text('')
 +              self._previousUnitValue.set_text('')
 +              self._previousUnitSymbol.set_text('')
 +
 +              self._unitModel.clear()
 +
 +      def _select_default_unit(self):
 +              # Restore the previous historical settings of previously selected units
 +              # in this newly selected category
 +              defaultPrimary = unit_data.get_base_unit(self._selectedCategoryName)
 +              defaultSecondary = ""
 +              if self._selectedCategoryName in self._defaultUnitForCategory:
 +                      if self._defaultUnitForCategory[self._selectedCategoryName][0]:
 +                              defaultPrimary = self._defaultUnitForCategory[self._selectedCategoryName][0]
 +                      if self._defaultUnitForCategory[self._selectedCategoryName][1]:
 +                              defaultSecondary = self._defaultUnitForCategory[self._selectedCategoryName][1]
 +
 +              units = unit_data.get_units(self._selectedCategoryName)
 +
 +              #Restore oldest selection first.
 +              if defaultPrimary:
 +                      try:
 +                              unitIndex = units.index(defaultPrimary)
 +                      except ValueError:
 +                              unitIndex = 0
 +                      self._unitsView.set_cursor(unitIndex, self._unitNameColumn, True)
 +
 +              #Restore newest selection second.
 +              if defaultSecondary:
 +                      try:
 +                              unitIndex = units.index(defaultSecondary)
 +                      except ValueError:
 +                              unitIndex = 0
 +                      self._unitsView.set_cursor(unitIndex, self._unitNameColumn, True)
 +
 +              # select the text so user can start typing right away
 +              self._unitValue.grab_focus()
 +              self._unitValue.select_region(0, -1)
 +
 +      def _sanitize_value(self, userEntry):
 +              if self._selectedCategoryName == "Computer Numbers":
 +                      if userEntry == '':
 +                              value = '0'
 +                      else:
 +                              value = userEntry
 +              else:
 +                      if userEntry == '':
 +                              value = 0.0
 +                      else:
 +                              value = float(userEntry)
 +              return value
 +
 +      def _select_sort_column(self, col):
 +              #Determine which column requires sorting
 +              columns = self._get_column_sort_stuff()
 +              for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns):
 +                      if col is maybeCol:
 +                              direction = getattr(self, directionName)
 +                              gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING
 +
 +                              # cause a sort
 +                              self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection)
 +
 +                              # set the visual for sorting
 +                              col.set_sort_indicator(True)
 +                              col.set_sort_order(not direction)
 +
 +                              setattr(self, directionName, not direction)
 +                              break
 +                      else:
 +                              maybeCol.set_sort_indicator(False)
 +              else:
 +                      assert False, "Unknown column: %s" % (col.get_title(), )
 +
 +      def set_orientation(self, orientation):
 +              if orientation == gtk.ORIENTATION_VERTICAL:
 +                      hildonize.window_to_portrait(self._mainWindow)
 +                      self.__isPortrait = True
 +              elif orientation == gtk.ORIENTATION_HORIZONTAL:
 +                      hildonize.window_to_landscape(self._mainWindow)
 +                      self.__isPortrait = False
 +              else:
 +                      raise NotImplementedError(orientation)
 +
 +      def get_orientation(self):
 +              return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
 +
 +      def _toggle_rotate(self):
 +              if self.__isPortrait:
 +                      self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
 +              else:
 +                      self.set_orientation(gtk.ORIENTATION_VERTICAL)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_key_press(self, widget, event, *args):
 +              """
 +              @note Hildon specific
 +              """
 +              RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
 +              if (
 +                      event.keyval == gtk.keysyms.F6 or
 +                      event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK
 +              ):
 +                      if self._isFullScreen:
 +                              self._mainWindow.unfullscreen()
 +                      else:
 +                              self._mainWindow.fullscreen()
 +              elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK:
 +                      if not hildonize.GTK_MENU_USED:
 +                              self._toggle_find()
 +              elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK:
 +                      self._find_previous()
 +              elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK:
 +                      self._find_next()
 +              elif event.keyval == gtk.keysyms.o and event.get_state() & gtk.gdk.CONTROL_MASK:
 +                      self._toggle_rotate()
 +              elif (
 +                      event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
 +                      event.get_state() & gtk.gdk.CONTROL_MASK
 +              ):
 +                      self._mainWindow.destroy()
 +              elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
 +                      with open(constants._user_logpath_, "r") as f:
 +                              logLines = f.xreadlines()
 +                              log = "".join(logLines)
 +                              self._clipboard.set_text(str(log))
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_toggle_search(self, *args):
 +              self._toggle_find()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_browse_key_press(self, widget, event, *args):
 +              if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up:
 +                      index, column = self._unitsView.get_cursor()
 +                      newIndex = max(index[0]-1, 0)
 +                      path = (newIndex, )
 +                      self._unitsView.set_cursor(path, column, True)
 +                      self._unitsView.scroll_to_cell(path, column, False, 0, 0)
 +                      return True # override default behavior
 +              elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down:
 +                      index, column = self._unitsView.get_cursor()
 +                      newIndex = min(index[0]+1, len(self._unitModel)-1)
 +                      path = (newIndex, )
 +                      self._unitsView.set_cursor(path, column, True)
 +                      self._unitsView.scroll_to_cell(path, column, False, 0, 0)
 +                      return True # override default behavior
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_window_state_change(self, widget, event, *args):
 +              """
 +              @note Hildon specific
 +              """
 +              if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
 +                      self._isFullScreen = True
 +              else:
 +                      self._isFullScreen = False
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_findEntry_changed(self, *args):
 +              """
 +              Clear out find results since the user wants to look for something new
 +              """
 +              self._clear_find()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_find_activate(self, *args):
 +              self._find_next()
 +              self._findButton.grab_focus()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_click_menu_filter(self, button, col):
 +              self._select_sort_column(col)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_click_unit_column(self, col):
 +              """
 +              Sort the contents of the col when the user clicks on the title.
 +              """
 +              self._select_sort_column(col)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_category_selector_clicked(self, *args):
 +              currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName)
 +              newIndex = hildonize.touch_selector(
 +                      self._mainWindow,
 +                      "Categories",
 +                      unit_data.UNIT_CATEGORIES,
 +                      currenntIndex,
 +              )
 +
 +              selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex]
 +              self._categorySelectionButton.get_child().set_markup("<big>%s</big>" % selectedCategoryName)
 +              self._switch_category(selectedCategoryName)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_click_category(self, *args):
 +              selected, iter = self._categoryView.get_selection().get_selected()
 +              if iter is None:
 +                      # User is typing in an invalid string, not selecting any category
 +                      return
 +              selectedCategory = self._categoryModel.get_value(iter, 0)
 +              self._switch_category(selectedCategory)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_click_unit(self, *args):
 +              selected, iter = self._unitsView.get_selection().get_selected()
 +              selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX)
 +              unit_spec = self._unitDataInCategory[selected_unit]
 +
 +              showSymbol = False
 +
 +              if self._unitName.get_text() != selected_unit:
 +                      self._previousUnitName.set_text(self._unitName.get_text())
 +                      self._previousUnitValue.set_text(self._unitValue.get_text())
 +                      self._previousUnitSymbol.set_text(self._unitSymbol.get_text())
 +                      if self._unitSymbol.get_text():
 +                              showSymbol = True
 +
 +              self._unitName.set_text(selected_unit)
 +              self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX))
 +              buffer = self._unitDescription.get_buffer()
 +              buffer.set_text(unit_spec[2])
 +              self._unitSymbol.set_text(unit_spec[1]) # put units into label text
 +              if unit_spec[1]:
 +                      showSymbol = True
 +              else:
 +                      showSymbol = False
 +
 +              if showSymbol:
 +                      self._unitSymbol.show()
 +                      self._previousUnitSymbol.show()
 +              else:
 +                      self._unitSymbol.hide()
 +                      self._previousUnitSymbol.hide()
 +
 +              if self._unitValue.get_text() == '':
 +                      if self._selectedCategoryName == "Computer Numbers":
 +                              self._unitValue.set_text("0")
 +                      else:
 +                              self._unitValue.set_text("0.0")
 +
 +              self._defaultUnitForCategory[self._selectedCategoryName] = [
 +                      self._unitName.get_text(), self._previousUnitName.get_text()
 +              ]
 +
 +              # select the text so user can start typing right away
 +              self._unitValue.grab_focus()
 +              self._unitValue.select_region(0, -1)
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_unit_value_changed(self, *args):
 +              if self._unitName.get_text() == '':
 +                      return
 +              if not self._unitValue.is_focus():
 +                      return
 +
 +              #retrieve the conversion function and value from the selected unit
 +              value = self._sanitize_value(self._unitValue.get_text())
 +              func, arg = self._unitDataInCategory[self._unitName.get_text()][0]
 +              base = func.to_base(value, arg)
 +
 +              #point to the first row
 +              for row in self._unitModel:
 +                      func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0]
 +                      newValue = func.from_base(base, arg)
 +
 +                      newValueDisplay = str(newValue)
 +                      integerDisplay, fractionalDisplay = split_number(newValue)
 +
 +                      row[self.UNITS_VALUE_IDX] = newValueDisplay
 +                      row[self.UNITS_INTEGER_IDX] = integerDisplay
 +                      row[self.UNITS_FRACTION_IDX] = fractionalDisplay
 +
 +              # Update the secondary unit entry
 +              if self._previousUnitName.get_text() != '':
 +                      func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0]
 +                      self._previousUnitValue.set_text(str(func.from_base(base, arg, )))
 +
 +              self._sortedUnitModel.sort_column_changed()
 +              self._refresh_columns()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_previous_unit_value_changed(self, *args):
 +              if self._previousUnitName.get_text() == '':
 +                      return
 +              if not self._previousUnitValue.is_focus():
 +                      return
 +
 +              #retrieve the conversion function and value from the selected unit
 +              value = self._sanitize_value(self._previousUnitValue.get_text())
 +              func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0]
 +              base = func.to_base(value, arg)
 +
 +              #point to the first row
 +              for row in self._unitModel:
 +                      func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0]
 +                      newValue = func.from_base(base, arg)
 +
 +                      newValueDisplay = str(newValue)
 +                      integerDisplay, fractionalDisplay = split_number(newValue)
 +
 +                      row[self.UNITS_VALUE_IDX] = newValueDisplay
 +                      row[self.UNITS_INTEGER_IDX] = integerDisplay
 +                      row[self.UNITS_FRACTION_IDX] = fractionalDisplay
 +
 +              # Update the primary unit entry
 +              func, arg = self._unitDataInCategory[self._unitName.get_text()][0]
 +              self._unitValue.set_text(str(func.from_base(base, arg, )))
 +
 +              self._sortedUnitModel.sort_column_changed()
 +              self._refresh_columns()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_about_clicked(self, a):
 +              dlg = gtk.AboutDialog()
 +              dlg.set_name(constants.__pretty_app_name__)
 +              dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
 +              dlg.set_copyright("Copyright 2009 - GPL")
 +              dlg.set_comments("")
 +              dlg.set_website("http://unihedron.com/projects/gonvert/gonvert.php")
 +              dlg.set_authors(["Anthony Tekatch <anthony@unihedron.com>", "Ed Page <eopage@byu.net> (Blame him for the most recent bugs)"])
 +              dlg.run()
 +              dlg.destroy()
 +
 +      @gtk_toolbox.log_exception(_moduleLogger)
 +      def _on_user_exit(self, *args):
 +              try:
 +                      self._save_settings()
 +              except Exception:
 +                      pass
 +              finally:
 +                      gtk.main_quit()
 +
 +
 +def run_gonvert():
 +      gtk.gdk.threads_init()
 +      if hildonize.IS_HILDON_SUPPORTED:
 +              gtk.set_application_name(constants.__pretty_app_name__)
 +      handle = Gonvert()
 +      if not constants.PROFILE_STARTUP:
 +              gtk.main()
 +
 +
 +if __name__ == "__main__":
 +      logging.basicConfig(level = logging.DEBUG)
 +      try:
 +              os.makedirs(constants._data_path_)
 +      except OSError, e:
 +              if e.errno != 17:
 +                      raise
 +
 +      run_gonvert()
diff --combined src/gtk_toolbox.py
@@@ -17,7 -17,7 +17,7 @@@ import gobjec
  import gtk
  
  
- _moduleLogger = logging.getLogger("gtk_toolbox")
+ _moduleLogger = logging.getLogger(__name__)
  
  
  def get_screen_orientation():
@@@ -567,202 -567,7 +567,202 @@@ class PopupCalendar(object)
                        _moduleLogger.exception(e)
  
  
 +class QuickAddView(object):
 +
 +      def __init__(self, widgetTree, errorDisplay, signalSink, prefix):
 +              self._errorDisplay = errorDisplay
 +              self._manager = None
 +              self._signalSink = signalSink
 +
 +              self._clipboard = gtk.clipboard_get()
 +
 +              self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry")
 +              self._addTaskButton = widgetTree.get_widget(prefix+"-addButton")
 +              self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton")
 +              self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton")
 +              self._onAddId = None
 +              self._onAddClickedId = None
 +              self._onAddReleasedId = None
 +              self._addToEditTimerId = None
 +              self._onClearId = None
 +              self._onPasteId = None
 +
 +      def enable(self, manager):
 +              self._manager = manager
 +
 +              self._onAddId = self._addTaskButton.connect("clicked", self._on_add)
 +              self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed)
 +              self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released)
 +              self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste)
 +              self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear)
 +
 +      def disable(self):
 +              self._manager = None
 +
 +              self._addTaskButton.disconnect(self._onAddId)
 +              self._addTaskButton.disconnect(self._onAddClickedId)
 +              self._addTaskButton.disconnect(self._onAddReleasedId)
 +              self._pasteTaskNameButton.disconnect(self._onPasteId)
 +              self._clearTaskNameButton.disconnect(self._onClearId)
 +
 +      def set_addability(self, addability):
 +              self._addTaskButton.set_sensitive(addability)
 +
 +      def _on_add(self, *args):
 +              try:
 +                      name = self._taskNameEntry.get_text()
 +                      self._taskNameEntry.set_text("")
 +
 +                      self._signalSink.stage.send(("add", name))
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +      def _on_add_edit(self, *args):
 +              try:
 +                      name = self._taskNameEntry.get_text()
 +                      self._taskNameEntry.set_text("")
 +
 +                      self._signalSink.stage.send(("add-edit", name))
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +      def _on_add_pressed(self, widget):
 +              try:
 +                      self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit)
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +      def _on_add_released(self, widget):
 +              try:
 +                      if self._addToEditTimerId is not None:
 +                              gobject.source_remove(self._addToEditTimerId)
 +                      self._addToEditTimerId = None
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +      def _on_paste(self, *args):
 +              try:
 +                      entry = self._taskNameEntry.get_text()
 +                      addedText = self._clipboard.wait_for_text()
 +                      if addedText:
 +                              entry += addedText
 +                      self._taskNameEntry.set_text(entry)
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +      def _on_clear(self, *args):
 +              try:
 +                      self._taskNameEntry.set_text("")
 +              except Exception, e:
 +                      self._errorDisplay.push_exception()
 +
 +
 +class TapOrHold(object):
 +
 +      def __init__(self, widget):
 +              self._widget = widget
 +              self._isTap = True
 +              self._isPointerInside = True
 +              self._holdTimeoutId = None
 +              self._tapTimeoutId = None
 +              self._taps = 0
 +
 +              self._bpeId = None
 +              self._breId = None
 +              self._eneId = None
 +              self._lneId = None
 +
 +      def enable(self):
 +              self._bpeId = self._widget.connect("button-press-event", self._on_button_press)
 +              self._breId = self._widget.connect("button-release-event", self._on_button_release)
 +              self._eneId = self._widget.connect("enter-notify-event", self._on_enter)
 +              self._lneId = self._widget.connect("leave-notify-event", self._on_leave)
 +
 +      def disable(self):
 +              self._widget.disconnect(self._bpeId)
 +              self._widget.disconnect(self._breId)
 +              self._widget.disconnect(self._eneId)
 +              self._widget.disconnect(self._lneId)
 +
 +      def on_tap(self, taps):
 +              print "TAP", taps
 +
 +      def on_hold(self, taps):
 +              print "HOLD", taps
 +
 +      def on_holding(self):
 +              print "HOLDING"
 +
 +      def on_cancel(self):
 +              print "CANCEL"
 +
 +      def _on_button_press(self, *args):
 +              # Hack to handle weird notebook behavior
 +              self._isPointerInside = True
 +              self._isTap = True
 +
 +              if self._tapTimeoutId is not None:
 +                      gobject.source_remove(self._tapTimeoutId)
 +                      self._tapTimeoutId = None
 +
 +              # Handle double taps
 +              if self._holdTimeoutId is None:
 +                      self._tapTimeoutId = None
 +
 +                      self._taps = 1
 +                      self._holdTimeoutId = gobject.timeout_add(1000, self._on_hold_timeout)
 +              else:
 +                      self._taps = 2
 +
 +      def _on_button_release(self, *args):
 +              assert self._tapTimeoutId is None
 +              # Handle release after timeout if user hasn't double-clicked
 +              self._tapTimeoutId = gobject.timeout_add(100, self._on_tap_timeout)
 +
 +      def _on_actual_press(self, *args):
 +              if self._holdTimeoutId is not None:
 +                      gobject.source_remove(self._holdTimeoutId)
 +              self._holdTimeoutId = None
 +
 +              if self._isPointerInside:
 +                      if self._isTap:
 +                              self.on_tap(self._taps)
 +                      else:
 +                              self.on_hold(self._taps)
 +              else:
 +                      self.on_cancel()
 +
 +      def _on_tap_timeout(self, *args):
 +              self._tapTimeoutId = None
 +              self._on_actual_press()
 +              return False
 +
 +      def _on_hold_timeout(self, *args):
 +              self._holdTimeoutId = None
 +              self._isTap = False
 +              self.on_holding()
 +              return False
 +
 +      def _on_enter(self, *args):
 +              self._isPointerInside = True
 +
 +      def _on_leave(self, *args):
 +              self._isPointerInside = False
 +
 +
  if __name__ == "__main__":
 +      if True:
 +              win = gtk.Window()
 +              win.set_title("Tap'N'Hold")
 +              eventBox = gtk.EventBox()
 +              win.add(eventBox)
 +
 +              context = ContextHandler(eventBox, coroutines.printer_sink())
 +              context.enable()
 +              win.connect("destroy", lambda w: gtk.main_quit())
 +
 +              win.show_all()
 +
        if False:
                import datetime
                cal = PopupCalendar(None, datetime.datetime.now())
diff --combined src/hildonize.py
index 77d585a,339eb2a..339eb2a
mode 100755,100644..100755
@@@ -47,24 -47,26 +47,26 @@@ except AttributeError
        get_app_class = _null_get_app_class
  
  
- def _hildon_set_application_title(window, title):
-       pass
+ def _hildon_set_application_name(name):
+       gtk.set_application_name(name)
  
  
- def _null_set_application_title(window, title):
-       window.set_title(title)
+ def _null_set_application_name(name):
+       pass
  
  
- if IS_HILDON_SUPPORTED:
-       set_application_title = _hildon_set_application_title
- else:
-       set_application_title = _null_set_application_title
+ try:
+       gtk.set_application_name
+       set_application_name = _hildon_set_application_name
+ except AttributeError:
+       set_application_name = _null_set_application_name
  
  
  def _fremantle_hildonize_window(app, window):
        oldWindow = window
        newWindow = hildon.StackableWindow()
-       oldWindow.get_child().reparent(newWindow)
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
        app.add_window(newWindow)
        return newWindow
  
@@@ -72,7 -74,8 +74,8 @@@
  def _hildon_hildonize_window(app, window):
        oldWindow = window
        newWindow = hildon.Window()
-       oldWindow.get_child().reparent(newWindow)
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
        app.add_window(newWindow)
        return newWindow
  
@@@ -356,6 -359,28 +359,28 @@@ else
        hildonize_combo_entry = _null_hildonize_combo_entry
  
  
+ def _null_create_seekbar():
+       adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
+       seek = gtk.HScale(adjustment)
+       seek.set_draw_value(False)
+       return seek
+ def _fremantle_create_seekbar():
+       seek = hildon.Seekbar()
+       seek.set_range(0.0, 100)
+       seek.set_draw_value(False)
+       seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
+       return seek
+ try:
+       hildon.Seekbar
+       create_seekbar = _fremantle_create_seekbar
+ except AttributeError:
+       create_seekbar = _null_create_seekbar
  def _fremantle_hildonize_scrollwindow(scrolledWindow):
        pannableWindow = hildon.PannableArea()
  
diff --combined support/builddeb.py
  import os
  import sys
  
 -try:
 -      import py2deb
 -except ImportError:
 -      import fake_py2deb as py2deb
 +import py2deb
  
  import constants
  
  
  __appname__ = constants.__app_name__
 -__description__ = """REPLACEME
 -REPLACEME
 +__description__ = """Unit Conversions
 +A conversion utility that allows conversion between many units like CGS, Ancient, Imperial with many categories like length, mass, numbers, etc. All units converted values shown at once as you type
  .
 -Homepage:
 +Homepage: http://www.unihedron.com/projects/gonvert/index.php
  """
 -__author__ = "Ed Page"
 -__email__ = "eopage@byu.net"
 +__author__ = "Anthony Tekatch"
 +__email__ = "anthony@unihedron.com"
  __version__ = constants.__version__
  __build__ = constants.__build__
  __changelog__ = """
 -REPLACEME
 +0.9.3
 +* Rotation support through Ctrl+o
 +* Switching from scrollbar to panning
- 0.9.2
- * Added search toggle to the menu
- * Maemo 5: Added sorting to the app menu
- 0.9.1
- * Added support for creating generic .deb files
- * Added an apothecary unit
- * Bug fix: Can directly enter numbers after selecting category
- * Bug fix: font of the category button was inconsistent
- * Bug fix: Improved up/down arrow keys
- 0.9.0
- * Added Radioactivity and Radiation dose categories.
- * Aligning the numbers by their decimal place
- * Added shortcuts for fullscreen
- * Switched to Find being brought up by CTRL+F
- * Added Find Previous and Find Next shortcuts (CTRL+P, CTRL+N)
- * Adjusted the sizing on various widgets
- * Removed unused UI features for polish
- * Bug fix: improved behavior when corner case values are inputted (like floats for base conversions)
- * Debugging: Added logging support
- * Marketting: Huge version bump to express a basic level of feature complete
- * Internal: Massive cleanup of code
- 0.2.23  - Added UK currency category and other UK measurements thanks to Dale Hair
- 0.2.22  - Restore previously used window size
- 0.2.21  - Category column widened. Maximize on start.
- 0.2.20  - correction in micron pressure conversion
- 0.2.19  - viscosity cP conversion correction
- 0.2.18        - addition of magnitudes per square arcsecond to Luminance category
- 0.2.17        - updated baud definitions
-       - fixed homepath location because new debian version changed
- 0.2.16        - fixed icon locating for display in about
-       - added alternate icon gonvert-icon_alernative.png (copy over gonvert.png)
- 0.2.15        - updated mainloop to main as discovered by Alexander Skwar
- 0.2.14        - added Calgary energy and volume suggestions per Kim Lux
- 0.2.13        - new more easily understandable icon
-       - nanotesla definition (nT).
-       - added shortlist feature.
- 0.2.12        - removed inoperable books feature.
-       - fixed up acre accuracy.
- 0.2.11        - miodified descriprion for silver, newton, sadzhens.
- 0.2.10        - \x90 changed to \u00C9 for Emile and similar for Reaumur utf-8 text.
-       - Added translation for "All" book text.
-       - The write units text is translatable.
-       - The pl_messages.po file has been updated
- 0.2.09        - Added utf-8 coding to all text strings in preparation for complete language translation.
- 0.2.08        - Added language translation for menus and labels.
- 0.2.07        - Added language translation changes and messages.pot.
- 0.2.06        - Fixed category list size to show preselected categorys on startup,
-         scroll window H&Vpolicy set to always.
- 0.2.05        - Spelling of Luminance category fixed.
- 0.2.04        - Modified unit clicking to force focus on value entry.
-         Modified Makefile to remove /share/share bug for desktop entry.
- 0.2.03        - Modified Makefile to allow better integration on other platforms.
- 0.2.01        - Added saved selections feature, creates ~/.gonvert/ and file. 
- 0.1.11        - fixed packaging for RPM
- 0.1.10        - added Current Loop category for PLCs and 4-20mA instrumentation.
- 0.1.9 - added kilobit, and more density units.
- 0.1.8 - Added Torque units
- 0.1.7 - Added many more pressure units
-       - Added thermal categories
-       - Added fuel consumption category
-       - Program extension to .pyw so that Windows startup without console
- 0.1.6 - add more frequency units
-       - fixed computer number bases nums was bad near "h" and "v"
-       - fixed error:
-         "GtkTextBuffer.insert_at_cursor() takes exactly 1 argument (2 given)"
-         thanks to Riccardo Galli
- 0.1.5 - put packages into /usr instead of /usr/local
-       - add gnome menu item back in
- 0.1.4 - remove dependency on gnome-config from Makefile, RPM, binary.
- 0.1.3 - touched up computer numbers units for better sorting
-       - limited up resizing of windows to prevent dissapearing areas
-       - fixed find bug that some users might notice (TreeViewColumn/None)
- 0.1.2 - Added description box when writing units
- 0.1.1 - Added help/about box
-       - fixed bug that sets focus on line 2480
-       - fixed temperature difference units labels
-       - all scroll bars only show when needed
-       - Added RPM distribution
- 0.1.0   - Major modifications for GTK2 (RedHat 8.0)
-       - addition of units column in display
-       - sorting for all units columns with sort pointer
- 0.0.15        - added Electromagnetic Radiation category
- 0.0.14        - fixed window close bug, attempt to fix libglade XML startup bug for
-           some machines
- 0.0.13        - changes for python2.2, had to remove gnome dependencies
- 0.0.12        - change contact information address
- 0.0.11        - addition of ppm to "find" utility
- 0.0.10        - addition of petabyte to computer data
- 0.0.9 - addition of cesium atom vibrations to Time category
- 0.0.8 - more accurate calculation of degrees F
- 0.0.7 - added 'Find unit' feature
-       - changed Category list to clist for ease of moveto (focus) after find
- 0.0.6 - added description for Amperes
-       - added DENSITY category
-       - added 4 new categories 101 new units
-       - added shoe size converter
-       - add a function to convert custom formulas (like area from diameter)
-         example: area = pi * (D/2)^2
-         base value = pi* (x/2)^2  #metres in diameter metres, cm, inch, foot.
- 0.0.5 - Tool for listing all categories and units to STDOUT.
-       - re-organization of project files.
-       - addition of suffixes between duodecillion and centillion.
-       - addition of Makefile to install onto Gnome based systems.
-       - sort Units or Value columns (ascending or descending)
-         by clicking on column.
- 0.0.4 - Prefixes and Suffixes addition of;
-         ppm, %, Marx brothers, various descriptions.
-       - addition of microgram to mass category.
-       - replaced base 63 with 62 from computer numbers since
-         only 62 characters can be represented.
-       - fixed error if second line has nothing it wouldn't get
-         updated.
- 0.0.3 - fix bug in labelling of base 36 (was base 37)
-         all numbering systems past 23 were at fault due
-         to improper nums string (fixed).
- 0.0.2 - Completion of second row data entry so that changes
-         to text are not cyclicly causing changes to all
-         values.
- 0.0.1 - Initial release.
  """
  
  
  __postinstall__ = """#!/bin/sh -e
  
  gtk-update-icon-cache -f /usr/share/icons/hicolor
 +rm -f ~/.gonvert/gonvert.log ~/.gonvert/selections.dat ~/.gonvert/window.dat
 +"""
 +
 +__preremove__ = """#!/bin/sh -e
  """
  
  
@@@ -188,11 -62,11 +65,11 @@@ def build_package(distribution)
        p = py2deb.Py2deb(__appname__)
        p.prettyName = constants.__pretty_app_name__
        p.description = __description__
 -      p.bugTracker = ""
 +      p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=Gonvert"
        p.upgradeDescription = __changelog__.split("\n\n", 1)[0]
        p.author = __author__
        p.mail = __email__
 -      p.license = "lgpl"
 +      p.license = "gpl"
        p.depends = ", ".join([
                "python2.6 | python2.5",
                "python-gtk2 | python2.5-gtk2",
        maemoSpecificDepends = ", python-osso | python2.5-osso, python-hildon | python2.5-hildon"
        p.depends += {
                "debian": ", python-glade2",
 -              "diablo": maemoSpecificDepends + ", python2.5-conic",
 -              "fremantle": maemoSpecificDepends + ", python-glade2, python-alarm",
 +              "diablo": maemoSpecificDepends,
 +              "fremantle": maemoSpecificDepends + ", python-glade2",
        }[distribution]
        p.recommends = ", ".join([
        ])
        p.section = {
 -              "debian": "REPLACEME",
 -              "diablo": "user/REPLACEME",
 -              "fremantle": "user/REPLACEME",
 +              "debian": "science",
 +              "diablo": "user/science",
 +              "fremantle": "user/science",
        }[distribution]
        p.arch = "all"
        p.urgency = "low"
        p.repository = "extras"
        p.changelog = __changelog__
        p.postinstall = __postinstall__
 +      p.preremove = __preremove__
        p.icon = {
 -              "debian": "REPLACEME",
 -              "diablo": "REPLACEME",
 -              "fremantle": "REPLACEME", # Fremantle natively uses 48x48
 +              "debian": "data-pixmaps-gonvert.png",
 +              "diablo": "data-pixmaps-gonvert.png",
 +              "fremantle": "data-pixmaps-gonvert.png", # Fremantle natively uses 48x48
        }[distribution]
 -      p["/usr/bin"] = [ "REPLACEME" ]
 -      for relPath, files in unflatten_files(find_files(".")).iteritems():
 -              fullPath = ""
 +      p["/usr/bin"] = [ "gonvert.py" ]
 +      for relPath, files in unflatten_files(find_files("src", ".")).iteritems():
 +              fullPath = "/usr/lib/gonvert"
 +              if relPath:
 +                      fullPath += os.sep+relPath
 +              p[fullPath] = list(
 +                      "|".join((oldName, newName))
 +                      for (oldName, newName) in files
 +              )
 +      for relPath, files in unflatten_files(find_files("data", ".")).iteritems():
 +              fullPath = "/usr/share/gonvert"
                if relPath:
                        fullPath += os.sep+relPath
                p[fullPath] = list(
                        "|".join((oldName, newName))
                        for (oldName, newName) in files
                )
 -      p["/usr/share/applications/hildon"] = ["REPLACEME.desktop"]
 -      p["/usr/share/icons/hicolor/26x26/hildon"] = ["REPLACEME"]
 -      p["/usr/share/icons/hicolor/64x64/hildon"] = ["REPLACEME"]
 -      p["/usr/share/icons/hicolor/scalable/hildon"] = ["REPLACEME"]
 +      p["/usr/share/applications/hildon"] = ["gonvert.desktop"]
 +      p["/usr/share/icons/hicolor/26x26/hildon"] = ["data-pixmaps-gonvert.png|gonvert.png"]
 +      p["/usr/share/icons/hicolor/64x64/hildon"] = ["data-pixmaps-gonvert.png|gonvert.png"]
 +      p["/usr/share/icons/hicolor/scalable/hildon"] = ["data-pixmaps-gonvert.png|gonvert.png"]
  
        if distribution == "debian":
                print p