Switching category so .deb can actually build
[ejpi] / src / ejpi_glade.py
index 507a345..a31d4f7 100755 (executable)
@@ -1,18 +1,6 @@
 #!/usr/bin/python
 
 """
-@todo Add preference file
-       @li enable/disable plugins
-       @li plugin search path
-       @li Number format
-       @li Current tab
-@todo Expand operations to support
-       @li mathml then to cairo?
-       @li cairo directly?
-@todo Expanded copy/paste (Unusure how far to go)
-       @li Copy formula, value, serialized, mathml, latex?
-       @li Paste serialized, value?
-
 Some useful things on Maemo
 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Statesave.html
 @li http://maemo.org/api_refs/4.1/libosso-2.16-1/group__Autosave.html
@@ -26,27 +14,31 @@ import sys
 import gc
 import os
 import string
+import logging
 import warnings
 
 import gtk
 import gtk.glade
 
-try:
-       import hildon
-except ImportError:
-       hildon = None
+import hildonize
 
 from libraries import gtkpie
 from libraries import gtkpieboard
 import plugin_utils
 import history
 import gtkhistory
+import gtk_toolbox
+import constants
+
 
+_moduleLogger = logging.getLogger("ejpi_glade")
 
 PLUGIN_SEARCH_PATHS = [
        os.path.join(os.path.dirname(__file__), "plugins/"),
 ]
 
+PROFILE_STARTUP = False
+
 
 class ValueEntry(object):
 
@@ -93,53 +85,8 @@ class ValueEntry(object):
        value = property(get_value, set_value, clear)
 
 
-class ErrorDisplay(history.ErrorReporting):
-
-       def __init__(self, widgetTree):
-               super(ErrorDisplay, self).__init__()
-               self.__errorBox = widgetTree.get_widget("errorEventBox")
-               self.__errorDescription = widgetTree.get_widget("errorDescription")
-               self.__errorClose = widgetTree.get_widget("errorClose")
-               self.__parentBox = self.__errorBox.get_parent()
-
-               self.__errorBox.connect("button_release_event", self._on_close)
-
-               self.__messages = []
-               self.__parentBox.remove(self.__errorBox)
-
-       def push_message(self, message):
-               if 0 < len(self.__messages):
-                       self.__messages.append(message)
-               else:
-                       self.__show_message(message)
-
-       def pop_message(self):
-               if 0 < len(self.__messages):
-                       self.__show_message(self.__messages[0])
-                       del self.__messages[0]
-               else:
-                       self.__hide_message()
-
-       def _on_close(self, *args):
-               self.pop_message()
-
-       def __show_message(self, message):
-               self.__errorDescription.set_text(message)
-               self.__parentBox.pack_start(self.__errorBox, False, False)
-               self.__parentBox.reorder_child(self.__errorBox, 1)
-
-       def __hide_message(self):
-               self.__errorDescription.set_text("")
-               self.__parentBox.remove(self.__errorBox)
-
-
 class Calculator(object):
 
-       __pretty_app_name__ = "e**(j pi) + 1 = 0"
-       __app_name__ = "ejpi"
-       __version__ = "0.9.3"
-       __app_magic__ = 0xdeadbeef
-
        _glade_files = [
                '/usr/lib/ejpi/ejpi.glade',
                os.path.join(os.path.dirname(__file__), "ejpi.glade"),
@@ -151,10 +98,12 @@ class Calculator(object):
                os.path.join(os.path.dirname(__file__), "plugins/"),
        ]
 
-       _user_data = os.path.expanduser("~/.%s/" % __app_name__)
+       _user_data = constants._data_path_
        _user_settings = "%s/settings.ini" % _user_data
        _user_history = "%s/history.stack" % _user_data
 
+       MIN_BUTTON_SIZE = min(800, 480) // 6 - 20
+
        def __init__(self):
                self.__constantPlugins = plugin_utils.ConstantPluginManager()
                self.__constantPlugins.add_path(*self._plugin_search_paths)
@@ -176,7 +125,7 @@ class Calculator(object):
 
                self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
                self.__keyboardPlugins.add_path(*self._plugin_search_paths)
-               self.__activeKeyboards = {}
+               self.__activeKeyboards = []
 
                for path in self._glade_files:
                        if os.path.isfile(path):
@@ -185,6 +134,7 @@ class Calculator(object):
                else:
                        self.display_error_message("Cannot find ejpi.glade")
                        gtk.main_quit()
+                       return
                try:
                        os.makedirs(self._user_data)
                except OSError, e:
@@ -192,33 +142,30 @@ class Calculator(object):
                                raise
 
                self._clipboard = gtk.clipboard_get()
-               self.__window = self._widgetTree.get_widget("mainWindow")
+               self._window = self._widgetTree.get_widget("mainWindow")
 
-               global hildon
                self._app = None
                self._isFullScreen = False
-               if hildon is not None:
-                       self._app = hildon.Program()
-                       self.__window = hildon.Window()
-                       self._widgetTree.get_widget("mainLayout").reparent(self.__window)
-                       self._app.add_window(self.__window)
-                       hildon.hildon_helper_set_thumb_scrollbar(self._widgetTree.get_widget('scrollingHistory'), True)
-
-                       gtkMenu = self._widgetTree.get_widget("mainMenubar")
-                       menu = gtk.Menu()
-                       for child in gtkMenu.get_children():
-                               child.reparent(menu)
-                       self.__window.set_menu(menu)
-                       gtkMenu.destroy()
-
-                       self.__window.connect("key-press-event", self._on_key_press)
-                       self.__window.connect("window-state-event", self._on_window_state_change)
-               else:
-                       pass # warnings.warn("No Hildon", UserWarning, 2)
+               self._app = hildonize.get_app_class()()
+               self._window = hildonize.hildonize_window(self._app, self._window)
+
+               menu = hildonize.hildonize_menu(
+                       self._window,
+                       self._widgetTree.get_widget("mainMenubar"),
+                       []
+               )
 
-               self.__errorDisplay = ErrorDisplay(self._widgetTree)
+               for scrollingWidgetName in (
+                       "scrollingHistory",
+               ):
+                       scrollingWidget = self._widgetTree.get_widget(scrollingWidgetName)
+                       assert scrollingWidget is not None, scrollingWidgetName
+                       hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget)
+
+               self.__errorDisplay = gtk_toolbox.ErrorDisplay(self._widgetTree)
                self.__userEntry = ValueEntry(self._widgetTree.get_widget("entryView"))
                self.__stackView = self._widgetTree.get_widget("historyView")
+               self.__pluginButton = self._widgetTree.get_widget("keyboardSelectionButton")
 
                self.__historyStore = gtkhistory.GtkCalcHistory(self.__stackView)
                self.__history = history.RpnCalcHistory(
@@ -228,6 +175,7 @@ class Calculator(object):
                )
                self.__load_history()
 
+               # Basic keyboard stuff
                self.__sliceStyle = gtkpie.generate_pie_style(gtk.Button())
                self.__handler = gtkpieboard.KeyboardHandler(self._on_entry_direct)
                self.__handler.register_command_handler("push", self._on_push)
@@ -235,42 +183,57 @@ class Calculator(object):
                self.__handler.register_command_handler("backspace", self._on_entry_backspace)
                self.__handler.register_command_handler("clear", self._on_entry_clear)
 
+               # Main keyboard
                builtinKeyboardId = self.__keyboardPlugins.lookup_plugin("Builtin")
                self.__keyboardPlugins.enable_plugin(builtinKeyboardId)
                self.__builtinPlugin = self.__keyboardPlugins.keyboards["Builtin"].construct_keyboard()
                self.__builtinKeyboard = self.__builtinPlugin.setup(self.__history, self.__sliceStyle, self.__handler)
-               self._widgetTree.get_widget("functionLayout").pack_start(self.__builtinKeyboard)
-               self._widgetTree.get_widget("functionLayout").reorder_child(self.__builtinKeyboard, 0)
+               self._widgetTree.get_widget("mainKeyboard").pack_start(self.__builtinKeyboard)
+               for child in self.__builtinKeyboard.get_children():
+                       child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
+
+               # Plugins
                self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Trigonometry"))
                self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Computer"))
                self.enable_plugin(self.__keyboardPlugins.lookup_plugin("Alphabet"))
-
-               callbackMapping = {
-                       "on_calculator_quit": self._on_close,
-                       "on_paste": self._on_paste,
-                       "on_clear_history": self._on_clear_all,
-                       "on_about": self._on_about_activate,
-               }
-               self._widgetTree.signal_autoconnect(callbackMapping)
-
-               if self.__window:
-                       if hildon is None:
-                               self.__window.set_title("%s" % self.__pretty_app_name__)
-                       self.__window.connect("destroy", self._on_close)
-                       self.__window.show_all()
+               self._set_plugin_kb(0)
+
+               # Callbacks
+               if not hildonize.IS_FREMANTLE_SUPPORTED:
+                       # Menus aren't used in the Fremantle version
+                       callbackMapping = {
+                               "on_calculator_quit": self._on_close,
+                               "on_paste": self._on_paste,
+                               "on_clear_history": self._on_clear_all,
+                               "on_about": self._on_about_activate,
+                       }
+                       self._widgetTree.signal_autoconnect(callbackMapping)
+                       self._widgetTree.get_widget("copyMenuItem").connect("activate", self._on_copy)
+                       self._widgetTree.get_widget("copyEquationMenuItem").connect("activate", self._on_copy_equation)
+               self._window.connect("key-press-event", self._on_key_press)
+               self._window.connect("window-state-event", self._on_window_state_change)
+               self._widgetTree.get_widget("entryView").connect("activate", self._on_push)
+               self.__pluginButton.connect("clicked", self._on_kb_plugin_selection_button)
+
+               hildonize.set_application_title(self._window, "%s" % constants.__pretty_app_name__)
+               self._window.connect("destroy", self._on_close)
+               self._window.show_all()
+
+               if not hildonize.IS_HILDON_SUPPORTED:
+                       _moduleLogger.warning("No hildonization support")
 
                try:
                        import osso
                except ImportError:
                        osso = None
-
                self._osso = None
+               self._deviceState = None
                if osso is not None:
-                       self._osso = osso.Context(Calculator.__app_name__, Calculator.__version__, False)
-                       device = osso.DeviceState(self._osso)
-                       device.set_device_state_callback(self._on_device_state_change, 0)
+                       self._osso = osso.Context(constants.__app_name__, constants.__version__, False)
+                       self._deviceState = osso.DeviceState(self._osso)
+                       self._deviceState.set_device_state_callback(self._on_device_state_change, 0)
                else:
-                       pass # warnings.warn("No OSSO", UserWarning, 2)
+                       _moduleLogger.warning("No OSSO support")
 
        def display_error_message(self, msg):
                error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
@@ -287,16 +250,36 @@ class Calculator(object):
                pluginName = pluginData[0]
                plugin = self.__keyboardPlugins.keyboards[pluginName].construct_keyboard()
                pluginKeyboard = plugin.setup(self.__history, self.__sliceStyle, self.__handler)
+               for child in pluginKeyboard.get_children():
+                       child.set_size_request(self.MIN_BUTTON_SIZE, self.MIN_BUTTON_SIZE)
 
-               keyboardTabs = self._widgetTree.get_widget("pluginKeyboards")
-               keyboardTabs.append_page(pluginKeyboard, gtk.Label(pluginName))
-               keyboardPageNum = keyboardTabs.page_num(pluginKeyboard)
-               assert keyboardPageNum not in self.__activeKeyboards
-               self.__activeKeyboards[keyboardPageNum] = {
+               self.__activeKeyboards.append({
                        "pluginName": pluginName,
                        "plugin": plugin,
                        "pluginKeyboard": pluginKeyboard,
-               }
+               })
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_kb_plugin_selection_button(self, *args):
+               pluginNames = [plugin["pluginName"] for plugin in self.__activeKeyboards]
+               oldIndex = pluginNames.index(self.__pluginButton.get_label())
+               newIndex = hildonize.touch_selector(self._window, "Keyboards", pluginNames, oldIndex)
+               self._set_plugin_kb(newIndex)
+
+       def _set_plugin_kb(self, pluginIndex):
+               plugin = self.__activeKeyboards[pluginIndex]
+               self.__pluginButton.set_label(plugin["pluginName"])
+
+               pluginParent = self._widgetTree.get_widget("pluginKeyboard")
+               oldPluginChildren = pluginParent.get_children()
+               if oldPluginChildren:
+                       assert len(oldPluginChildren) == 1, "%r" % (oldPluginChildren, )
+                       pluginParent.remove(oldPluginChildren[0])
+                       oldPluginChildren[0].hide()
+               pluginKeyboard = plugin["pluginKeyboard"]
+               pluginParent.pack_start(pluginKeyboard)
+
+               pluginKeyboard.show_all()
 
        def __load_history(self):
                serialized = []
@@ -318,6 +301,7 @@ class Calculator(object):
                                line = " ".join(data for data in lineData)
                                f.write("%s\n" % line)
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_device_state_change(self, shutdown, save_unsaved_data, memory_low, system_inactivity, message, userData):
                """
                For system_inactivity, we have no background tasks to pause
@@ -330,63 +314,104 @@ class Calculator(object):
                if save_unsaved_data or shutdown:
                        self.__save_history()
 
+       @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_close(self, *args, **kwds):
                try:
                        self.__save_history()
+
+                       try:
+                               self._deviceState.close()
+                       except AttributeError:
+                               pass # Either None or close was removed (in Fremantle)
+                       try:
+                               self._osso.close()
+                       except AttributeError:
+                               pass # Either None or close was removed (in Fremantle)
                finally:
                        gtk.main_quit()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_copy(self, *args):
+               equationNode = self.__history.history.peek()
+               result = str(equationNode.evaluate())
+               self._clipboard.set_text(result)
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_copy_equation(self, *args):
+               equationNode = self.__history.history.peek()
+               equation = str(equationNode)
+               self._clipboard.set_text(equation)
+
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_paste(self, *args):
                contents = self._clipboard.wait_for_text()
                self.__userEntry.append(contents)
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_key_press(self, widget, event, *args):
-               """
-               @note Hildon specific
-               """
-               if event.keyval == gtk.keysyms.F6:
+               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.__window.unfullscreen()
+                               self._window.unfullscreen()
                        else:
-                               self.__window.fullscreen()
-
+                               self._window.fullscreen()
+               elif event.keyval == ord("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_push(self, *args):
                self.__history.push_entry()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_unpush(self, *args):
                self.__historyStore.unpush()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_entry_direct(self, keys, modifiers):
                if "shift" in modifiers:
                        keys = keys.upper()
                self.__userEntry.append(keys)
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_entry_backspace(self, *args):
                self.__userEntry.pop()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_entry_clear(self, *args):
                self.__userEntry.clear()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_clear_all(self, *args):
                self.__history.clear()
 
+       @gtk_toolbox.log_exception(_moduleLogger)
        def _on_about_activate(self, *args):
                dlg = gtk.AboutDialog()
-               dlg.set_name(self.__pretty_app_name__)
-               dlg.set_version(self.__version__)
+               dlg.set_name(constants.__pretty_app_name__)
+               dlg.set_version(constants.__version__)
                dlg.set_copyright("Copyright 2008 - LGPL")
-               dlg.set_comments("")
-               dlg.set_website("")
-               dlg.set_authors([""])
+               dlg.set_comments("""
+ejpi A Touch Screen Optimized RPN Calculator for Maemo and Linux.
+
+RPN: Stack based math, its fun
+Buttons: Try both pressing and hold/drag
+History: Try dragging things around, deleting them, etc
+""")
+               dlg.set_website("http://ejpi.garage.maemo.org")
+               dlg.set_authors(["Ed Page <eopage@byu.net>"])
                dlg.run()
                dlg.destroy()
 
@@ -406,10 +431,11 @@ def run_calculator():
        gtk.gdk.threads_init()
 
        gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
-       if hildon is not None:
-               gtk.set_application_name(Calculator.__pretty_app_name__)
+       if hildonize.IS_HILDON_SUPPORTED:
+               gtk.set_application_name(constants.__pretty_app_name__)
        handle = Calculator()
-       gtk.main()
+       if not PROFILE_STARTUP:
+               gtk.main()
 
 
 class DummyOptions(object):
@@ -419,6 +445,7 @@ class DummyOptions(object):
 
 
 if __name__ == "__main__":
+       logging.basicConfig(level=logging.DEBUG)
        if len(sys.argv) > 1:
                try:
                        import optparse