Pulling in Maemo skeleton
authorEd Page <eopage@byu.net>
Wed, 9 Jun 2010 00:30:52 +0000 (19:30 -0500)
committerEd Page <eopage@byu.net>
Wed, 9 Jun 2010 00:30:52 +0000 (19:30 -0500)
59 files changed:
Makefile
src/constants.py
src/ejpi.glade [new file with mode: 0644]
src/ejpi.py [new file with mode: 0755]
src/ejpi_cli.py [new file with mode: 0755]
src/ejpi_glade.py [new file with mode: 0755]
src/gtkhistory.py [new file with mode: 0755]
src/hildonize.py [changed mode: 0644->0755]
src/history.py [new file with mode: 0644]
src/libraries/__init__.py [new file with mode: 0644]
src/libraries/gtkpie.py [new file with mode: 0755]
src/libraries/gtkpieboard.py [new file with mode: 0755]
src/libraries/images/alt.png [new file with mode: 0644]
src/libraries/images/arrows.png [new file with mode: 0644]
src/libraries/images/backspace.png [new file with mode: 0644]
src/libraries/images/clear.png [new file with mode: 0644]
src/libraries/images/control.png [new file with mode: 0644]
src/libraries/images/newline.png [new file with mode: 0644]
src/libraries/images/shift.png [new file with mode: 0644]
src/libraries/images/space.png [new file with mode: 0644]
src/libraries/images/super.png [new file with mode: 0644]
src/libraries/images/symbols.dia [new file with mode: 0644]
src/libraries/images/tab.png [new file with mode: 0644]
src/libraries/recipes/__init__.py [new file with mode: 0644]
src/libraries/recipes/algorithms.py [new file with mode: 0644]
src/libraries/recipes/concurrent.py [new file with mode: 0644]
src/libraries/recipes/coroutines.py [new file with mode: 0755]
src/libraries/recipes/datatypes.py [new file with mode: 0644]
src/libraries/recipes/gtk_utils.py [new file with mode: 0644]
src/libraries/recipes/io.py [new file with mode: 0644]
src/libraries/recipes/misc.py [new file with mode: 0644]
src/libraries/recipes/operators.py [new file with mode: 0644]
src/libraries/recipes/overloading.py [new file with mode: 0644]
src/libraries/recipes/test_utils.py [new file with mode: 0644]
src/libraries/recipes/xml_utils.py [new file with mode: 0755]
src/operation.py [new file with mode: 0644]
src/plugin_utils.py [new file with mode: 0644]
src/plugins/__init__.py [new file with mode: 0644]
src/plugins/alphabet.ini [new file with mode: 0644]
src/plugins/alphabet.map [new file with mode: 0644]
src/plugins/alphabet.py [new file with mode: 0644]
src/plugins/builtins.ini [new file with mode: 0644]
src/plugins/builtins.map [new file with mode: 0644]
src/plugins/builtins.py [new file with mode: 0644]
src/plugins/computer.ini [new file with mode: 0644]
src/plugins/computer.map [new file with mode: 0644]
src/plugins/computer.py [new file with mode: 0644]
src/plugins/trig.ini [new file with mode: 0644]
src/plugins/trig.map [new file with mode: 0644]
src/plugins/trig.py [new file with mode: 0644]
support/builddeb.py
support/ejpi.desktop [new file with mode: 0644]
support/icons/26.png [new file with mode: 0644]
support/icons/64.png [new file with mode: 0644]
support/icons/scalable.png [new file with mode: 0644]
www/download.html
www/ejpi.deb [new file with mode: 0644]
www/images/screenshot-0.9.0-ubuntu.jpg [new file with mode: 0644]
www/index.html

index 6b80980..93aee70 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-PROJECT_NAME=REPLACEME
+PROJECT_NAME=ejpi
 SOURCE_PATH=src
 SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py")
 PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py
@@ -46,9 +46,9 @@ package: $(OBJ)
        $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
        $(foreach file, $(SOURCE), 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/icons/26.png $(BUILD_PATH)/generic/26x26-$(PROJECT_NAME).png
+       cp support/icons/64.png $(BUILD_PATH)/generic/64x64-$(PROJECT_NAME).png
+       cp support/icons/scalable.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
index 8be32c8..9f9a330 100644 (file)
@@ -1,8 +1,8 @@
 import os
 
-__pretty_app_name__ = "REPLACEME"
-__app_name__ = "REPLACEME"
-__version__ = "0.1.0"
+__pretty_app_name__ = "e**(j pi) + 1 = 0"
+__app_name__ = "ejpi"
+__version__ = "0.9.8"
 __build__ = 0
 __app_magic__ = 0xdeadbeef
 _data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
diff --git a/src/ejpi.glade b/src/ejpi.glade
new file mode 100644 (file)
index 0000000..fe1f0ab
--- /dev/null
@@ -0,0 +1,272 @@
+<?xml version="1.0"?>
+<glade-interface>
+  <!-- interface-requires gtk+ 2.16 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <widget class="GtkWindow" id="mainWindow">
+    <property name="title" translatable="yes">Cluttered Calc</property>
+    <property name="default_width">800</property>
+    <property name="default_height">480</property>
+    <child>
+      <widget class="GtkVBox" id="mainLayout">
+        <property name="visible">True</property>
+        <child>
+          <widget class="GtkMenuBar" id="mainMenubar">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkMenuItem" id="fileMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_File</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="fileMenu">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="quitMenuItem">
+                        <property name="label">gtk-quit</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_calculator_quit"/>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkMenuItem" id="editMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_Edit</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="editMenuIte">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="copyMenuItem">
+                        <property name="label">gtk-copy</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="copyEquationMenuItem">
+                        <property name="label" translatable="yes">Copy Equation</property>
+                        <property name="visible">True</property>
+                        <property name="use_stock">False</property>
+                        <child internal-child="image">
+                          <widget class="GtkImage" id="image1">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-copy</property>
+                          </widget>
+                        </child>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="pasteMenuItem">
+                        <property name="label">gtk-paste</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_paste"/>
+                      </widget>
+                    </child>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="deleteMenuItem">
+                        <property name="label">Clear _History</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">False</property>
+                        <signal name="activate" handler="on_clear_history"/>
+                        <child internal-child="image">
+                          <widget class="GtkImage" id="image2">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-delete</property>
+                          </widget>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+            <child>
+              <widget class="GtkMenuItem" id="helpMenuItem">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">_Help</property>
+                <property name="use_underline">True</property>
+                <child>
+                  <widget class="GtkMenu" id="helpMenu">
+                    <property name="visible">True</property>
+                    <child>
+                      <widget class="GtkImageMenuItem" id="aboutMenuItem">
+                        <property name="label">gtk-about</property>
+                        <property name="visible">True</property>
+                        <property name="use_underline">True</property>
+                        <property name="use_stock">True</property>
+                        <signal name="activate" handler="on_about"/>
+                      </widget>
+                    </child>
+                  </widget>
+                </child>
+              </widget>
+            </child>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <widget class="GtkHBox" id="calculatorLayout">
+            <property name="visible">True</property>
+            <child>
+              <widget class="GtkVBox" id="historyLayout">
+                <property name="visible">True</property>
+                <child>
+                  <widget class="GtkScrolledWindow" id="scrollingHistory">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="window_placement">bottom-left</property>
+                    <property name="window_placement_set">True</property>
+                    <child>
+                      <widget class="GtkTreeView" id="historyView">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_visible">False</property>
+                        <property name="reorderable">True</property>
+                        <property name="rules_hint">True</property>
+                        <property name="enable_search">False</property>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEventBox" id="errorEventBox">
+                    <property name="visible">True</property>
+                    <property name="events">GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_STRUCTURE_MASK</property>
+                    <child>
+                      <widget class="GtkHBox" id="errorBox">
+                        <property name="visible">True</property>
+                        <child>
+                          <widget class="GtkImage" id="errorImage">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-dialog-error</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkLabel" id="errorDescription">
+                            <property name="visible">True</property>
+                            <property name="use_markup">True</property>
+                            <property name="ellipsize">end</property>
+                            <property name="single_line_mode">True</property>
+                          </widget>
+                          <packing>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <widget class="GtkImage" id="errorClose">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-close</property>
+                          </widget>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </widget>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkEntry" id="entryView">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="editable">False</property>
+                    <property name="invisible_char">&#x25CF;</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <widget class="GtkVBox" id="functionLayout">
+                <property name="visible">True</property>
+                <child>
+                  <widget class="GtkButton" id="keyboardSelectionButton">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHBox" id="pluginKeyboard">
+                    <property name="visible">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHSeparator" id="hseparator1">
+                    <property name="visible">True</property>
+                  </widget>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <widget class="GtkHBox" id="mainKeyboard">
+                    <property name="visible">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </widget>
+                  <packing>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+              </widget>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </widget>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/src/ejpi.py b/src/ejpi.py
new file mode 100755 (executable)
index 0000000..403cca1
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/python
+
+import os
+import sys
+import logging
+
+
+_moduleLogger = logging.getLogger("dialcentral")
+sys.path.append("/usr/lib/ejpi/")
+
+
+import constants
+import ejpi_glade
+
+
+try:
+       os.makedirs(constants._data_path_)
+except OSError, e:
+       if e.errno != 17:
+               raise
+
+logging.basicConfig(level=logging.DEBUG, filename=constants._user_logpath_)
+_moduleLogger.info("ejpi %s-%s" % (constants.__version__, constants.__build__))
+_moduleLogger.info("OS: %s" % (os.uname()[0], ))
+_moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:])
+_moduleLogger.info("Hostname: %s" % os.uname()[1])
+
+
+ejpi_glade.run_calculator()
diff --git a/src/ejpi_cli.py b/src/ejpi_cli.py
new file mode 100755 (executable)
index 0000000..8fc34c3
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+import os
+
+import plugin_utils
+import history
+
+
+PLUGIN_SEARCH_PATHS = [
+       os.path.join(os.path.dirname(__file__), "plugins/"),
+]
+
+
+OPERATIONS = {}
+
+CONSTANTS = {}
+
+
+class CliEntry(object):
+
+       def __init__(self):
+               self.value = ""
+
+       def set_value(self, value):
+               self.value = value
+
+       def get_value(self):
+               return self.value
+
+       def clear(self):
+               self.value = ""
+
+
+def parse_command(userInput):
+       return OPERATIONS[userInput.strip()]
+
+
+def ambiguous_parse(calc, userInput):
+       try:
+               Node = parse_command(userInput)
+               calc.apply_operation(Node)
+               return True
+       except KeyError:
+               return False
+
+
+def repl():
+       entry = CliEntry()
+       stack = history.CalcHistory()
+       rpnCalc = history.RpnCalcHistory(
+               stack,
+               entry, history.ErrorWarning(),
+               CONSTANTS, OPERATIONS
+       )
+       while True:
+               userInput = raw_input(">")
+               isUsed = ambiguous_parse(rpnCalc, userInput)
+               if not isUsed:
+                       entry.set_value(userInput)
+                       rpnCalc.push_entry()
+
+               if 0 < len(stack):
+                       node = stack.peek()
+                       print "\t= %s" % str(node)
+                       print "\t~= %s" % str(node.simplify(**CONSTANTS))
+
+
+def main():
+       constantPlugins = plugin_utils.ConstantPluginManager()
+       constantPlugins.add_path(*PLUGIN_SEARCH_PATHS)
+       constantPlugins.enable_plugin(constantPlugins.lookup_plugin("Builtin"))
+       CONSTANTS.update(constantPlugins.constants)
+
+       operatorPlugins = plugin_utils.OperatorPluginManager()
+       operatorPlugins.add_path(*PLUGIN_SEARCH_PATHS)
+       operatorPlugins.enable_plugin(operatorPlugins.lookup_plugin("Builtin"))
+       OPERATIONS.update(operatorPlugins.operators)
+
+       repl()
+
+if __name__ == "__main__":
+       main()
diff --git a/src/ejpi_glade.py b/src/ejpi_glade.py
new file mode 100755 (executable)
index 0000000..876374c
--- /dev/null
@@ -0,0 +1,475 @@
+#!/usr/bin/python
+
+"""
+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
+"""
+
+
+from __future__ import with_statement
+
+
+import sys
+import gc
+import os
+import string
+import logging
+import warnings
+
+import gtk
+import gtk.glade
+
+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):
+
+       def __init__(self, widget):
+               self.__widget = widget
+               self.__actualEntryDisplay = ""
+
+       def get_value(self):
+               value = self.__actualEntryDisplay.strip()
+               if any(
+                       0 < value.find(whitespace)
+                       for whitespace in string.whitespace
+               ):
+                       self.clear()
+                       raise ValueError('Invalid input "%s"' % value)
+               return value
+
+       def set_value(self, value):
+               value = value.strip()
+               if any(
+                       0 < value.find(whitespace)
+                       for whitespace in string.whitespace
+               ):
+                       raise ValueError('Invalid input "%s"' % value)
+               self.__actualEntryDisplay = value
+               self.__widget.set_text(value)
+
+       def append(self, value):
+               value = value.strip()
+               if any(
+                       0 < value.find(whitespace)
+                       for whitespace in string.whitespace
+               ):
+                       raise ValueError('Invalid input "%s"' % value)
+               self.set_value(self.get_value() + value)
+
+       def pop(self):
+               value = self.get_value()[0:-1]
+               self.set_value(value)
+
+       def clear(self):
+               self.set_value("")
+
+       value = property(get_value, set_value, clear)
+
+
+class Calculator(object):
+
+       _glade_files = [
+               '/usr/lib/ejpi/ejpi.glade',
+               os.path.join(os.path.dirname(__file__), "ejpi.glade"),
+               os.path.join(os.path.dirname(__file__), "../lib/ejpi.glade"),
+       ]
+
+       _plugin_search_paths = [
+               "/usr/lib/ejpi/plugins/",
+               os.path.join(os.path.dirname(__file__), "plugins/"),
+       ]
+
+       _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)
+               for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
+                       try:
+                               pluginId = self.__constantPlugins.lookup_plugin(pluginName)
+                               self.__constantPlugins.enable_plugin(pluginId)
+                       except:
+                               warnings.warn("Failed to load plugin %s" % pluginName)
+
+               self.__operatorPlugins = plugin_utils.OperatorPluginManager()
+               self.__operatorPlugins.add_path(*self._plugin_search_paths)
+               for pluginName in ["Builtin", "Trigonometry", "Computer", "Alphabet"]:
+                       try:
+                               pluginId = self.__operatorPlugins.lookup_plugin(pluginName)
+                               self.__operatorPlugins.enable_plugin(pluginId)
+                       except:
+                               warnings.warn("Failed to load plugin %s" % pluginName)
+
+               self.__keyboardPlugins = plugin_utils.KeyboardPluginManager()
+               self.__keyboardPlugins.add_path(*self._plugin_search_paths)
+               self.__activeKeyboards = []
+
+               for path in self._glade_files:
+                       if os.path.isfile(path):
+                               self._widgetTree = gtk.glade.XML(path)
+                               break
+               else:
+                       self.display_error_message("Cannot find ejpi.glade")
+                       gtk.main_quit()
+                       return
+               try:
+                       os.makedirs(self._user_data)
+               except OSError, e:
+                       if e.errno != 17:
+                               raise
+
+               self._clipboard = gtk.clipboard_get()
+               self._window = self._widgetTree.get_widget("mainWindow")
+
+               self._app = None
+               self._isFullScreen = False
+               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"),
+               )
+
+               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(
+                       self.__historyStore,
+                       self.__userEntry, self.__errorDisplay,
+                       self.__constantPlugins.constants, self.__operatorPlugins.operators
+               )
+               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)
+               self.__handler.register_command_handler("unpush", self._on_unpush)
+               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("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"))
+               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(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:
+                       _moduleLogger.warning("No OSSO support")
+
+       def display_error_message(self, msg):
+               error_dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, msg)
+
+               def close(dialog, response, editor):
+                       editor.about_dialog = None
+                       dialog.destroy()
+               error_dialog.connect("response", close, self)
+               error_dialog.run()
+
+       def enable_plugin(self, pluginId):
+               self.__keyboardPlugins.enable_plugin(pluginId)
+               pluginData = self.__keyboardPlugins.plugin_info(pluginId)
+               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)
+
+               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 = []
+               try:
+                       with open(self._user_history, "rU") as f:
+                               serialized = (
+                                       (part.strip() for part in line.split(" "))
+                                       for line in f.readlines()
+                               )
+               except IOError, e:
+                       if e.errno != 2:
+                               raise
+               self.__history.deserialize_stack(serialized)
+
+       def __save_history(self):
+               serialized = self.__history.serialize_stack()
+               with open(self._user_history, "w") as f:
+                       for lineData in serialized:
+                               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
+
+               @note Hildon specific
+               """
+               if memory_low:
+                       gc.collect()
+
+               if save_unsaved_data or shutdown:
+                       self.__save_history()
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_window_state_change(self, widget, event, *args):
+               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):
+               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()
+                       else:
+                               self._window.fullscreen()
+               elif event.keyval == ord("c") and event.get_state() & gtk.gdk.CONTROL_MASK:
+                       equationNode = self.__history.history.peek()
+                       result = str(equationNode.evaluate())
+                       self._clipboard.set_text(result)
+               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))
+               elif event.keyval == gtk.keysyms.BackSpace and event.get_state() & gtk.gdk.CONTROL_MASK:
+                       self.__historyStore.unpush()
+               elif event.keyval == gtk.keysyms.BackSpace:
+                       self.__userEntry.pop()
+               elif event.keyval in RETURN_TYPES:
+                       self.__history.push_entry()
+
+       @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(constants.__pretty_app_name__)
+               dlg.set_version(constants.__version__)
+               dlg.set_copyright("Copyright 2008 - LGPL")
+               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()
+
+
+def run_doctest():
+       import doctest
+
+       failureCount, testCount = doctest.testmod()
+       if not failureCount:
+               print "Tests Successful"
+               sys.exit(0)
+       else:
+               sys.exit(1)
+
+
+def run_calculator():
+       gtk.gdk.threads_init()
+
+       gtkpie.IMAGES.add_path(os.path.join(os.path.dirname(__file__), "libraries/images"), )
+       if hildonize.IS_HILDON_SUPPORTED:
+               gtk.set_application_name(constants.__pretty_app_name__)
+       handle = Calculator()
+       if not PROFILE_STARTUP:
+               gtk.main()
+
+
+class DummyOptions(object):
+
+       def __init__(self):
+               self.test = False
+
+
+if __name__ == "__main__":
+       logging.basicConfig(level=logging.DEBUG)
+       if len(sys.argv) > 1:
+               try:
+                       import optparse
+               except ImportError:
+                       optparse = None
+
+               if optparse is not None:
+                       parser = optparse.OptionParser()
+                       parser.add_option("-t", "--test", action="store_true", dest="test", help="Run tests")
+                       (commandOptions, commandArgs) = parser.parse_args()
+       else:
+               commandOptions = DummyOptions()
+               commandArgs = []
+
+       if commandOptions.test:
+               run_doctest()
+       else:
+               run_calculator()
diff --git a/src/gtkhistory.py b/src/gtkhistory.py
new file mode 100755 (executable)
index 0000000..000e609
--- /dev/null
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+"""
+http://www.grigoriev.ru/svgmath/ (MathML->SVG in Python)
+http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++)
+"""
+
+import logging
+
+import gobject
+import pango
+import gtk
+
+
+import gtk_toolbox
+import hildonize
+import history
+import operation
+
+
+_moduleLogger = logging.getLogger("gtkhistory")
+
+
+class GtkCalcHistory(history.AbstractHistory):
+
+       BUTTON_IDX = 0
+       VIEW_DATA_IDX = 1
+       VIEW_RESULT_IDX = 2
+       DATA_IDX = 3
+       RESULT_IDX = 4
+
+       def __init__(self, view):
+               super(GtkCalcHistory, self).__init__()
+               self.__prettyRenderer = operation.render_number()
+               self._historyView = view
+
+               # stock-id, display, value
+               self.__historyStore = gtk.ListStore(
+                       gobject.TYPE_STRING, # stock id for pixbuf
+                       gobject.TYPE_STRING, # view of data
+                       gobject.TYPE_STRING, # view of result
+                       object, # data
+                       object, # result
+               )
+               self._historyView.set_model(self.__historyStore)
+
+               # create the TreeViewColumns to display the data
+               self.__closeColumn = gtk.TreeViewColumn('')
+               self._historyView.append_column(self.__closeColumn)
+
+               self.__historyColumn = gtk.TreeViewColumn('History')
+               self.__historyColumn.set_sort_column_id(0)
+               self.__historyColumn.set_expand(True)
+               self._historyView.append_column(self.__historyColumn)
+
+               self.__resultColumn = gtk.TreeViewColumn('')
+               self.__resultColumn.set_sort_column_id(0)
+               self._historyView.append_column(self.__resultColumn)
+
+               # create a CellRenderers to render the data
+               self.__closeCell = gtk.CellRendererPixbuf()
+               hildonize.set_pix_cell_thumb_selectable(self.__closeCell)
+               self.__closeColumn.pack_start(self.__closeCell, False)
+               self.__closeColumn.set_attributes(self.__closeCell, stock_id=0)
+
+               self.__expressionCell = gtk.CellRendererText()
+               self.__expressionCell.set_property("ellipsize", pango.ELLIPSIZE_MIDDLE)
+               self.__historyColumn.pack_start(self.__expressionCell, True)
+               self.__historyColumn.set_attributes(self.__expressionCell, text=1)
+
+               self.__valueCell = gtk.CellRendererText()
+               self.__valueCell.set_property("ellipsize", pango.ELLIPSIZE_NONE)
+               self.__resultColumn.pack_end(self.__valueCell, False)
+               self.__resultColumn.set_attributes(self.__valueCell, text=2)
+
+               self._historyView.set_reorderable(True)
+               self._historyView.get_selection().set_mode(gtk.SELECTION_SINGLE)
+               self._historyView.connect("row-activated", self._on_close_activated)
+
+       def push(self, node):
+               simpleNode = node.simplify()
+               self.__historyStore.append([
+                       gtk.STOCK_CLOSE,
+                       operation.render_operation(self.__prettyRenderer, node),
+                       operation.render_operation(self.__prettyRenderer, simpleNode),
+                       node,
+                       simpleNode
+               ])
+               selection = self._historyView.get_selection()
+               selectionPath = (len(self.__historyStore)-1, )
+               selection.select_path(selectionPath)
+               self._historyView.scroll_to_cell(selectionPath)
+
+       def pop(self):
+               if len(self.__historyStore) == 0:
+                       raise IndexError("Not enough items in the history for the operation")
+
+               row = self.__historyStore[-1]
+               data = row[self.DATA_IDX]
+               del self.__historyStore[-1]
+
+               return data
+
+       def peek(self):
+               if len(self.__historyStore) == 0:
+                       raise IndexError("Not enough items in the history for the operation")
+               row = self.__historyStore[-1]
+               data = row[self.DATA_IDX]
+               return data
+
+       def clear(self):
+               self.__historyStore.clear()
+
+       def __len__(self):
+               return len(self.__historyStore)
+
+       def __iter__(self):
+               for row in iter(self.__historyStore):
+                       data = row[self.DATA_IDX]
+                       yield data
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_close_activated(self, treeView, path, viewColumn):
+               if viewColumn is self.__closeColumn:
+                       del self.__historyStore[path[0]]
+               elif viewColumn is self.__resultColumn:
+                       row = self.__historyStore[path[0]]
+                       data = row[self.RESULT_IDX]
+                       self.push(data)
+               elif viewColumn is self.__historyColumn:
+                       row = self.__historyStore[path[0]]
+                       data = row[self.DATA_IDX]
+                       self.push(data)
+               else:
+                       assert False
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/src/history.py b/src/history.py
new file mode 100644 (file)
index 0000000..05e1b5b
--- /dev/null
@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+
+
+import weakref
+import warnings
+
+from libraries.recipes import algorithms
+import operation
+
+
+__BASE_MAPPINGS = {
+       "0x": 16,
+       "0o": 8,
+       "0b": 2,
+}
+
+
+def parse_number(userInput):
+       try:
+               base = __BASE_MAPPINGS.get(userInput[0:2], 10)
+               if base != 10:
+                       userInput = userInput[2:] # Remove prefix
+               value = int(userInput, base)
+               return value, base
+       except ValueError:
+               pass
+
+       try:
+               value = float(userInput)
+               return value, 10
+       except ValueError:
+               pass
+
+       try:
+               value = complex(userInput)
+               return value, 10
+       except ValueError:
+               pass
+
+       raise ValueError('Cannot parse "%s" as a number' % userInput)
+
+
+class AbstractHistory(object):
+       """
+       Is it just me or is this class name begging for some jokes?
+       """
+
+       def push(self, node):
+               raise NotImplementedError
+
+       def pop(self):
+               raise NotImplementedError
+
+       def unpush(self):
+               node = self.pop()
+               for child in node.get_children():
+                       self.push(child)
+
+       def peek(self):
+               raise NotImplementedError
+
+       def clear(self):
+               raise NotImplementedError
+
+       def __len__(self):
+               raise NotImplementedError
+
+       def __iter__(self):
+               raise NotImplementedError
+
+
+class CalcHistory(AbstractHistory):
+
+       def __init__(self):
+               super(CalcHistory, self).__init__()
+               self.__nodeStack = []
+
+       def push(self, node):
+               assert node is not None
+               self.__nodeStack.append(node)
+               return node
+
+       def pop(self):
+               popped = self.__nodeStack[-1]
+               del self.__nodeStack[-1]
+               return popped
+
+       def peek(self):
+               return self.__nodeStack[-1]
+
+       def clear(self):
+               self.__nodeStack = []
+
+       def __len__(self):
+               return len(self.__nodeStack)
+
+       def __iter__(self):
+               return self.__nodeStack[::-1]
+
+
+class RpnCalcHistory(object):
+
+       def __init__(self, history, entry, errorReporting, constants, operations):
+               self.history = history
+               self.__entry = weakref.ref(entry)
+
+               self.__errorReporter = errorReporting
+               self.__constants = constants
+               self.__operations = operations
+
+               self.__serialRenderer = operation.render_number()
+
+       @property
+       def errorReporter(self):
+               return self.__errorReporter
+
+       @property
+       def OPERATIONS(self):
+               return self.__operations
+
+       @property
+       def CONSTANTS(self):
+               return self.__constants
+
+       def clear(self):
+               self.history.clear()
+               self.__entry().clear()
+
+       def push_entry(self):
+               value = self.__entry().get_value()
+
+               valueNode = None
+               if 0 < len(value):
+                       valueNode = self._parse_value(value)
+                       self.history.push(valueNode)
+
+               self.__entry().clear()
+               return valueNode
+
+       def apply_operation(self, Node):
+               try:
+                       self.push_entry()
+
+                       node = self._apply_operation(Node)
+                       return node
+               except StandardError, e:
+                       self.errorReporter.push_exception()
+                       return None
+
+       def serialize_stack(self):
+               serialized = (
+                       stackNode.serialize(self.__serialRenderer)
+                       for stackNode in self.history
+               )
+               serialized = list(serialized)
+               return serialized
+
+       def deserialize_stack(self, data):
+               for possibleNode in data:
+                       for nodeValue in possibleNode:
+                               if nodeValue in self.OPERATIONS:
+                                       Node = self.OPERATIONS[nodeValue]
+                                       self._apply_operation(Node)
+                               else:
+                                       node = self._parse_value(nodeValue)
+                                       self.history.push(node)
+
+       def _parse_value(self, userInput):
+               try:
+                       value, base = parse_number(userInput)
+                       return operation.Value(value, base)
+               except ValueError:
+                       pass
+
+               try:
+                       return self.CONSTANTS[userInput]
+               except KeyError:
+                       pass
+
+               return operation.Variable(userInput)
+
+       def _apply_operation(self, Node):
+               numArgs = Node.argumentCount
+
+               if len(self.history) < numArgs:
+                       raise ValueError(
+                               "Not enough arguments.  The stack has %d but %s needs %d" % (
+                                       len(self.history), Node.symbol, numArgs
+                               )
+                       )
+
+               args = [arg for arg in algorithms.func_repeat(numArgs, self.history.pop)]
+               args.reverse()
+
+               try:
+                       node = Node(*args)
+               except StandardError:
+                       for arg in args:
+                               self.history.push(arg)
+                       raise
+               self.history.push(node)
+               return node
diff --git a/src/libraries/__init__.py b/src/libraries/__init__.py
new file mode 100644 (file)
index 0000000..3e621cd
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+from pkgutil import extend_path
+
+
+#__path__ = extend_path(__path__, __name__)
diff --git a/src/libraries/gtkpie.py b/src/libraries/gtkpie.py
new file mode 100755 (executable)
index 0000000..80e3d52
--- /dev/null
@@ -0,0 +1,794 @@
+#!/usr/bin/env python
+
+"""
+@todo Handle sizing in a better manner http://www.gtkmm.org/docs/gtkmm-2.4/docs/tutorial/html/sec-custom-widgets.html
+"""
+
+
+from __future__ import division
+
+import os
+import weakref
+import math
+import copy
+import warnings
+
+import gobject
+import gtk
+import cairo
+import pango
+
+try:
+       import rsvg
+except ImportError:
+       rsvg = None
+
+
+def deg_to_rad(deg):
+       return (2 * math.pi * deg) / 360.0
+
+
+def rad_to_deg(rad):
+       return (360.0 * rad) / (2 * math.pi)
+
+
+def normalize_radian_angle(radAng):
+       """
+       Restricts @param radAng to the range [0..2pi)
+       """
+       twoPi = 2 * math.pi
+
+       while radAng < 0:
+               radAng += twoPi
+       while twoPi <= radAng:
+               radAng -= twoPi
+
+       return radAng
+
+
+def delta_to_rtheta(dx, dy):
+       distance = math.sqrt(dx**2 + dy**2)
+
+       angleInRads = math.atan2(-dy, dx)
+       if angleInRads < 0:
+               angleInRads = 2*math.pi + angleInRads
+       return distance, angleInRads
+
+
+class FontCache(object):
+
+       def __init__(self):
+               self.__fontCache = {}
+
+       def get_font(self, s):
+               if s in self.__fontCache:
+                       return self.__fontCache[s]
+
+               descr = pango.FontDescription(s)
+               self.__fontCache[s] = descr
+
+               return descr
+
+
+FONTS = FontCache()
+
+
+class ImageCache(object):
+
+       def __init__(self):
+               self.__imageCache = {}
+               self.__imagePaths = [
+                       os.path.join(os.path.dirname(__file__), "images"),
+               ]
+
+       def add_path(self, path):
+               self.__imagePaths.append(path)
+
+       def get_image(self, s):
+               if s in self.__imageCache:
+                       return self.__imageCache[s]
+
+               image = None
+
+               if s.lower().endswith(".png"):
+                       for path in self.__imagePaths:
+                               imagePath = os.path.join(path, s)
+                               try:
+                                       image = cairo.ImageSurface.create_from_png(imagePath)
+                                       break
+                               except:
+                                       warnings.warn("Unable to load image %s" % imagePath)
+               elif s.lower().endswith(".svg") and rsvg is not None:
+                       for path in self.__imagePaths:
+                               imagePath = os.path.join(path, s)
+                               try:
+                                       image = rsvg.Handle(file=imagePath)
+                               except:
+                                       warnings.warn("Unable to load image %s" % imagePath)
+               else:
+                       print "Don't know how to load image file type:", s
+
+               if image is not None:
+                       self.__imageCache[s] = image
+
+               return image
+
+
+IMAGES = ImageCache()
+
+
+def convert_color(gtkColor):
+       r = gtkColor.red / 65535
+       g = gtkColor.green / 65535
+       b = gtkColor.blue / 65535
+       return r, g, b
+
+
+def generate_pie_style(widget):
+       # GTK states:
+       # * gtk.STATE_NORMAL - The state of a sensitive widget that is not active and does not have the focus
+       # * gtk.STATE_ACTIVE - The state of a sensitive widget when it is active e.g. a button that is pressed but not yet released
+       # * gtk.STATE_PRELIGHT - The state of a sensitive widget that has the focus e.g. a button that has the mouse pointer over it.
+       # * gtk.STATE_SELECTED - The state of a widget that is selected e.g. selected text in a gtk.Entry widget
+       # * gtk.STATE_INSENSITIVE - The state of a widget that is insensitive and will not respond to any events e.g. cannot be activated, selected or prelit.
+
+       widget.ensure_style()
+       gtkStyle = widget.get_style()
+       sliceStyle = dict(
+               (gtkStyleState, {
+                       "text": convert_color(gtkStyle.text[gtkStyleState]),
+                       "fill": convert_color(gtkStyle.bg[gtkStyleState]),
+                       "stroke": None,
+               })
+               for gtkStyleState in (
+                       gtk.STATE_NORMAL, gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT, gtk.STATE_SELECTED, gtk.STATE_INSENSITIVE
+               )
+       )
+
+       return sliceStyle
+
+
+class PieSlice(object):
+
+       SLICE_CENTER = 0
+       SLICE_EAST = 1
+       SLICE_SOUTH_EAST = 2
+       SLICE_SOUTH = 3
+       SLICE_SOUTH_WEST = 4
+       SLICE_WEST = 5
+       SLICE_NORTH_WEST = 6
+       SLICE_NORTH = 7
+       SLICE_NORTH_EAST = 8
+
+       MAX_ANGULAR_SLICES = 8
+
+       SLICE_DIRECTIONS = [
+               SLICE_CENTER,
+               SLICE_EAST,
+               SLICE_SOUTH_EAST,
+               SLICE_SOUTH,
+               SLICE_SOUTH_WEST,
+               SLICE_WEST,
+               SLICE_NORTH_WEST,
+               SLICE_NORTH,
+               SLICE_NORTH_EAST,
+       ]
+
+       SLICE_DIRECTION_NAMES = [
+               "CENTER",
+               "EAST",
+               "SOUTH_EAST",
+               "SOUTH",
+               "SOUTH_WEST",
+               "WEST",
+               "NORTH_WEST",
+               "NORTH",
+               "NORTH_EAST",
+       ]
+
+       def __init__(self, handler = (lambda p, s, d: None)):
+               self._direction = self.SLICE_CENTER
+               self._pie = None
+               self._style = None
+               self._handler = handler
+
+       def menu_init(self, pie, direction):
+               self._direction = direction
+               self._pie = weakref.ref(pie)
+               self._style = pie.sliceStyle
+
+       def calculate_minimum_radius(self, context, textLayout):
+               return 0
+
+       def draw_fg(self, styleState, isSelected, context, textLayout):
+               if isSelected:
+                       styleState = gtk.STATE_ACTIVE
+               self._draw_fg(styleState, context, textLayout)
+
+       def draw_bg(self, styleState, isSelected, context, textLayout):
+               if isSelected:
+                       styleState = gtk.STATE_ACTIVE
+               self._draw_bg(styleState, context, textLayout)
+
+       def _draw_fg(self, styleState, context, textLayout):
+               pass
+
+       def _draw_bg(self, styleState, context, textLayout):
+               centerPosition = self._pie().centerPosition
+               radius = max(self._pie().radius, self.calculate_minimum_radius(context, textLayout))
+               outerRadius = self._pie().outerRadius
+
+               fillColor = self._style[styleState]["fill"]
+               if not fillColor:
+                       return
+
+               if self._direction == self.SLICE_CENTER:
+                       context.arc(
+                               centerPosition[0],
+                               centerPosition[1],
+                               radius,
+                               0,
+                               2 * math.pi
+                       )
+
+                       context.set_source_rgb(*fillColor)
+                       context.fill()
+               else:
+                       sliceCenterAngle = self.quadrant_to_theta(self._direction)
+                       sliceArcWidth = 2*math.pi / self.MAX_ANGULAR_SLICES
+                       sliceStartAngle = sliceCenterAngle - sliceArcWidth/2
+                       sliceEndAngle = sliceCenterAngle + sliceArcWidth/2
+
+                       context.arc(
+                               centerPosition[0],
+                               centerPosition[1],
+                               radius,
+                               sliceStartAngle,
+                               sliceEndAngle,
+                       )
+                       context.arc_negative(
+                               centerPosition[0],
+                               centerPosition[1],
+                               outerRadius,
+                               sliceEndAngle,
+                               sliceStartAngle,
+                       )
+                       context.close_path()
+
+                       context.set_source_rgb(*fillColor)
+                       context.fill()
+
+       def activate(self):
+               self._handler(self._pie(), self, self._direction)
+
+       @classmethod
+       def rtheta_to_quadrant(cls, distance, angleInRads, innerRadius):
+               if distance < innerRadius:
+                       quadrant = 0
+               else:
+                       gradians = angleInRads / (2*math.pi)
+                       preciseQuadrant = gradians * cls.MAX_ANGULAR_SLICES + cls.MAX_ANGULAR_SLICES / (2 * 2*math.pi)
+                       quadrantWithWrap = int(preciseQuadrant)
+                       quadrant = quadrantWithWrap % cls.MAX_ANGULAR_SLICES
+                       quadrant += 1
+
+               return quadrant
+
+       @classmethod
+       def quadrant_to_theta(cls, quadrant):
+               assert quadrant != 0
+               quadrant -= 1
+
+               gradians = quadrant / cls.MAX_ANGULAR_SLICES
+               radians = gradians * 2*math.pi
+
+               return radians
+
+
+class NullPieSlice(PieSlice):
+
+       def draw_bg(self, styleState, isSelected, context, textLayout):
+               super(NullPieSlice, self).draw_bg(styleState, False, context, textLayout)
+
+
+class LabelPieSlice(PieSlice):
+
+       def _align_label(self, labelWidth, labelHeight):
+               centerPosition = self._pie().centerPosition
+               if self._direction == PieSlice.SLICE_CENTER:
+                       labelX = centerPosition[0] - labelWidth/2
+                       labelY = centerPosition[1] - labelHeight/2
+               else:
+                       if self._direction in (PieSlice.SLICE_NORTH_WEST, PieSlice.SLICE_WEST, PieSlice.SLICE_SOUTH_WEST):
+                               outerX = 0
+                               labelX = outerX
+                       elif self._direction in (PieSlice.SLICE_SOUTH, PieSlice.SLICE_NORTH):
+                               outerX = centerPosition[0]
+                               labelX = outerX - labelWidth/2
+                       elif self._direction in (PieSlice.SLICE_NORTH_EAST, PieSlice.SLICE_EAST, PieSlice.SLICE_SOUTH_EAST):
+                               outerX = centerPosition[0] * 2
+                               labelX = outerX - labelWidth
+                       else:
+                               assert False, "Direction %d is incorrect" % self._direction
+
+                       if self._direction in (PieSlice.SLICE_NORTH_EAST, PieSlice.SLICE_NORTH, PieSlice.SLICE_NORTH_WEST):
+                               outerY = 0
+                               labelY = outerY
+                       elif self._direction in (PieSlice.SLICE_EAST, PieSlice.SLICE_WEST):
+                               outerY = centerPosition[1]
+                               labelY = outerY - labelHeight/2
+                       elif self._direction in (PieSlice.SLICE_SOUTH_EAST, PieSlice.SLICE_SOUTH, PieSlice.SLICE_SOUTH_WEST):
+                               outerY = centerPosition[1] * 2
+                               labelY = outerY - labelHeight
+                       else:
+                               assert False, "Direction %d is incorrect" % self._direction
+
+               return int(labelX), int(labelY)
+
+
+class TextLabelPieSlice(LabelPieSlice):
+
+       def __init__(self, text, fontName = 'Helvetica 12', handler = (lambda p, s, d: None)):
+               super(TextLabelPieSlice, self).__init__(handler = handler)
+               self.__text = text
+               self.__fontName = fontName
+
+       def calculate_minimum_radius(self, context, textLayout):
+               font = FONTS.get_font(self.__fontName)
+               textLayout.set_font_description(font)
+               textLayout.set_markup(self.__text)
+
+               labelWidth, labelHeight = textLayout.get_pixel_size()
+               return min(labelWidth, labelHeight) / 2
+
+       def _draw_fg(self, styleState, context, textLayout):
+               super(TextLabelPieSlice, self)._draw_fg(styleState, context, textLayout)
+
+               textColor = self._style[styleState]["text"]
+               font = FONTS.get_font(self.__fontName)
+
+               context.set_source_rgb(*textColor)
+               textLayout.set_font_description(font)
+               textLayout.set_markup(self.__text)
+               labelWidth, labelHeight = textLayout.get_pixel_size()
+               labelX, labelY = self._align_label(labelWidth, labelHeight)
+
+               context.move_to(
+                       labelX,
+                       labelY,
+               )
+
+               context.show_layout(textLayout)
+
+
+class ImageLabelPieSlice(LabelPieSlice):
+
+       def __init__(self, imagePath, handler = (lambda p, s, d: None)):
+               super(ImageLabelPieSlice, self).__init__(handler = handler)
+               self.__imagePath = imagePath
+
+       def calculate_minimum_radius(self, context, textLayout):
+               image = IMAGES.get_image(self.__imagePath)
+               if image is None:
+                       return
+               labelWidth, labelHeight = image.get_width(), image.get_height()
+               return min(labelWidth, labelHeight) / 2
+
+       def _draw_fg(self, styleState, context, textLayout):
+               super(ImageLabelPieSlice, self)._draw_fg(styleState, context, textLayout)
+
+               image = IMAGES.get_image(self.__imagePath)
+               if image is None:
+                       return
+
+               labelWidth, labelHeight = image.get_width(), image.get_height()
+               labelX, labelY = self._align_label(labelWidth, labelHeight)
+
+               context.set_source_surface(
+                       image,
+                       labelX,
+                       labelY,
+               )
+
+               context.paint()
+
+
+class PieMenu(gtk.DrawingArea):
+
+       def __init__(self, style = None, **kwds):
+               super(PieMenu, self).__init__()
+
+               self.sliceStyle = style
+               self.centerPosition = 0, 0
+               self.radius = 20
+               self.outerRadius = self.radius * 2
+
+               self.connect("expose_event", self._on_expose)
+               self.connect("motion_notify_event", self._on_motion_notify)
+               self.connect("leave_notify_event", self._on_leave_notify)
+               self.connect("proximity_in_event", self._on_motion_notify)
+               self.connect("proximity_out_event", self._on_leave_notify)
+               self.connect("button_press_event", self._on_button_press)
+               self.connect("button_release_event", self._on_button_release)
+
+               self.set_events(
+                       gtk.gdk.EXPOSURE_MASK |
+                       gtk.gdk.POINTER_MOTION_MASK |
+                       gtk.gdk.POINTER_MOTION_HINT_MASK |
+                       gtk.gdk.BUTTON_MOTION_MASK |
+                       gtk.gdk.BUTTON_PRESS_MASK |
+                       gtk.gdk.BUTTON_RELEASE_MASK |
+                       gtk.gdk.PROXIMITY_IN_MASK |
+                       gtk.gdk.PROXIMITY_OUT_MASK |
+                       gtk.gdk.LEAVE_NOTIFY_MASK
+               )
+
+               self.__activeSlice = None
+               self.__slices = {}
+               for direction in PieSlice.SLICE_DIRECTIONS:
+                       self.add_slice(NullPieSlice(), direction)
+
+               self.__clickPosition = 0, 0
+               self.__styleState = gtk.STATE_NORMAL
+
+       def add_slice(self, slice, direction):
+               assert direction in PieSlice.SLICE_DIRECTIONS
+
+               slice.menu_init(self, direction)
+               self.__slices[direction] = slice
+
+               if direction == PieSlice.SLICE_CENTER:
+                       self.__activeSlice = self.__slices[PieSlice.SLICE_CENTER]
+
+       def __update_state(self, mousePosition):
+               rect = self.get_allocation()
+               newStyleState = self.__styleState
+
+               if (
+                       0 <= mousePosition[0] and mousePosition[1] < rect.width and
+                       0 <= mousePosition[1] and mousePosition[1] < rect.height
+               ):
+                       if self.__clickPosition == (0, 0):
+                               newStyleState = gtk.STATE_PRELIGHT
+               else:
+                       if self.__clickPosition != (0, 0):
+                               newStyleState = gtk.STATE_PRELIGHT
+
+               if newStyleState != self.__styleState:
+                       self.__generate_draw_event()
+                       self.__styleState = newStyleState
+
+       def __process_mouse_position(self, mousePosition):
+               self.__update_state(mousePosition)
+               if self.__clickPosition == (0, 0):
+                       return
+
+               delta = (
+                       mousePosition[0] - self.centerPosition[0],
+                       - (mousePosition[1] - self.centerPosition[1])
+               )
+               distance, angleInRads = delta_to_rtheta(delta[0], delta[1])
+               quadrant = PieSlice.rtheta_to_quadrant(distance, angleInRads, self.radius)
+               self.__select_slice(self.__slices[quadrant])
+
+       def __select_slice(self, newSlice):
+               if newSlice is self.__activeSlice:
+                       return
+
+               oldSlice = self.__activeSlice
+               self.__activeSlice = newSlice
+               self.__generate_draw_event()
+
+       def __generate_draw_event(self):
+               if self.window is None:
+                       return
+               self.queue_draw()
+
+       def _on_expose(self, widget, event):
+               cairoContext = self.window.cairo_create()
+               pangoContext = self.create_pango_context()
+               textLayout = pango.Layout(pangoContext)
+
+               rect = self.get_allocation()
+               position = 0, 0
+               dimensions = rect.width, rect.height
+
+               self.centerPosition = position[0] + dimensions[0] / 2, position[1] + dimensions[1] / 2
+               self.outerRadius = max(*dimensions) # be larger than the view
+               self.radius = self.outerRadius / (3*2) # fit inside the middle cell
+
+               # Draw Background
+               cairoContext.rectangle(
+                       position[0],
+                       position[1],
+                       dimensions[0],
+                       dimensions[1],
+               )
+               cairoContext.set_source_rgb(*self.sliceStyle[self.__styleState]["fill"])
+               cairoContext.fill()
+
+               isSelected = self.__clickPosition != (0, 0)
+               self.__activeSlice.draw_bg(self.__styleState, isSelected, cairoContext, textLayout)
+
+               # Draw Foreground
+               for slice in self.__slices.itervalues():
+                       isSelected = (slice is self.__activeSlice)
+                       if not isSelected:
+                               slice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout)
+
+               isSelected = self.__clickPosition != (0, 0)
+               self.__activeSlice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout)
+
+       def _on_leave_notify(self, widget, event):
+               newStyleState = gtk.STATE_NORMAL
+               if newStyleState != self.__styleState:
+                       self.__generate_draw_event()
+                       self.__styleState = newStyleState
+
+               mousePosition = event.get_coords()
+               self.__process_mouse_position(mousePosition)
+
+       def _on_motion_notify(self, widget, event):
+               mousePosition = event.get_coords()
+               self.__process_mouse_position(mousePosition)
+
+       def _on_button_press(self, widget, event):
+               self.__clickPosition = event.get_coords()
+
+               self._on_motion_notify(widget, event)
+               self.__generate_draw_event()
+
+       def _on_button_release(self, widget, event):
+               self._on_motion_notify(widget, event)
+
+               self.__activeSlice.activate()
+               self.__activeSlice = self.__slices[PieSlice.SLICE_CENTER]
+               self.__clickPosition = 0, 0
+
+               self.__generate_draw_event()
+
+
+gobject.type_register(PieMenu)
+
+
+class FakeEvent(object):
+
+       def __init__(self, x, y, isHint):
+               self.x = x
+               self.y = y
+               self.is_hint = isHint
+
+       def get_coords(self):
+               return self.x, self.y
+
+
+class PiePopup(gtk.DrawingArea):
+
+       def __init__(self, style = None, **kwds):
+               super(PiePopup, self).__init__()
+
+               self.showAllSlices = True
+               self.sliceStyle = style
+               self.centerPosition = 0, 0
+               self.radius = 20
+               self.outerRadius = self.radius * 2
+
+               self.connect("expose_event", self._on_expose)
+               self.connect("motion_notify_event", self._on_motion_notify)
+               self.connect("proximity_in_event", self._on_motion_notify)
+               self.connect("proximity_out_event", self._on_leave_notify)
+               self.connect("leave_notify_event", self._on_leave_notify)
+               self.connect("button_press_event", self._on_button_press)
+               self.connect("button_release_event", self._on_button_release)
+
+               self.set_events(
+                       gtk.gdk.EXPOSURE_MASK |
+                       gtk.gdk.POINTER_MOTION_MASK |
+                       gtk.gdk.POINTER_MOTION_HINT_MASK |
+                       gtk.gdk.BUTTON_MOTION_MASK |
+                       gtk.gdk.BUTTON_PRESS_MASK |
+                       gtk.gdk.BUTTON_RELEASE_MASK |
+                       gtk.gdk.PROXIMITY_IN_MASK |
+                       gtk.gdk.PROXIMITY_OUT_MASK |
+                       gtk.gdk.LEAVE_NOTIFY_MASK
+               )
+
+               self.__popped = False
+               self.__styleState = gtk.STATE_NORMAL
+               self.__activeSlice = None
+               self.__slices = {}
+               self.__localSlices = {}
+
+               self.__clickPosition = 0, 0
+               self.__popupTimeDelay = None
+
+               self.__pie = None
+               self.__pie = PieMenu(self.sliceStyle)
+               self.__pie.connect("button_release_event", self._on_button_release)
+               self.__pie.show()
+
+               self.__popupWindow = gtk.Window(type = gtk.WINDOW_POPUP)
+               self.__popupWindow.set_title("")
+               self.__popupWindow.add(self.__pie)
+
+               self.add_slice(NullPieSlice(), PieSlice.SLICE_CENTER)
+
+       def add_slice(self, slice, direction):
+               assert direction in PieSlice.SLICE_DIRECTIONS
+
+               self.__pie.add_slice(copy.copy(slice), direction)
+
+               if self.showAllSlices or direction == PieSlice.SLICE_CENTER:
+                       self.__localSlices[direction] = slice
+                       self.__localSlices[direction].menu_init(self, direction)
+               if direction == PieSlice.SLICE_CENTER:
+                       self.__activeSlice = self.__localSlices[PieSlice.SLICE_CENTER]
+
+       def __update_state(self, mousePosition):
+               rect = self.get_allocation()
+               newStyleState = self.__styleState
+
+               if (
+                       0 <= mousePosition[0] and mousePosition[0] < rect.width and
+                       0 <= mousePosition[1] and mousePosition[1] < rect.height
+               ):
+                       if self.__clickPosition == (0, 0):
+                               newStyleState = gtk.STATE_PRELIGHT
+               else:
+                       if self.__clickPosition != (0, 0):
+                               newStyleState = gtk.STATE_PRELIGHT
+
+               if newStyleState != self.__styleState:
+                       self.__styleState = newStyleState
+                       self.__generate_draw_event()
+
+       def __generate_draw_event(self):
+               self.queue_draw()
+
+       def _on_expose(self, widget, event):
+               rect = self.get_allocation()
+               position = 0, 0
+               dimensions = rect.width, rect.height
+
+               # update sizing information
+               self.centerPosition = position[0] + dimensions[0] / 2, position[1] + dimensions[1] / 2
+               self.outerRadius = max(*dimensions) # be larger than the view
+               self.radius = self.outerRadius / (3*2) # fit inside the middle cell
+
+               # Draw Background
+               cairoContext = self.window.cairo_create()
+               cairoContext.rectangle(
+                       position[0],
+                       position[1],
+                       dimensions[0],
+                       dimensions[1],
+               )
+               cairoContext.set_source_rgb(*self.sliceStyle[self.__styleState]["fill"])
+               cairoContext.fill()
+
+               # Draw Foreground
+               pangoContext = self.create_pango_context()
+               textLayout = pango.Layout(pangoContext)
+               for slice in self.__localSlices.itervalues():
+                       isSelected = (slice is self.__activeSlice)
+                       if not isSelected:
+                               slice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout)
+
+               isSelected = self.__clickPosition != (0, 0)
+               self.__activeSlice.draw_fg(self.__styleState, isSelected, cairoContext, textLayout)
+
+       def _on_leave_notify(self, widget, event):
+               newStyleState = gtk.STATE_NORMAL
+               if newStyleState != self.__styleState:
+                       self.__styleState = newStyleState
+                       self.__generate_draw_event()
+
+               self._on_motion_notify(widget, event)
+
+       def _on_motion_notify(self, widget, event):
+               self.__update_state(event.get_coords())
+               if not self.__popped:
+                       return
+
+               mousePosition = event.get_root_coords()
+               piePosition = self.__popupWindow.get_position()
+               event.x = mousePosition[0] - piePosition[0]
+               event.y = mousePosition[1] - piePosition[1]
+               self.__pie._on_motion_notify(self.__pie, event)
+
+       def _on_button_press(self, widget, event):
+               if len(self.__localSlices) == 0:
+                       return
+
+               if self.__popupTimeDelay is not None:
+                       # This press is for a double click for which we do not get a release
+                       # self._on_button_release(widget, event)
+                       return
+
+               self.__clickPosition = event.get_root_coords()
+               self.__generate_draw_event()
+               self.__popupTimeDelay = gobject.timeout_add(150, self._on_delayed_popup)
+
+       def _on_delayed_popup(self):
+               self.__popup(self.__clickPosition)
+               gobject.source_remove(self.__popupTimeDelay)
+               self.__popupTimeDelay = None
+               return False
+
+       def _on_button_release(self, widget, event):
+               if len(self.__localSlices) == 0:
+                       return
+
+               if self.__popupTimeDelay is None:
+                       mousePosition = event.get_root_coords()
+                       piePosition = self.__popupWindow.get_position()
+                       eventX = mousePosition[0] - piePosition[0]
+                       eventY = mousePosition[1] - piePosition[1]
+                       pieRelease = FakeEvent(eventX, eventY, False)
+                       self.__pie._on_button_release(self.__pie, pieRelease)
+
+                       self.__unpop()
+               else:
+                       gobject.source_remove(self.__popupTimeDelay)
+                       self.__popupTimeDelay = None
+                       self.__activeSlice.activate()
+
+               self.__clickPosition = 0, 0
+               self.__generate_draw_event()
+
+       def __popup(self, position):
+               assert not self.__popped
+               self.__popped = True
+
+               width, height = 256, 256
+               popupX, popupY = position[0] - width/2, position[1] - height/2
+
+               self.__popupWindow.move(int(popupX), int(popupY))
+               self.__popupWindow.resize(width, height)
+               pieClick = FakeEvent(width/2, height/2, False)
+               self.__pie._on_button_press(self.__pie, pieClick)
+               self.__pie.grab_focus()
+
+               self.__popupWindow.show()
+
+       def __unpop(self):
+               assert self.__popped
+               self.__popped = False
+
+               piePosition = self.__popupWindow.get_position()
+               self.grab_focus()
+
+               self.__popupWindow.hide()
+
+
+gobject.type_register(PiePopup)
+
+
+def pie_main(isPop):
+       win = gtk.Window()
+       win.set_title("Pie Menu Test")
+
+       sliceStyle = generate_pie_style(win)
+       if isPop:
+               target = PiePopup(sliceStyle)
+       else:
+               target = PieMenu(sliceStyle)
+
+       def handler(pie, slice, direction):
+               print pie, slice, direction
+       target.add_slice(TextLabelPieSlice("C", handler=handler), PieSlice.SLICE_CENTER)
+       target.add_slice(TextLabelPieSlice("N", handler=handler), PieSlice.SLICE_NORTH)
+       target.add_slice(TextLabelPieSlice("S", handler=handler), PieSlice.SLICE_SOUTH)
+       target.add_slice(TextLabelPieSlice("E", handler=handler), PieSlice.SLICE_EAST)
+       target.add_slice(TextLabelPieSlice("W", handler=handler), PieSlice.SLICE_WEST)
+
+       win.add(target)
+       win.resize(300, 300)
+       win.connect("destroy", lambda w: gtk.main_quit())
+       win.show_all()
+
+
+if __name__ == "__main__":
+       pie_main(False)
+       pie_main(True)
+       gtk.main()
diff --git a/src/libraries/gtkpieboard.py b/src/libraries/gtkpieboard.py
new file mode 100755 (executable)
index 0000000..c2c7175
--- /dev/null
@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+
+
+from __future__ import division
+
+import copy
+import warnings
+
+import gobject
+import gtk
+
+import gtkpie
+
+
+class PieKeyboard(gtk.Table):
+
+       def __init__(self, style, rows, columns, alternateStyles=True):
+               super(PieKeyboard, self).__init__(rows, columns, homogeneous=True)
+
+               self.__cells = {}
+               for row in xrange(rows):
+                       for column in xrange(columns):
+                               popup = gtkpie.PiePopup(
+                                       self._alternate_style(row, column, style) if alternateStyles else style
+                               )
+                               self.attach(popup, column, column+1, row, row+1)
+                               self.__cells[(row, column)] = popup
+
+       def add_slice(self, row, column, slice, direction):
+               pie = self.__cells[(row, column)]
+               pie.add_slice(slice, direction)
+
+       def add_slices(self, row, column, slices):
+               pie = self.__cells[(row, column)]
+               for direction, slice in slices.iteritems():
+                       pie.add_slice(slice, direction)
+
+       def get_pie(self, row, column):
+               return self.__cells[(row, column)]
+
+       @classmethod
+       def _alternate_style(cls, row, column, style):
+               i = row + column
+               isEven = (i % 2) == 0
+
+               if not isEven:
+                       return style
+
+               altStyle = copy.copy(style)
+               selected = altStyle[True]
+               notSelected = altStyle[False]
+               altStyle[False] = selected
+               altStyle[True] = notSelected
+               return altStyle
+
+
+class KeyboardModifier(object):
+
+       def __init__(self, name):
+               self.name = name
+               self.lock = False
+               self.once = False
+
+       @property
+       def isActive(self):
+               return self.lock or self.once
+
+       def on_toggle_lock(self, *args, **kwds):
+               self.lock = not self.lock
+
+       def on_toggle_once(self, *args, **kwds):
+               self.once = not self.once
+
+       def reset_once(self):
+               self.once = False
+
+
+gobject.type_register(PieKeyboard)
+
+
+def parse_keyboard_data(text):
+       return eval(text)
+
+
+def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler):
+       for (row, column), pieData in dataTree.iteritems():
+               showAllSlices = pieData["showAllSlices"]
+               keyboard.get_pie(row, column).showAllSlices = showAllSlices
+               for direction, directionName in enumerate(gtkpie.PieSlice.SLICE_DIRECTION_NAMES):
+                       if directionName not in pieData:
+                               continue
+                       sliceName = "%s-(%d, %d)-%s" % (keyboardName, row, column, directionName)
+
+                       sliceData = pieData[directionName]
+                       sliceAction = sliceData["action"]
+                       sliceType = sliceData["type"]
+                       if sliceType == "text":
+                               text = sliceData["text"]
+                               # font = sliceData["font"] # @TODO
+                               slice = gtkpie.TextLabelPieSlice(text, handler=keyboardHandler)
+                       elif sliceType == "image":
+                               path = sliceData["path"]
+                               slice = gtkpie.ImageLabelPieSlice(path, handler=keyboardHandler)
+
+                       slice.name = sliceName
+                       keyboard.add_slice(row, column, slice, direction)
+                       keyboardHandler.map_slice_action(slice, sliceAction)
+
+
+class KeyboardHandler(object):
+
+       def __init__(self, keyhandler):
+               self.__keyhandler = keyhandler
+               self.__commandHandlers = {}
+               self.__modifiers = {}
+               self.__sliceActions = {}
+
+               self.register_modifier("Shift")
+               self.register_modifier("Super")
+               self.register_modifier("Control")
+               self.register_modifier("Alt")
+
+       def register_command_handler(self, command, handler):
+               #@todo Make this handle multiple handlers or switch to gobject events
+               self.__commandHandlers["[%s]" % command] = handler
+
+       def unregister_command_handler(self, command):
+               #@todo Make this handle multiple handlers or switch to gobject events
+               del self.__commandHandlers["[%s]" % command]
+
+       def register_modifier(self, modifierName):
+               mod = KeyboardModifier(modifierName)
+               self.register_command_handler(modifierName, mod.on_toggle_lock)
+               self.__modifiers["<%s>" % modifierName] = mod
+
+       def unregister_modifier(self, modifierName):
+               self.unregister_command_handler(modifierName)
+               del self.__modifiers["<%s>" % modifierName]
+
+       def map_slice_action(self, slice, action):
+               self.__sliceActions[slice.name] = action
+
+       def __call__(self, pie, slice, direction):
+               try:
+                       action = self.__sliceActions[slice.name]
+               except KeyError:
+                       return
+
+               activeModifiers = [
+                       mod.name
+                       for mod in self.__modifiers.itervalues()
+                               if mod.isActive
+               ]
+
+               needResetOnce = False
+               if action.startswith("[") and action.endswith("]"):
+                       commandName = action[1:-1]
+                       if action in self.__commandHandlers:
+                               self.__commandHandlers[action](commandName, activeModifiers)
+                               needResetOnce = True
+                       else:
+                               warnings.warn("Unknown command: [%s]" % commandName)
+               elif action.startswith("<") and action.endswith(">"):
+                       modName = action[1:-1]
+                       for mod in self.__modifiers.itervalues():
+                               if mod.name == modName:
+                                       mod.on_toggle_once()
+                                       break
+                       else:
+                               warnings.warn("Unknown modifier: <%s>" % modName)
+               else:
+                       self.__keyhandler(action, activeModifiers)
+                       needResetOnce = True
+
+               if needResetOnce:
+                       for mod in self.__modifiers.itervalues():
+                               mod.reset_once()
diff --git a/src/libraries/images/alt.png b/src/libraries/images/alt.png
new file mode 100644 (file)
index 0000000..8ccd110
Binary files /dev/null and b/src/libraries/images/alt.png differ
diff --git a/src/libraries/images/arrows.png b/src/libraries/images/arrows.png
new file mode 100644 (file)
index 0000000..6ab51c0
Binary files /dev/null and b/src/libraries/images/arrows.png differ
diff --git a/src/libraries/images/backspace.png b/src/libraries/images/backspace.png
new file mode 100644 (file)
index 0000000..c8d5506
Binary files /dev/null and b/src/libraries/images/backspace.png differ
diff --git a/src/libraries/images/clear.png b/src/libraries/images/clear.png
new file mode 100644 (file)
index 0000000..e18eed4
Binary files /dev/null and b/src/libraries/images/clear.png differ
diff --git a/src/libraries/images/control.png b/src/libraries/images/control.png
new file mode 100644 (file)
index 0000000..f24980e
Binary files /dev/null and b/src/libraries/images/control.png differ
diff --git a/src/libraries/images/newline.png b/src/libraries/images/newline.png
new file mode 100644 (file)
index 0000000..8b242f2
Binary files /dev/null and b/src/libraries/images/newline.png differ
diff --git a/src/libraries/images/shift.png b/src/libraries/images/shift.png
new file mode 100644 (file)
index 0000000..789d7e3
Binary files /dev/null and b/src/libraries/images/shift.png differ
diff --git a/src/libraries/images/space.png b/src/libraries/images/space.png
new file mode 100644 (file)
index 0000000..71207c3
Binary files /dev/null and b/src/libraries/images/space.png differ
diff --git a/src/libraries/images/super.png b/src/libraries/images/super.png
new file mode 100644 (file)
index 0000000..6461770
Binary files /dev/null and b/src/libraries/images/super.png differ
diff --git a/src/libraries/images/symbols.dia b/src/libraries/images/symbols.dia
new file mode 100644 (file)
index 0000000..a04fe5a
Binary files /dev/null and b/src/libraries/images/symbols.dia differ
diff --git a/src/libraries/images/tab.png b/src/libraries/images/tab.png
new file mode 100644 (file)
index 0000000..1040cac
Binary files /dev/null and b/src/libraries/images/tab.png differ
diff --git a/src/libraries/recipes/__init__.py b/src/libraries/recipes/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/libraries/recipes/algorithms.py b/src/libraries/recipes/algorithms.py
new file mode 100644 (file)
index 0000000..040967a
--- /dev/null
@@ -0,0 +1,577 @@
+#!/usr/bin/env python
+
+"""
+@note Source http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66448
+"""
+
+import itertools
+import functools
+import datetime
+import types
+
+
+def ordered_itr(collection):
+       """
+       >>> [v for v in ordered_itr({"a": 1, "b": 2})]
+       [('a', 1), ('b', 2)]
+       >>> [v for v in ordered_itr([3, 1, 10, -20])]
+       [-20, 1, 3, 10]
+       """
+       if isinstance(collection, types.DictType):
+               keys = list(collection.iterkeys())
+               keys.sort()
+               for key in keys:
+                       yield key, collection[key]
+       else:
+               values = list(collection)
+               values.sort()
+               for value in values:
+                       yield value
+
+
+def itercat(*iterators):
+       """
+       Concatenate several iterators into one.
+
+       >>> [v for v in itercat([1, 2, 3], [4, 1, 3])]
+       [1, 2, 3, 4, 1, 3]
+       """
+       for i in iterators:
+               for x in i:
+                       yield x
+
+
+def iterwhile(func, iterator):
+       """
+       Iterate for as long as func(value) returns true.
+       >>> through = lambda b: b
+       >>> [v for v in iterwhile(through, [True, True, False])]
+       [True, True]
+       """
+       iterator = iter(iterator)
+       while 1:
+               next = iterator.next()
+               if not func(next):
+                       raise StopIteration
+               yield next
+
+
+def iterfirst(iterator, count=1):
+       """
+       Iterate through 'count' first values.
+
+       >>> [v for v in iterfirst([1, 2, 3, 4, 5], 3)]
+       [1, 2, 3]
+       """
+       iterator = iter(iterator)
+       for i in xrange(count):
+               yield iterator.next()
+
+
+def iterstep(iterator, n):
+       """
+       Iterate every nth value.
+
+       >>> [v for v in iterstep([1, 2, 3, 4, 5], 1)]
+       [1, 2, 3, 4, 5]
+       >>> [v for v in iterstep([1, 2, 3, 4, 5], 2)]
+       [1, 3, 5]
+       >>> [v for v in iterstep([1, 2, 3, 4, 5], 3)]
+       [1, 4]
+       """
+       iterator = iter(iterator)
+       while True:
+               yield iterator.next()
+               # skip n-1 values
+               for dummy in xrange(n-1):
+                       iterator.next()
+
+
+def itergroup(iterator, count, padValue = None):
+       """
+       Iterate in groups of 'count' values. If there
+       aren't enough values, the last result is padded with
+       None.
+
+       >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
+       ...     print tuple(val)
+       (1, 2, 3)
+       (4, 5, 6)
+       >>> for val in itergroup([1, 2, 3, 4, 5, 6], 3):
+       ...     print list(val)
+       [1, 2, 3]
+       [4, 5, 6]
+       >>> for val in itergroup([1, 2, 3, 4, 5, 6, 7], 3):
+       ...     print tuple(val)
+       (1, 2, 3)
+       (4, 5, 6)
+       (7, None, None)
+       >>> for val in itergroup("123456", 3):
+       ...     print tuple(val)
+       ('1', '2', '3')
+       ('4', '5', '6')
+       >>> for val in itergroup("123456", 3):
+       ...     print repr("".join(val))
+       '123'
+       '456'
+       """
+       paddedIterator = itertools.chain(iterator, itertools.repeat(padValue, count-1))
+       nIterators = (paddedIterator, ) * count
+       return itertools.izip(*nIterators)
+
+
+def xzip(*iterators):
+       """Iterative version of builtin 'zip'."""
+       iterators = itertools.imap(iter, iterators)
+       while 1:
+               yield tuple([x.next() for x in iterators])
+
+
+def xmap(func, *iterators):
+       """Iterative version of builtin 'map'."""
+       iterators = itertools.imap(iter, iterators)
+       values_left = [1]
+
+       def values():
+               # Emulate map behaviour, i.e. shorter
+               # sequences are padded with None when
+               # they run out of values.
+               values_left[0] = 0
+               for i in range(len(iterators)):
+                       iterator = iterators[i]
+                       if iterator is None:
+                               yield None
+                       else:
+                               try:
+                                       yield iterator.next()
+                                       values_left[0] = 1
+                               except StopIteration:
+                                       iterators[i] = None
+                                       yield None
+       while 1:
+               args = tuple(values())
+               if not values_left[0]:
+                       raise StopIteration
+               yield func(*args)
+
+
+def xfilter(func, iterator):
+       """Iterative version of builtin 'filter'."""
+       iterator = iter(iterator)
+       while 1:
+               next = iterator.next()
+               if func(next):
+                       yield next
+
+
+def xreduce(func, iterator, default=None):
+       """Iterative version of builtin 'reduce'."""
+       iterator = iter(iterator)
+       try:
+               prev = iterator.next()
+       except StopIteration:
+               return default
+       single = 1
+       for next in iterator:
+               single = 0
+               prev = func(prev, next)
+       if single:
+               return func(prev, default)
+       return prev
+
+
+def daterange(begin, end, delta = datetime.timedelta(1)):
+       """
+       Form a range of dates and iterate over them.
+
+       Arguments:
+       begin -- a date (or datetime) object; the beginning of the range.
+       end   -- a date (or datetime) object; the end of the range.
+       delta -- (optional) a datetime.timedelta object; how much to step each iteration.
+                       Default step is 1 day.
+
+       Usage:
+       """
+       if not isinstance(delta, datetime.timedelta):
+               delta = datetime.timedelta(delta)
+
+       ZERO = datetime.timedelta(0)
+
+       if begin < end:
+               if delta <= ZERO:
+                       raise StopIteration
+               test = end.__gt__
+       else:
+               if delta >= ZERO:
+                       raise StopIteration
+               test = end.__lt__
+
+       while test(begin):
+               yield begin
+               begin += delta
+
+
+class LazyList(object):
+       """
+       A Sequence whose values are computed lazily by an iterator.
+
+       Module for the creation and use of iterator-based lazy lists.
+       this module defines a class LazyList which can be used to represent sequences
+       of values generated lazily. One can also create recursively defined lazy lists
+       that generate their values based on ones previously generated.
+
+       Backport to python 2.5 by Michael Pust
+       """
+
+       __author__ = 'Dan Spitz'
+
+       def __init__(self, iterable):
+               self._exhausted = False
+               self._iterator = iter(iterable)
+               self._data = []
+
+       def __len__(self):
+               """Get the length of a LazyList's computed data."""
+               return len(self._data)
+
+       def __getitem__(self, i):
+               """Get an item from a LazyList.
+               i should be a positive integer or a slice object."""
+               if isinstance(i, int):
+                       #index has not yet been yielded by iterator (or iterator exhausted
+                       #before reaching that index)
+                       if i >= len(self):
+                               self.exhaust(i)
+                       elif i < 0:
+                               raise ValueError('cannot index LazyList with negative number')
+                       return self._data[i]
+
+               #LazyList slices are iterators over a portion of the list.
+               elif isinstance(i, slice):
+                       start, stop, step = i.start, i.stop, i.step
+                       if any(x is not None and x < 0 for x in (start, stop, step)):
+                               raise ValueError('cannot index or step through a LazyList with'
+                                                               'a negative number')
+                       #set start and step to their integer defaults if they are None.
+                       if start is None:
+                               start = 0
+                       if step is None:
+                               step = 1
+
+                       def LazyListIterator():
+                               count = start
+                               predicate = (
+                                       (lambda: True)
+                                       if stop is None
+                                       else (lambda: count < stop)
+                               )
+                               while predicate():
+                                       try:
+                                               yield self[count]
+                                       #slices can go out of actual index range without raising an
+                                       #error
+                                       except IndexError:
+                                               break
+                                       count += step
+                       return LazyListIterator()
+
+               raise TypeError('i must be an integer or slice')
+
+       def __iter__(self):
+               """return an iterator over each value in the sequence,
+               whether it has been computed yet or not."""
+               return self[:]
+
+       def computed(self):
+               """Return an iterator over the values in a LazyList that have
+               already been computed."""
+               return self[:len(self)]
+
+       def exhaust(self, index = None):
+               """Exhaust the iterator generating this LazyList's values.
+               if index is None, this will exhaust the iterator completely.
+               Otherwise, it will iterate over the iterator until either the list
+               has a value for index or the iterator is exhausted.
+               """
+               if self._exhausted:
+                       return
+               if index is None:
+                       ind_range = itertools.count(len(self))
+               else:
+                       ind_range = range(len(self), index + 1)
+
+               for ind in ind_range:
+                       try:
+                               self._data.append(self._iterator.next())
+                       except StopIteration: #iterator is fully exhausted
+                               self._exhausted = True
+                               break
+
+
+class RecursiveLazyList(LazyList):
+
+       def __init__(self, prod, *args, **kwds):
+               super(RecursiveLazyList, self).__init__(prod(self, *args, **kwds))
+
+
+class RecursiveLazyListFactory:
+
+       def __init__(self, producer):
+               self._gen = producer
+
+       def __call__(self, *a, **kw):
+               return RecursiveLazyList(self._gen, *a, **kw)
+
+
+def lazylist(gen):
+       """
+       Decorator for creating a RecursiveLazyList subclass.
+       This should decorate a generator function taking the LazyList object as its
+       first argument which yields the contents of the list in order.
+
+       >>> #fibonnacci sequence in a lazy list.
+       >>> @lazylist
+       ... def fibgen(lst):
+       ...     yield 0
+       ...     yield 1
+       ...     for a, b in itertools.izip(lst, lst[1:]):
+       ...             yield a + b
+       ...
+       >>> #now fibs can be indexed or iterated over as if it were an infinitely long list containing the fibonnaci sequence
+       >>> fibs = fibgen()
+       >>>
+       >>> #prime numbers in a lazy list.
+       >>> @lazylist
+       ... def primegen(lst):
+       ...     yield 2
+       ...     for candidate in itertools.count(3): #start at next number after 2
+       ...             #if candidate is not divisible by any smaller prime numbers,
+       ...             #it is a prime.
+       ...             if all(candidate % p for p in lst.computed()):
+       ...                     yield candidate
+       ...
+       >>> #same for primes- treat it like an infinitely long list containing all prime numbers.
+       >>> primes = primegen()
+       >>> print fibs[0], fibs[1], fibs[2], primes[0], primes[1], primes[2]
+       0 1 1 2 3 5
+       >>> print list(fibs[:10]), list(primes[:10])
+       [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
+       """
+       return RecursiveLazyListFactory(gen)
+
+
+def map_func(f):
+       """
+       >>> import misc
+       >>> misc.validate_decorator(map_func)
+       """
+
+       @functools.wraps(f)
+       def wrapper(*args):
+               result = itertools.imap(f, args)
+               return result
+       return wrapper
+
+
+def reduce_func(function):
+       """
+       >>> import misc
+       >>> misc.validate_decorator(reduce_func(lambda x: x))
+       """
+
+       def decorator(f):
+
+               @functools.wraps(f)
+               def wrapper(*args):
+                       result = reduce(function, f(args))
+                       return result
+               return wrapper
+       return decorator
+
+
+def any_(iterable):
+       """
+       @note Python Version <2.5
+
+       >>> any_([True, True])
+       True
+       >>> any_([True, False])
+       True
+       >>> any_([False, False])
+       False
+       """
+
+       for element in iterable:
+               if element:
+                       return True
+       return False
+
+
+def all_(iterable):
+       """
+       @note Python Version <2.5
+
+       >>> all_([True, True])
+       True
+       >>> all_([True, False])
+       False
+       >>> all_([False, False])
+       False
+       """
+
+       for element in iterable:
+               if not element:
+                       return False
+       return True
+
+
+def for_every(pred, seq):
+       """
+       for_every takes a one argument predicate function and a sequence.
+       @param pred The predicate function should return true or false.
+       @returns true if every element in seq returns true for predicate, else returns false.
+
+       >>> for_every (lambda c: c > 5,(6,7,8,9))
+       True
+
+       @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907
+       """
+
+       for i in seq:
+               if not pred(i):
+                       return False
+       return True
+
+
+def there_exists(pred, seq):
+       """
+       there_exists takes a one argument predicate     function and a sequence.
+       @param pred The predicate function should return true or false.
+       @returns true if any element in seq returns true for predicate, else returns false.
+
+       >>> there_exists (lambda c: c > 5,(6,7,8,9))
+       True
+
+       @author Source:http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52907
+       """
+
+       for i in seq:
+               if pred(i):
+                       return True
+       return False
+
+
+def func_repeat(quantity, func, *args, **kwd):
+       """
+       Meant to be in connection with "reduce"
+       """
+       for i in xrange(quantity):
+               yield func(*args, **kwd)
+
+
+def function_map(preds, item):
+       """
+       Meant to be in connection with "reduce"
+       """
+       results = (pred(item) for pred in preds)
+
+       return results
+
+
+def functional_if(combiner, preds, item):
+       """
+       Combines the result of a list of predicates applied to item according to combiner
+
+       @see any, every for example combiners
+       """
+       pass_bool = lambda b: b
+
+       bool_results = function_map(preds, item)
+       return combiner(pass_bool, bool_results)
+
+
+def pushback_itr(itr):
+       """
+       >>> list(pushback_itr(xrange(5)))
+       [0, 1, 2, 3, 4]
+       >>>
+       >>> first = True
+       >>> itr = pushback_itr(xrange(5))
+       >>> for i in itr:
+       ...     print i
+       ...     if first and i == 2:
+       ...             first = False
+       ...             print itr.send(i)
+       0
+       1
+       2
+       None
+       2
+       3
+       4
+       >>>
+       >>> first = True
+       >>> itr = pushback_itr(xrange(5))
+       >>> for i in itr:
+       ...     print i
+       ...     if first and i == 2:
+       ...             first = False
+       ...             print itr.send(i)
+       ...             print itr.send(i)
+       0
+       1
+       2
+       None
+       None
+       2
+       2
+       3
+       4
+       >>>
+       >>> itr = pushback_itr(xrange(5))
+       >>> print itr.next()
+       0
+       >>> print itr.next()
+       1
+       >>> print itr.send(10)
+       None
+       >>> print itr.next()
+       10
+       >>> print itr.next()
+       2
+       >>> print itr.send(20)
+       None
+       >>> print itr.send(30)
+       None
+       >>> print itr.send(40)
+       None
+       >>> print itr.next()
+       40
+       >>> print itr.next()
+       30
+       >>> print itr.send(50)
+       None
+       >>> print itr.next()
+       50
+       >>> print itr.next()
+       20
+       >>> print itr.next()
+       3
+       >>> print itr.next()
+       4
+       """
+       for item in itr:
+               maybePushedBack = yield item
+               queue = []
+               while queue or maybePushedBack is not None:
+                       if maybePushedBack is not None:
+                               queue.append(maybePushedBack)
+                               maybePushedBack = yield None
+                       else:
+                               item = queue.pop()
+                               maybePushedBack = yield item
+
+
+if __name__ == "__main__":
+       import doctest
+       print doctest.testmod()
diff --git a/src/libraries/recipes/concurrent.py b/src/libraries/recipes/concurrent.py
new file mode 100644 (file)
index 0000000..bcdc7c3
--- /dev/null
@@ -0,0 +1,328 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import os
+import sys
+import cPickle
+import weakref
+import threading
+import errno
+import time
+import functools
+import contextlib
+
+
+def synchronized(lock):
+       """
+       Synchronization decorator.
+
+       >>> import misc
+       >>> misc.validate_decorator(synchronized(object()))
+       """
+
+       def wrap(f):
+
+               @functools.wraps(f)
+               def newFunction(*args, **kw):
+                       lock.acquire()
+                       try:
+                               return f(*args, **kw)
+                       finally:
+                               lock.release()
+               return newFunction
+       return wrap
+
+
+@contextlib.contextmanager
+def qlock(queue, gblock = True, gtimeout = None, pblock = True, ptimeout = None):
+       """
+       Locking with a queue, good for when you want to lock an item passed around
+
+       >>> import Queue
+       >>> item = 5
+       >>> lock = Queue.Queue()
+       >>> lock.put(item)
+       >>> with qlock(lock) as i:
+       ...     print i
+       5
+       """
+       item = queue.get(gblock, gtimeout)
+       try:
+               yield item
+       finally:
+               queue.put(item, pblock, ptimeout)
+
+
+@contextlib.contextmanager
+def flock(path, timeout=-1):
+       WAIT_FOREVER = -1
+       DELAY = 0.1
+       timeSpent = 0
+
+       acquired = False
+
+       while timeSpent <= timeout or timeout == WAIT_FOREVER:
+               try:
+                       fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
+                       acquired = True
+                       break
+               except OSError, e:
+                       if e.errno != errno.EEXIST:
+                               raise
+               time.sleep(DELAY)
+               timeSpent += DELAY
+
+       assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout)
+
+       try:
+               yield fd
+       finally:
+               os.unlink(path)
+
+
+def threaded(f):
+       """
+       This decorator calls the method in a new thread, so execution returns straight away
+
+       >>> import misc
+       >>> misc.validate_decorator(threaded)
+       """
+
+       @functools.wraps(f)
+       def wrapper(*args, **kwargs):
+               t = threading.Thread(target=f, args=args, kwargs=kwargs)
+               t.setDaemon(True)
+               t.start()
+       return wrapper
+
+
+def fork(f):
+       """
+       Fork a function into a seperate process and block on it, for forcing reclaiming of resources for highly intensive functions
+       @return The original value through pickling.  If it is unable to be pickled, then the pickling exception is passed through
+       @throws Through pickling, exceptions are passed back and re-raised
+       @note source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511474
+
+       >>> import misc
+       >>> misc.validate_decorator(fork)
+       """
+
+       @functools.wraps(f)
+       def wrapper(*args, **kwds):
+               pread, pwrite = os.pipe()
+               pid = os.fork()
+               if pid > 0:
+                       os.close(pwrite)
+                       with os.fdopen(pread, 'rb') as f:
+                               status, result = cPickle.load(f)
+                       os.waitpid(pid, 0)
+                       if status == 0:
+                               return result
+                       else:
+                               raise result
+               else:
+                       os.close(pread)
+                       try:
+                               result = f(*args, **kwds)
+                               status = 0
+                       except Exception, exc:
+                               result = exc
+                               status = 1
+                       with os.fdopen(pwrite, 'wb') as f:
+                               try:
+                                       cPickle.dump((status, result), f, cPickle.HIGHEST_PROTOCOL)
+                               except cPickle.PicklingError, exc:
+                                       cPickle.dump((2, exc), f, cPickle.HIGHEST_PROTOCOL)
+                       f.close()
+                       sys.exit(0)
+       return wrapper
+
+
+@contextlib.contextmanager
+def qlock(queue, gblock = True, gtimeout = None, pblock = True, ptimeout = None):
+       """
+       Locking with a queue, good for when you want to lock an item passed around
+
+       >>> import Queue
+       >>> item = 5
+       >>> lock = Queue.Queue()
+       >>> lock.put(item)
+       >>> with qlock(lock) as i:
+       ...     print i
+       5
+       """
+       item = queue.get(gblock, gtimeout)
+       yield item
+       queue.put(item, pblock, ptimeout)
+
+
+class EventSource(object):
+       """
+       Asynchronous implementation of the observer pattern
+
+       >>> sourceRoot = EventSource()
+       >>> sourceChild1 = EventSource()
+       >>> sourceChild1.register_provided_events("1-event-0", "1-event-1")
+       >>> sourceChild2 = EventSource()
+       >>> sourceChild2.register_provided_events("1-event-0", "1-event-1")
+       >>> sourceRoot.add_children(sourceChild1, sourceChild2)
+       """
+
+       def __init__(self):
+               """
+               @warning Not thread safe
+               """
+
+               self.__callbackQueues = {}
+               self.__children = []
+
+       def add_children(self, *childrenSources):
+               """
+               @warning Not thread safe
+               """
+
+               self.__children.extend(childrenSources)
+
+       def remove_children(self, *childrenSources):
+               """
+               @warning Not thread safe
+               """
+
+               for child in childrenSources:
+                       self.__children.remove(child)
+
+       def register_provided_events(self, *events):
+               """
+               @warning Not thread safe
+               """
+
+               self.__callbackQueues.update(dict((event, []) for event in events))
+
+       def notify_observers(self, event, message):
+               """
+               @warning As threadsafe as the queue used.  qlock is recommended for the message if it needs locking
+               """
+
+               for queue in self.__callbackQueues[event]:
+                       queue.put(message)
+
+       def _register_queue(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               if event in self.__callbackQueues:
+                       self.__callbackQueues[event].append(queue)
+                       return self
+               else:
+                       for child in self.__children:
+                               source = child._register_queue(event, queue)
+                               if source is not None:
+                                       return source
+                       else:
+                               return None
+
+       def _unregister_queue(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               if event in self.__callbackQueues:
+                       self.__callbackQueues[event].remove(queue)
+                       return self
+               else:
+                       for child in self.__children:
+                               source = child._unregister_queue(event, queue)
+                               if source is not None:
+                                       return source
+                       else:
+                               return None
+
+
+class StrongEventSourceProxy(object):
+
+       def __init__(self, source):
+               """
+               @warning Not thread safe
+               """
+
+               self.source = source
+
+       def register(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               actualSource = self.source._register_queue(event, queue)
+               ActualType = type(self)
+               return ActualType(actualSource)
+
+       def unregister(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               actualSource = self.source._unregister_queue(event, queue)
+               ActualType = type(self)
+               return ActualType(actualSource)
+
+
+class WeakEventSourceProxy(object):
+
+       def __init__(self, source):
+               """
+               @warning Not thread safe
+               """
+
+               self.source = weakref.ref(source)
+
+       def register(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               actualSource = self.source()._register_queue(event, queue)
+               ActualType = type(self)
+               return ActualType(actualSource)
+
+       def unregister(self, event, queue):
+               """
+               @warning Not thread safe
+               """
+
+               actualSource = self.source()._unregister_queue(event, queue)
+               ActualType = type(self)
+               return ActualType(actualSource)
+
+
+class EventObserver(object):
+       """
+
+       >>> import Queue
+       >>> class Observer(EventObserver):
+       ...     def connect_to_source(self, eventSourceRoot):
+       ...             self.queue = Queue.Queue()
+       ...             self.source = eventSourceRoot.register("1-event-0", self.queue)
+       >>>
+       >>> sourceRoot = EventSource()
+       >>> sourceChild1 = EventSource()
+       >>> sourceChild1.register_provided_events("1-event-0", "1-event-1")
+       >>> sourceChild2 = EventSource()
+       >>> sourceChild2.register_provided_events("1-event-0", "1-event-1")
+       >>> sourceRoot.add_children(sourceChild1, sourceChild2)
+       >>>
+       >>> o1 = Observer()
+       >>> o1.connect_to_source(StrongEventSourceProxy(sourceRoot))
+       >>> o2 = Observer()
+       >>> o2.connect_to_source(WeakEventSourceProxy(sourceRoot))
+       >>>
+       >>> sourceChild1.notify_observers("1-event-0", "Hello World")
+       >>> o1.queue.get(False)
+       'Hello World'
+       >>> o2.queue.get(False)
+       'Hello World'
+       """
+
+       def connect_to_source(self, eventSourceRoot):
+               raise NotImplementedError
diff --git a/src/libraries/recipes/coroutines.py b/src/libraries/recipes/coroutines.py
new file mode 100755 (executable)
index 0000000..b1e539e
--- /dev/null
@@ -0,0 +1,623 @@
+#!/usr/bin/env python\r
+\r
+"""\r
+Uses for generators\r
+* Pull pipelining (iterators)\r
+* Push pipelining (coroutines)\r
+* State machines (coroutines)\r
+* "Cooperative multitasking" (coroutines)\r
+* Algorithm -> Object transform for cohesiveness (for example context managers) (coroutines)\r
+\r
+Design considerations\r
+* When should a stage pass on exceptions or have it thrown within it?\r
+* When should a stage pass on GeneratorExits?\r
+* Is there a way to either turn a push generator into a iterator or to use\r
+       comprehensions syntax for push generators (I doubt it)\r
+* When should the stage try and send data in both directions\r
+* Since pull generators (generators), push generators (coroutines), subroutines, and coroutines are all coroutines, maybe we should rename the push generators to not confuse them, like signals/slots? and then refer to two-way generators as coroutines\r
+** If so, make s* and co* implementation of functions\r
+"""\r
+\r
+import threading\r
+import Queue\r
+import pickle\r
+import functools\r
+import itertools\r
+import xml.sax\r
+import xml.parsers.expat\r
+\r
+\r
+def autostart(func):\r
+       """\r
+       >>> @autostart\r
+       ... def grep_sink(pattern):\r
+       ...     print "Looking for %s" % pattern\r
+       ...     while True:\r
+       ...             line = yield\r
+       ...             if pattern in line:\r
+       ...                     print line,\r
+       >>> g = grep_sink("python")\r
+       Looking for python\r
+       >>> g.send("Yeah but no but yeah but no")\r
+       >>> g.send("A series of tubes")\r
+       >>> g.send("python generators rock!")\r
+       python generators rock!\r
+       >>> g.close()\r
+       """\r
+\r
+       @functools.wraps(func)\r
+       def start(*args, **kwargs):\r
+               cr = func(*args, **kwargs)\r
+               cr.next()\r
+               return cr\r
+\r
+       return start\r
+\r
+\r
+@autostart\r
+def printer_sink(format = "%s"):\r
+       """\r
+       >>> pr = printer_sink("%r")\r
+       >>> pr.send("Hello")\r
+       'Hello'\r
+       >>> pr.send("5")\r
+       '5'\r
+       >>> pr.send(5)\r
+       5\r
+       >>> p = printer_sink()\r
+       >>> p.send("Hello")\r
+       Hello\r
+       >>> p.send("World")\r
+       World\r
+       >>> # p.throw(RuntimeError, "Goodbye")\r
+       >>> # p.send("Meh")\r
+       >>> # p.close()\r
+       """\r
+       while True:\r
+               item = yield\r
+               print format % (item, )\r
+\r
+\r
+@autostart\r
+def null_sink():\r
+       """\r
+       Good for uses like with cochain to pick up any slack\r
+       """\r
+       while True:\r
+               item = yield\r
+\r
+\r
+def itr_source(itr, target):\r
+       """\r
+       >>> itr_source(xrange(2), printer_sink())\r
+       0\r
+       1\r
+       """\r
+       for item in itr:\r
+               target.send(item)\r
+\r
+\r
+@autostart\r
+def cofilter(predicate, target):\r
+       """\r
+       >>> p = printer_sink()\r
+       >>> cf = cofilter(None, p)\r
+       >>> cf.send("")\r
+       >>> cf.send("Hello")\r
+       Hello\r
+       >>> cf.send([])\r
+       >>> cf.send([1, 2])\r
+       [1, 2]\r
+       >>> cf.send(False)\r
+       >>> cf.send(True)\r
+       True\r
+       >>> cf.send(0)\r
+       >>> cf.send(1)\r
+       1\r
+       >>> # cf.throw(RuntimeError, "Goodbye")\r
+       >>> # cf.send(False)\r
+       >>> # cf.send(True)\r
+       >>> # cf.close()\r
+       """\r
+       if predicate is None:\r
+               predicate = bool\r
+\r
+       while True:\r
+               try:\r
+                       item = yield\r
+                       if predicate(item):\r
+                               target.send(item)\r
+               except StandardError, e:\r
+                       target.throw(e.__class__, e.message)\r
+\r
+\r
+@autostart\r
+def comap(function, target):\r
+       """\r
+       >>> p = printer_sink()\r
+       >>> cm = comap(lambda x: x+1, p)\r
+       >>> cm.send(0)\r
+       1\r
+       >>> cm.send(1.0)\r
+       2.0\r
+       >>> cm.send(-2)\r
+       -1\r
+       >>> # cm.throw(RuntimeError, "Goodbye")\r
+       >>> # cm.send(0)\r
+       >>> # cm.send(1.0)\r
+       >>> # cm.close()\r
+       """\r
+       while True:\r
+               try:\r
+                       item = yield\r
+                       mappedItem = function(item)\r
+                       target.send(mappedItem)\r
+               except StandardError, e:\r
+                       target.throw(e.__class__, e.message)\r
+\r
+\r
+def func_sink(function):\r
+       return comap(function, null_sink())\r
+\r
+\r
+def expand_positional(function):\r
+\r
+       @functools.wraps(function)\r
+       def expander(item):\r
+               return function(*item)\r
+\r
+       return expander\r
+\r
+\r
+@autostart\r
+def append_sink(l):\r
+       """\r
+       >>> l = []\r
+       >>> apps = append_sink(l)\r
+       >>> apps.send(1)\r
+       >>> apps.send(2)\r
+       >>> apps.send(3)\r
+       >>> print l\r
+       [1, 2, 3]\r
+       """\r
+       while True:\r
+               item = yield\r
+               l.append(item)\r
+\r
+\r
+@autostart\r
+def last_n_sink(l, n = 1):\r
+       """\r
+       >>> l = []\r
+       >>> lns = last_n_sink(l)\r
+       >>> lns.send(1)\r
+       >>> lns.send(2)\r
+       >>> lns.send(3)\r
+       >>> print l\r
+       [3]\r
+       """\r
+       del l[:]\r
+       while True:\r
+               item = yield\r
+               extraCount = len(l) - n + 1\r
+               if 0 < extraCount:\r
+                       del l[0:extraCount]\r
+               l.append(item)\r
+\r
+\r
+@autostart\r
+def coreduce(target, function, initializer = None):\r
+       """\r
+       >>> reduceResult = []\r
+       >>> lns = last_n_sink(reduceResult)\r
+       >>> cr = coreduce(lns, lambda x, y: x + y, 0)\r
+       >>> cr.send(1)\r
+       >>> cr.send(2)\r
+       >>> cr.send(3)\r
+       >>> print reduceResult\r
+       [6]\r
+       >>> cr = coreduce(lns, lambda x, y: x + y)\r
+       >>> cr.send(1)\r
+       >>> cr.send(2)\r
+       >>> cr.send(3)\r
+       >>> print reduceResult\r
+       [6]\r
+       """\r
+       isFirst = True\r
+       cumulativeRef = initializer\r
+       while True:\r
+               item = yield\r
+               if isFirst and initializer is None:\r
+                       cumulativeRef = item\r
+               else:\r
+                       cumulativeRef = function(cumulativeRef, item)\r
+               target.send(cumulativeRef)\r
+               isFirst = False\r
+\r
+\r
+@autostart\r
+def cotee(targets):\r
+       """\r
+       Takes a sequence of coroutines and sends the received items to all of them\r
+\r
+       >>> ct = cotee((printer_sink("1 %s"), printer_sink("2 %s")))\r
+       >>> ct.send("Hello")\r
+       1 Hello\r
+       2 Hello\r
+       >>> ct.send("World")\r
+       1 World\r
+       2 World\r
+       >>> # ct.throw(RuntimeError, "Goodbye")\r
+       >>> # ct.send("Meh")\r
+       >>> # ct.close()\r
+       """\r
+       while True:\r
+               try:\r
+                       item = yield\r
+                       for target in targets:\r
+                               target.send(item)\r
+               except StandardError, e:\r
+                       for target in targets:\r
+                               target.throw(e.__class__, e.message)\r
+\r
+\r
+class CoTee(object):\r
+       """\r
+       >>> ct = CoTee()\r
+       >>> ct.register_sink(printer_sink("1 %s"))\r
+       >>> ct.register_sink(printer_sink("2 %s"))\r
+       >>> ct.stage.send("Hello")\r
+       1 Hello\r
+       2 Hello\r
+       >>> ct.stage.send("World")\r
+       1 World\r
+       2 World\r
+       >>> ct.register_sink(printer_sink("3 %s"))\r
+       >>> ct.stage.send("Foo")\r
+       1 Foo\r
+       2 Foo\r
+       3 Foo\r
+       >>> # ct.stage.throw(RuntimeError, "Goodbye")\r
+       >>> # ct.stage.send("Meh")\r
+       >>> # ct.stage.close()\r
+       """\r
+\r
+       def __init__(self):\r
+               self.stage = self._stage()\r
+               self._targets = []\r
+\r
+       def register_sink(self, sink):\r
+               self._targets.append(sink)\r
+\r
+       def unregister_sink(self, sink):\r
+               self._targets.remove(sink)\r
+\r
+       def restart(self):\r
+               self.stage = self._stage()\r
+\r
+       @autostart\r
+       def _stage(self):\r
+               while True:\r
+                       try:\r
+                               item = yield\r
+                               for target in self._targets:\r
+                                       target.send(item)\r
+                       except StandardError, e:\r
+                               for target in self._targets:\r
+                                       target.throw(e.__class__, e.message)\r
+\r
+\r
+def _flush_queue(queue):\r
+       while not queue.empty():\r
+               yield queue.get()\r
+\r
+\r
+@autostart\r
+def cocount(target, start = 0):\r
+       """\r
+       >>> cc = cocount(printer_sink("%s"))\r
+       >>> cc.send("a")\r
+       0\r
+       >>> cc.send(None)\r
+       1\r
+       >>> cc.send([])\r
+       2\r
+       >>> cc.send(0)\r
+       3\r
+       """\r
+       for i in itertools.count(start):\r
+               item = yield\r
+               target.send(i)\r
+\r
+\r
+@autostart\r
+def coenumerate(target, start = 0):\r
+       """\r
+       >>> ce = coenumerate(printer_sink("%r"))\r
+       >>> ce.send("a")\r
+       (0, 'a')\r
+       >>> ce.send(None)\r
+       (1, None)\r
+       >>> ce.send([])\r
+       (2, [])\r
+       >>> ce.send(0)\r
+       (3, 0)\r
+       """\r
+       for i in itertools.count(start):\r
+               item = yield\r
+               decoratedItem = i, item\r
+               target.send(decoratedItem)\r
+\r
+\r
+@autostart\r
+def corepeat(target, elem):\r
+       """\r
+       >>> cr = corepeat(printer_sink("%s"), "Hello World")\r
+       >>> cr.send("a")\r
+       Hello World\r
+       >>> cr.send(None)\r
+       Hello World\r
+       >>> cr.send([])\r
+       Hello World\r
+       >>> cr.send(0)\r
+       Hello World\r
+       """\r
+       while True:\r
+               item = yield\r
+               target.send(elem)\r
+\r
+\r
+@autostart\r
+def cointercept(target, elems):\r
+       """\r
+       >>> cr = cointercept(printer_sink("%s"), [1, 2, 3, 4])\r
+       >>> cr.send("a")\r
+       1\r
+       >>> cr.send(None)\r
+       2\r
+       >>> cr.send([])\r
+       3\r
+       >>> cr.send(0)\r
+       4\r
+       >>> cr.send("Bye")\r
+       Traceback (most recent call last):\r
+         File "/usr/lib/python2.5/doctest.py", line 1228, in __run\r
+           compileflags, 1) in test.globs\r
+         File "<doctest __main__.cointercept[5]>", line 1, in <module>\r
+           cr.send("Bye")\r
+       StopIteration\r
+       """\r
+       item = yield\r
+       for elem in elems:\r
+               target.send(elem)\r
+               item = yield\r
+\r
+\r
+@autostart\r
+def codropwhile(target, pred):\r
+       """\r
+       >>> cdw = codropwhile(printer_sink("%s"), lambda x: x)\r
+       >>> cdw.send([0, 1, 2])\r
+       >>> cdw.send(1)\r
+       >>> cdw.send(True)\r
+       >>> cdw.send(False)\r
+       >>> cdw.send([0, 1, 2])\r
+       [0, 1, 2]\r
+       >>> cdw.send(1)\r
+       1\r
+       >>> cdw.send(True)\r
+       True\r
+       """\r
+       while True:\r
+               item = yield\r
+               if not pred(item):\r
+                       break\r
+\r
+       while True:\r
+               item = yield\r
+               target.send(item)\r
+\r
+\r
+@autostart\r
+def cotakewhile(target, pred):\r
+       """\r
+       >>> ctw = cotakewhile(printer_sink("%s"), lambda x: x)\r
+       >>> ctw.send([0, 1, 2])\r
+       [0, 1, 2]\r
+       >>> ctw.send(1)\r
+       1\r
+       >>> ctw.send(True)\r
+       True\r
+       >>> ctw.send(False)\r
+       >>> ctw.send([0, 1, 2])\r
+       >>> ctw.send(1)\r
+       >>> ctw.send(True)\r
+       """\r
+       while True:\r
+               item = yield\r
+               if not pred(item):\r
+                       break\r
+               target.send(item)\r
+\r
+       while True:\r
+               item = yield\r
+\r
+\r
+@autostart\r
+def coslice(target, lower, upper):\r
+       """\r
+       >>> cs = coslice(printer_sink("%r"), 3, 5)\r
+       >>> cs.send("0")\r
+       >>> cs.send("1")\r
+       >>> cs.send("2")\r
+       >>> cs.send("3")\r
+       '3'\r
+       >>> cs.send("4")\r
+       '4'\r
+       >>> cs.send("5")\r
+       >>> cs.send("6")\r
+       """\r
+       for i in xrange(lower):\r
+               item = yield\r
+       for i in xrange(upper - lower):\r
+               item = yield\r
+               target.send(item)\r
+       while True:\r
+               item = yield\r
+\r
+\r
+@autostart\r
+def cochain(targets):\r
+       """\r
+       >>> cr = cointercept(printer_sink("good %s"), [1, 2, 3, 4])\r
+       >>> cc = cochain([cr, printer_sink("end %s")])\r
+       >>> cc.send("a")\r
+       good 1\r
+       >>> cc.send(None)\r
+       good 2\r
+       >>> cc.send([])\r
+       good 3\r
+       >>> cc.send(0)\r
+       good 4\r
+       >>> cc.send("Bye")\r
+       end Bye\r
+       """\r
+       behind = []\r
+       for target in targets:\r
+               try:\r
+                       while behind:\r
+                               item = behind.pop()\r
+                               target.send(item)\r
+                       while True:\r
+                               item = yield\r
+                               target.send(item)\r
+               except StopIteration:\r
+                       behind.append(item)\r
+\r
+\r
+@autostart\r
+def queue_sink(queue):\r
+       """\r
+       >>> q = Queue.Queue()\r
+       >>> qs = queue_sink(q)\r
+       >>> qs.send("Hello")\r
+       >>> qs.send("World")\r
+       >>> qs.throw(RuntimeError, "Goodbye")\r
+       >>> qs.send("Meh")\r
+       >>> qs.close()\r
+       >>> print [i for i in _flush_queue(q)]\r
+       [(None, 'Hello'), (None, 'World'), (<type 'exceptions.RuntimeError'>, 'Goodbye'), (None, 'Meh'), (<type 'exceptions.GeneratorExit'>, None)]\r
+       """\r
+       while True:\r
+               try:\r
+                       item = yield\r
+                       queue.put((None, item))\r
+               except StandardError, e:\r
+                       queue.put((e.__class__, e.message))\r
+               except GeneratorExit:\r
+                       queue.put((GeneratorExit, None))\r
+                       raise\r
+\r
+\r
+def decode_item(item, target):\r
+       if item[0] is None:\r
+               target.send(item[1])\r
+               return False\r
+       elif item[0] is GeneratorExit:\r
+               target.close()\r
+               return True\r
+       else:\r
+               target.throw(item[0], item[1])\r
+               return False\r
+\r
+\r
+def queue_source(queue, target):\r
+       """\r
+       >>> q = Queue.Queue()\r
+       >>> for i in [\r
+       ...     (None, 'Hello'),\r
+       ...     (None, 'World'),\r
+       ...     (GeneratorExit, None),\r
+       ...     ]:\r
+       ...     q.put(i)\r
+       >>> qs = queue_source(q, printer_sink())\r
+       Hello\r
+       World\r
+       """\r
+       isDone = False\r
+       while not isDone:\r
+               item = queue.get()\r
+               isDone = decode_item(item, target)\r
+\r
+\r
+def threaded_stage(target, thread_factory = threading.Thread):\r
+       messages = Queue.Queue()\r
+\r
+       run_source = functools.partial(queue_source, messages, target)\r
+       thread_factory(target=run_source).start()\r
+\r
+       # Sink running in current thread\r
+       return functools.partial(queue_sink, messages)\r
+\r
+\r
+@autostart\r
+def pickle_sink(f):\r
+       while True:\r
+               try:\r
+                       item = yield\r
+                       pickle.dump((None, item), f)\r
+               except StandardError, e:\r
+                       pickle.dump((e.__class__, e.message), f)\r
+               except GeneratorExit:\r
+                       pickle.dump((GeneratorExit, ), f)\r
+                       raise\r
+               except StopIteration:\r
+                       f.close()\r
+                       return\r
+\r
+\r
+def pickle_source(f, target):\r
+       try:\r
+               isDone = False\r
+               while not isDone:\r
+                       item = pickle.load(f)\r
+                       isDone = decode_item(item, target)\r
+       except EOFError:\r
+               target.close()\r
+\r
+\r
+class EventHandler(object, xml.sax.ContentHandler):\r
+\r
+       START = "start"\r
+       TEXT = "text"\r
+       END = "end"\r
+\r
+       def __init__(self, target):\r
+               object.__init__(self)\r
+               xml.sax.ContentHandler.__init__(self)\r
+               self._target = target\r
+\r
+       def startElement(self, name, attrs):\r
+               self._target.send((self.START, (name, attrs._attrs)))\r
+\r
+       def characters(self, text):\r
+               self._target.send((self.TEXT, text))\r
+\r
+       def endElement(self, name):\r
+               self._target.send((self.END, name))\r
+\r
+\r
+def expat_parse(f, target):\r
+       parser = xml.parsers.expat.ParserCreate()\r
+       parser.buffer_size = 65536\r
+       parser.buffer_text = True\r
+       parser.returns_unicode = False\r
+       parser.StartElementHandler = lambda name, attrs: target.send(('start', (name, attrs)))\r
+       parser.EndElementHandler = lambda name: target.send(('end', name))\r
+       parser.CharacterDataHandler = lambda data: target.send(('text', data))\r
+       parser.ParseFile(f)\r
+\r
+\r
+if __name__ == "__main__":\r
+       import doctest\r
+       doctest.testmod()\r
diff --git a/src/libraries/recipes/datatypes.py b/src/libraries/recipes/datatypes.py
new file mode 100644 (file)
index 0000000..2993cb3
--- /dev/null
@@ -0,0 +1,386 @@
+#!/usr/bin/env python
+
+
+"""
+This module provides three types of queues, with these constructors:
+       Stack([items])  -- Create a Last In First Out queue, implemented as a list
+       Queue([items])  -- Create a First In First Out queue
+       PriorityQueue([items]) -- Create a queue where minimum item (by <) is first
+Here [items] is an optional list of initial items; if omitted, queue is empty.
+Each type supports the following methods and functions:
+       len(q)          -- number of items in q (also q.__len__())
+       q.append(item)-- add an item to the queue
+       q.extend(items) -- add each of the items to the queue
+       q.pop()          -- remove and return the "first" item from the queue
+"""
+
+
+import types
+import operator
+
+
+def Stack(items=None):
+       "A stack, or last-in-first-out queue, is implemented as a list."
+       return items or []
+
+
+class Queue(object):
+       "A first-in-first-out queue."
+
+       def __init__(self, initialItems=None):
+               self.start = 0
+               self.items = initialItems or []
+
+       def __len__(self):
+               return len(self.items) - self.start
+
+       def append(self, item):
+               self.items.append(item)
+
+       def extend(self, items):
+               self.items.extend(items)
+
+       def pop(self):
+               items = self.items
+               item = items[self.start]
+               self.start += 1
+               if self.start > 100 and self.start > len(items)/2:
+                       del items[:self.start]
+                       self.start = 0
+               return item
+
+
+class PriorityQueue(object):
+       "A queue in which the minimum element (as determined by cmp) is first."
+
+       def __init__(self, initialItems=None, comparator=operator.lt):
+               self.items = []
+               self.cmp = comparator
+               if initialItems is not None:
+                       self.extend(initialItems)
+
+       def __len__(self):
+               return len(self.items)
+
+       def append(self, item):
+               items, cmp_func = self.items, self.cmp
+               items.append(item)
+               i = len(items) - 1
+               while i > 0 and cmp_func(item, items[i//2]):
+                       items[i], i = items[i//2], i//2
+               items[i] = item
+
+       def extend(self, items):
+               for item in items:
+                       self.append(item)
+
+       def pop(self):
+               items = self.items
+               if len(items) == 1:
+                       return items.pop()
+               e = items[0]
+               items[0] = items.pop()
+               self.heapify(0)
+               return e
+
+       def heapify(self, i):
+               """
+               itemsssumes items is an array whose left and right children are heaps,
+               move items[i] into the correct position.See CLR&S p. 130
+               """
+               items, cmp_func = self.items, self.cmp
+               left, right, N = 2*i + 1, 2*i + 2, len(items)-1
+               if left <= N and cmp_func(items[left], items[i]):
+                       smallest = left
+               else:
+                       smallest = i
+
+               if right <= N and cmp_func(items[right], items[smallest]):
+                       smallest = right
+               if smallest != i:
+                       items[i], items[smallest] = items[smallest], items[i]
+                       self.heapify(smallest)
+
+
+class AttrDict(object):
+       """
+       Can act as a mixin to add dictionary access to members to ease dynamic attribute access
+       or as a wrapper around a class
+
+       >>> class Mixin (AttrDict):
+       ...     def __init__ (self):
+       ...             AttrDict.__init__ (self)
+       ...             self.x = 5
+       ...
+       >>> mixinExample = Mixin ()
+       >>> mixinExample.x
+       5
+       >>> mixinExample["x"]
+       5
+       >>> mixinExample["x"] = 10; mixinExample.x
+       10
+       >>> "x" in mixinExample
+       True
+       >>> class Wrapper (object):
+       ...     def __init__ (self):
+       ...             self.y = 10
+       ...
+       >>> wrapper = Wrapper()
+       >>> wrapper.y
+       10
+       >>> wrapperExample = AttrDict (wrapper)
+       >>> wrapperExample["y"]
+       10
+       >>> wrapperExample["y"] = 20; wrapper.y
+       20
+       >>> "y" in wrapperExample
+       True
+       """
+
+       def __init__(self, obj = None):
+               self.__obj = obj if obj is not None else self
+
+       def __getitem__(self, name):
+               return getattr(self.__obj, name)
+
+       def __setitem__(self, name, value):
+               setattr(self.__obj, name, value)
+
+       def __delitem__(self, name):
+               delattr(self.__obj, name)
+
+       def __contains__(self, name):
+               return hasattr(self.__obj, name)
+
+
+class Uncertain(object):
+       """
+       Represents a numeric value with a known small uncertainty
+       (error, standard deviation...).
+       Numeric operators are overloaded to work with other Uncertain or
+       numeric objects.
+       The uncertainty (error) must be small. Otherwise the linearization
+       employed here becomes wrong.
+
+       >>> pie = Uncertain(3.14, 0.01)
+       >>> ee = Uncertain(2.718, 0.001)
+       >>> pie, repr(pie)
+       (Uncertain(3.14, 0.01), 'Uncertain(3.14, 0.01)')
+       >>> ee, repr(ee)
+       (Uncertain(2.718, 0.001), 'Uncertain(2.718, 0.001)')
+       >>> pie + ee
+       Uncertain(5.858, 0.0100498756211)
+       >>> pie * ee
+       Uncertain(8.53452, 0.0273607748428)
+       """
+
+       def __init__(self, value=0., error=0., *a, **t):
+               self.value = value
+               self.error = abs(error)
+               super(Uncertain, self).__init__(*a, **t)
+
+       # Conversions
+
+       def __str__(self):
+               return "%g+-%g" % (self.value, self.error)
+
+       def __repr__(self):
+               return "Uncertain(%s, %s)" % (self.value, self.error)
+
+       def __complex__(self):
+               return complex(self.value)
+
+       def __int__(self):
+               return int(self.value)
+
+       def __long__(self):
+               return long(self.value)
+
+       def __float__(self):
+               return self.value
+
+       # Comparison
+
+       def __eq__(self, other):
+               epsilon = max(self.error, other.error)
+               return abs(other.value - self.value) < epsilon
+
+       def __ne__(self, other):
+               return not (self == other)
+
+       def __hash__(self):
+               return hash(self.value) ^ hash(self.error)
+
+       def __le__(self, other):
+               return self.value < other.value or self == other
+
+       def __lt__(self, other):
+               return self.value < other.value and self != other
+
+       def __gt__(self, other):
+               return not (self <= other)
+
+       def __ge__(self, other):
+               return not (self < other)
+
+       def __nonzero__(self):
+               return self.error < abs(self.value)
+
+       # Math
+
+       def assign(self, other):
+               if isinstance(other, Uncertain):
+                       self.value = other.value
+                       self.error = other.error
+               else:
+                       self.value = other
+                       self.error = 0.
+
+       def __add__(self, other):
+               if isinstance(other, Uncertain):
+                       v = self.value + other.value
+                       e = (self.error**2 + other.error**2) ** .5
+                       return Uncertain(v, e)
+               else:
+                       return Uncertain(self.value+other, self.error)
+
+       def __sub__(self, other):
+               return self + (-other)
+
+       def __mul__(self, other):
+               if isinstance(other, Uncertain):
+                       v = self.value * other.value
+                       e = ((self.error * other.value)**2 + (other.error * self.value)**2) ** .5
+                       return Uncertain(v, e)
+               else:
+                       return Uncertain(self.value*other,
+                                       self.error*other)
+
+       def __div__(self, other):
+               return self*(1./other)
+
+       def __truediv__(self, other):
+               return self*(1./other)
+
+       def __radd__(self, other):
+               return self + other
+
+       def __rsub__(self, other):
+               return -self + other
+
+       def __rmul__(self, other):
+               return self * other
+
+       def __rdiv__(self, other):
+               return (self/other)**-1.
+
+       def __rtruediv__(self, other):
+               return (self/other)**-1.
+
+       def __neg__(self):
+               return self*-1
+
+       def __pos__(self):
+               return self
+
+       def __abs__(self):
+               return Uncertain(abs(self.value), self.error)
+
+
+class Enumeration(object):
+       """
+       C-Style enumeration mapping attributes to numbers
+
+       >>> Color = Enumeration("Color", ["Red", "Green", "Blue"])
+       >>> Color.Red, Color.Green, Color.Blue
+       (0, 1, 2)
+       >>>
+       >>> Color["Red"], Color.whatis(0)
+       (0, 'Red')
+       >>> Color.names(), Color.values()
+       (['Blue', 'Green', 'Red'], [2, 1, 0])
+       >>>
+       >>> str(Color)
+       "Color: {'Blue': 2, 'Green': 1, 'Red': 0}"
+       >>>
+       >>> 0 in Color, 10 in Color
+       (True, False)
+       >>> "Red" in Color, "Black" in Color
+       (True, False)
+       """
+
+       def __init__(self, name, enumList):
+               self.__name__ = name
+               self.__doc__ = name
+               lookup = { }
+               reverseLookup = { }
+
+               i = 0
+               uniqueNames = [ ]
+               uniqueValues = [ ]
+               for x in enumList:
+                       if type(x) == types.TupleType:
+                               x, i = x
+                       if type(x) != types.StringType:
+                               raise TypeError("enum name is not a string: " + x)
+                       if type(i) != types.IntType:
+                               raise TypeError("enum value is not an integer: " + str(i))
+                       if x in uniqueNames:
+                               raise ValueError("enum name is not unique: " + x)
+                       if i in uniqueValues:
+                               raise ValueError("enum value is not unique for " + x)
+                       uniqueNames.append(x)
+                       uniqueValues.append(i)
+                       lookup[x] = i
+                       reverseLookup[i] = x
+                       i = i + 1
+
+               self.__lookup = lookup
+               self.__reverseLookup = reverseLookup
+
+       def whatis(self, value):
+               return self.__reverseLookup[value]
+
+       def names(self):
+               return self.__lookup.keys()
+
+       def values(self):
+               return self.__lookup.values()
+
+       def __getattr__(self, attr):
+               if attr not in self.__lookup:
+                       raise (AttributeError)
+               return self.__lookup[attr]
+
+       def __str__(self):
+               return str(self.__doc__)+": "+str(self.__lookup)
+
+       def __len__(self):
+               return len(self.__lookup)
+
+       def __contains__(self, x):
+               return (x in self.__lookup) or (x in self.__reverseLookup)
+
+       def __getitem__(self, attr):
+               return self.__lookup[attr]
+
+       def __iter__(self):
+               return self.__lookup.itervalues()
+
+       def iterkeys(self):
+               return self.__lookup.iterkeys()
+
+       def itervalues(self):
+               return self.__lookup.itervalues()
+
+       def iteritems(self):
+               return self.__lookup.iteritems()
+
+
+def make_enum(cls):
+       """
+       @todo Make more object orientated (inheritance?)
+       """
+       name = cls.__name__
+       values = cls.__values__
+       return Enumeration(name, values)
diff --git a/src/libraries/recipes/gtk_utils.py b/src/libraries/recipes/gtk_utils.py
new file mode 100644 (file)
index 0000000..01f86db
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+import threading
+import contextlib
+import functools
+
+import gobject
+import gtk
+import gtk.glade
+
+
+def make_idler(func):
+       """
+       Decorator that makes a generator-function into a function that will continue execution on next call
+
+       >>> import misc
+       >>> misc.validate_decorator(make_idler)
+
+       """
+       a = []
+
+       @functools.wraps(func)
+       def decorated_func(*args, **kwds):
+               if not a:
+                       a.append(func(*args, **kwds))
+               try:
+                       shouldBeNone = a[0].next()
+                       assert shouldBeNone is None, "The idle only task yield a value, %r" % shouldBeNone
+                       return True
+               except StopIteration:
+                       del a[:]
+                       return False
+
+       return decorated_func
+
+
+@contextlib.contextmanager
+def gtk_critical_section():
+       #The API changed and I hope these are the right calls
+       gtk.gdk.threads_enter()
+       try:
+               yield
+       finally:
+               gtk.gdk.threads_leave()
+
+
+if __name__ == "__main__":
+       #gtk.gdk.threads_init()
+       pass
diff --git a/src/libraries/recipes/io.py b/src/libraries/recipes/io.py
new file mode 100644 (file)
index 0000000..aece2dd
--- /dev/null
@@ -0,0 +1,129 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+import os
+import pickle
+import contextlib
+import itertools
+import functools
+
+
+@contextlib.contextmanager
+def change_directory(directory):
+       previousDirectory = os.getcwd()
+       os.chdir(directory)
+       currentDirectory = os.getcwd()
+
+       try:
+               yield previousDirectory, currentDirectory
+       finally:
+               os.chdir(previousDirectory)
+
+
+@contextlib.contextmanager
+def pickled(filename):
+       """
+       Here is an example usage:
+       with pickled("foo.db") as p:
+               p("users", list).append(["srid", "passwd", 23])
+       """
+
+       if os.path.isfile(filename):
+               data = pickle.load(open(filename))
+       else:
+               data = {}
+
+       def getter(item, factory):
+               if item in data:
+                       return data[item]
+               else:
+                       data[item] = factory()
+                       return data[item]
+
+       yield getter
+
+       pickle.dump(data, open(filename, "w"))
+
+
+@contextlib.contextmanager
+def redirect(object_, attr, value):
+       """
+       >>> import sys
+       ... with redirect(sys, 'stdout', open('stdout', 'w')):
+       ...     print "hello"
+       ...
+       >>> print "we're back"
+       we're back
+       """
+       orig = getattr(object_, attr)
+       setattr(object_, attr, value)
+       try:
+               yield
+       finally:
+               setattr(object_, attr, orig)
+
+
+def pathsplit(path):
+       """
+       >>> pathsplit("/a/b/c")
+       ['', 'a', 'b', 'c']
+       >>> pathsplit("./plugins/builtins.ini")
+       ['.', 'plugins', 'builtins.ini']
+       """
+       pathParts = path.split(os.path.sep)
+       return pathParts
+
+
+def commonpath(l1, l2, common=None):
+       """
+       >>> commonpath(pathsplit('/a/b/c/d'), pathsplit('/a/b/c1/d1'))
+       (['', 'a', 'b'], ['c', 'd'], ['c1', 'd1'])
+       >>> commonpath(pathsplit("./plugins/"), pathsplit("./plugins/builtins.ini"))
+       (['.', 'plugins'], [''], ['builtins.ini'])
+       >>> commonpath(pathsplit("./plugins/builtins"), pathsplit("./plugins"))
+       (['.', 'plugins'], ['builtins'], [])
+       """
+       if common is None:
+               common = []
+
+       if l1 == l2:
+               return l1, [], []
+
+       for i, (leftDir, rightDir) in enumerate(zip(l1, l2)):
+               if leftDir != rightDir:
+                       return l1[0:i], l1[i:], l2[i:]
+       else:
+               if leftDir == rightDir:
+                       i += 1
+               return l1[0:i], l1[i:], l2[i:]
+
+
+def relpath(p1, p2):
+       """
+       >>> relpath('/', '/')
+       './'
+       >>> relpath('/a/b/c/d', '/')
+       '../../../../'
+       >>> relpath('/a/b/c/d', '/a/b/c1/d1')
+       '../../c1/d1'
+       >>> relpath('/a/b/c/d', '/a/b/c1/d1/')
+       '../../c1/d1'
+       >>> relpath("./plugins/builtins", "./plugins")
+       '../'
+       >>> relpath("./plugins/", "./plugins/builtins.ini")
+       'builtins.ini'
+       """
+       sourcePath = os.path.normpath(p1)
+       destPath = os.path.normpath(p2)
+
+       (common, sourceOnly, destOnly) = commonpath(pathsplit(sourcePath), pathsplit(destPath))
+       if len(sourceOnly) or len(destOnly):
+               relParts = itertools.chain(
+                       (('..' + os.sep) * len(sourceOnly), ),
+                       destOnly,
+               )
+               return os.path.join(*relParts)
+       else:
+               return "."+os.sep
diff --git a/src/libraries/recipes/misc.py b/src/libraries/recipes/misc.py
new file mode 100644 (file)
index 0000000..85c6ca0
--- /dev/null
@@ -0,0 +1,625 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import sys
+import cPickle
+
+import functools
+import contextlib
+import inspect
+
+import optparse
+import traceback
+import warnings
+import string
+
+
+def printfmt(template):
+       """
+       This hides having to create the Template object and call substitute/safe_substitute on it. For example:
+
+       >>> num = 10
+       >>> word = "spam"
+       >>> printfmt("I would like to order $num units of $word, please") #doctest: +SKIP
+       I would like to order 10 units of spam, please
+       """
+       frame = inspect.stack()[-1][0]
+       try:
+               print string.Template(template).safe_substitute(frame.f_locals)
+       finally:
+               del frame
+
+
+def is_special(name):
+       return name.startswith("__") and name.endswith("__")
+
+
+def is_private(name):
+       return name.startswith("_") and not is_special(name)
+
+
+def privatize(clsName, attributeName):
+       """
+       At runtime, make an attributeName private
+
+       Example:
+       >>> class Test(object):
+       ...     pass
+       ...
+       >>> try:
+       ...     dir(Test).index("_Test__me")
+       ...     print dir(Test)
+       ... except:
+       ...     print "Not Found"
+       Not Found
+       >>> setattr(Test, privatize(Test.__name__, "me"), "Hello World")
+       >>> try:
+       ...     dir(Test).index("_Test__me")
+       ...     print "Found"
+       ... except:
+       ...     print dir(Test)
+       0
+       Found
+       >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
+       Hello World
+       >>>
+       >>> is_private(privatize(Test.__name__, "me"))
+       True
+       >>> is_special(privatize(Test.__name__, "me"))
+       False
+       """
+       return "".join(["_", clsName, "__", attributeName])
+
+
+def obfuscate(clsName, attributeName):
+       """
+       At runtime, turn a private name into the obfuscated form
+
+       Example:
+       >>> class Test(object):
+       ...     __me = "Hello World"
+       ...
+       >>> try:
+       ...     dir(Test).index("_Test__me")
+       ...     print "Found"
+       ... except:
+       ...     print dir(Test)
+       0
+       Found
+       >>> print getattr(Test, obfuscate(Test.__name__, "__me"))
+       Hello World
+       >>> is_private(obfuscate(Test.__name__, "__me"))
+       True
+       >>> is_special(obfuscate(Test.__name__, "__me"))
+       False
+       """
+       return "".join(["_", clsName, attributeName])
+
+
+class PAOptionParser(optparse.OptionParser, object):
+       """
+       >>> if __name__ == '__main__':
+       ...     #parser = PAOptionParser("My usage str")
+       ...     parser = PAOptionParser()
+       ...     parser.add_posarg("Foo", help="Foo usage")
+       ...     parser.add_posarg("Bar", dest="bar_dest")
+       ...     parser.add_posarg("Language", dest='tr_type', type="choice", choices=("Python", "Other"))
+       ...     parser.add_option('--stocksym', dest='symbol')
+       ...     values, args = parser.parse_args()
+       ...     print values, args
+       ...
+
+       python mycp.py  -h
+       python mycp.py
+       python mycp.py  foo
+       python mycp.py  foo bar
+
+       python mycp.py foo bar lava
+       Usage: pa.py <Foo> <Bar> <Language> [options]
+
+       Positional Arguments:
+       Foo: Foo usage
+       Bar:
+       Language:
+
+       pa.py: error: option --Language: invalid choice: 'lava' (choose from 'Python', 'Other'
+       """
+
+       def __init__(self, *args, **kw):
+               self.posargs = []
+               super(PAOptionParser, self).__init__(*args, **kw)
+
+       def add_posarg(self, *args, **kw):
+               pa_help = kw.get("help", "")
+               kw["help"] = optparse.SUPPRESS_HELP
+               o = self.add_option("--%s" % args[0], *args[1:], **kw)
+               self.posargs.append((args[0], pa_help))
+
+       def get_usage(self, *args, **kwargs):
+               params = (' '.join(["<%s>" % arg[0] for arg in self.posargs]), '\n '.join(["%s: %s" % (arg) for arg in self.posargs]))
+               self.usage = "%%prog %s [options]\n\nPositional Arguments:\n %s" % params
+               return super(PAOptionParser, self).get_usage(*args, **kwargs)
+
+       def parse_args(self, *args, **kwargs):
+               args = sys.argv[1:]
+               args0 = []
+               for p, v in zip(self.posargs, args):
+                       args0.append("--%s" % p[0])
+                       args0.append(v)
+               args = args0 + args
+               options, args = super(PAOptionParser, self).parse_args(args, **kwargs)
+               if len(args) < len(self.posargs):
+                       msg = 'Missing value(s) for "%s"\n' % ", ".join([arg[0] for arg in self.posargs][len(args):])
+                       self.error(msg)
+               return options, args
+
+
+def explicitly(name, stackadd=0):
+       """
+       This is an alias for adding to '__all__'.  Less error-prone than using
+       __all__ itself, since setting __all__ directly is prone to stomping on
+       things implicitly exported via L{alias}.
+
+       @note Taken from PyExport (which could turn out pretty cool):
+       @li @a http://codebrowse.launchpad.net/~glyph/
+       @li @a http://glyf.livejournal.com/74356.html
+       """
+       packageVars = sys._getframe(1+stackadd).f_locals
+       globalAll = packageVars.setdefault('__all__', [])
+       globalAll.append(name)
+
+
+def public(thunk):
+       """
+       This is a decorator, for convenience.  Rather than typing the name of your
+       function twice, you can decorate a function with this.
+
+       To be real, @public would need to work on methods as well, which gets into
+       supporting types...
+
+       @note Taken from PyExport (which could turn out pretty cool):
+       @li @a http://codebrowse.launchpad.net/~glyph/
+       @li @a http://glyf.livejournal.com/74356.html
+       """
+       explicitly(thunk.__name__, 1)
+       return thunk
+
+
+def _append_docstring(obj, message):
+       if obj.__doc__ is None:
+               obj.__doc__ = message
+       else:
+               obj.__doc__ += message
+
+
+def validate_decorator(decorator):
+
+       def simple(x):
+               return x
+
+       f = simple
+       f.__name__ = "name"
+       f.__doc__ = "doc"
+       f.__dict__["member"] = True
+
+       g = decorator(f)
+
+       if f.__name__ != g.__name__:
+               print f.__name__, "!=", g.__name__
+
+       if g.__doc__ is None:
+               print decorator.__name__, "has no doc string"
+       elif not g.__doc__.startswith(f.__doc__):
+               print g.__doc__, "didn't start with", f.__doc__
+
+       if not ("member" in g.__dict__ and g.__dict__["member"]):
+               print "'member' not in ", g.__dict__
+
+
+def deprecated_api(func):
+       """
+       This is a decorator which can be used to mark functions
+       as deprecated. It will result in a warning being emitted
+       when the function is used.
+
+       >>> validate_decorator(deprecated_api)
+       """
+
+       @functools.wraps(func)
+       def newFunc(*args, **kwargs):
+               warnings.warn("Call to deprecated function %s." % func.__name__, category=DeprecationWarning)
+               return func(*args, **kwargs)
+       _append_docstring(newFunc, "\n@deprecated")
+       return newFunc
+
+
+def unstable_api(func):
+       """
+       This is a decorator which can be used to mark functions
+       as deprecated. It will result in a warning being emitted
+       when the function is used.
+
+       >>> validate_decorator(unstable_api)
+       """
+
+       @functools.wraps(func)
+       def newFunc(*args, **kwargs):
+               warnings.warn("Call to unstable API function %s." % func.__name__, category=FutureWarning)
+               return func(*args, **kwargs)
+       _append_docstring(newFunc, "\n@unstable")
+       return newFunc
+
+
+def enabled(func):
+       """
+       This decorator doesn't add any behavior
+
+       >>> validate_decorator(enabled)
+       """
+       return func
+
+
+def disabled(func):
+       """
+       This decorator disables the provided function, and does nothing
+
+       >>> validate_decorator(disabled)
+       """
+
+       @functools.wraps(func)
+       def emptyFunc(*args, **kargs):
+               pass
+       _append_docstring(emptyFunc, "\n@note Temporarily Disabled")
+       return emptyFunc
+
+
+def metadata(document=True, **kwds):
+       """
+       >>> validate_decorator(metadata(author="Ed"))
+       """
+
+       def decorate(func):
+               for k, v in kwds.iteritems():
+                       setattr(func, k, v)
+                       if document:
+                               _append_docstring(func, "\n@"+k+" "+v)
+               return func
+       return decorate
+
+
+def prop(func):
+       """Function decorator for defining property attributes
+
+       The decorated function is expected to return a dictionary
+       containing one or more of the following pairs:
+               fget - function for getting attribute value
+               fset - function for setting attribute value
+               fdel - function for deleting attribute
+       This can be conveniently constructed by the locals() builtin
+       function; see:
+       http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/205183
+       @author http://kbyanc.blogspot.com/2007/06/python-property-attribute-tricks.html
+
+       Example:
+       >>> #Due to transformation from function to property, does not need to be validated
+       >>> #validate_decorator(prop)
+       >>> class MyExampleClass(object):
+       ...     @prop
+       ...     def foo():
+       ...             "The foo property attribute's doc-string"
+       ...             def fget(self):
+       ...                     print "GET"
+       ...                     return self._foo
+       ...             def fset(self, value):
+       ...                     print "SET"
+       ...                     self._foo = value
+       ...             return locals()
+       ...
+       >>> me = MyExampleClass()
+       >>> me.foo = 10
+       SET
+       >>> print me.foo
+       GET
+       10
+       """
+       return property(doc=func.__doc__, **func())
+
+
+def print_handler(e):
+       """
+       @see ExpHandler
+       """
+       print "%s: %s" % (type(e).__name__, e)
+
+
+def print_ignore(e):
+       """
+       @see ExpHandler
+       """
+       print 'Ignoring %s exception: %s' % (type(e).__name__, e)
+
+
+def print_traceback(e):
+       """
+       @see ExpHandler
+       """
+       #print sys.exc_info()
+       traceback.print_exc(file=sys.stdout)
+
+
+def ExpHandler(handler = print_handler, *exceptions):
+       """
+       An exception handling idiom using decorators
+       Examples
+       Specify exceptions in order, first one is handled first
+       last one last.
+
+       >>> validate_decorator(ExpHandler())
+       >>> @ExpHandler(print_ignore, ZeroDivisionError)
+       ... @ExpHandler(None, AttributeError, ValueError)
+       ... def f1():
+       ...     1/0
+       >>> @ExpHandler(print_traceback, ZeroDivisionError)
+       ... def f2():
+       ...     1/0
+       >>> @ExpHandler()
+       ... def f3(*pargs):
+       ...     l = pargs
+       ...     return l[10]
+       >>> @ExpHandler(print_traceback, ZeroDivisionError)
+       ... def f4():
+       ...     return 1
+       >>>
+       >>>
+       >>> f1()
+       Ignoring ZeroDivisionError exception: integer division or modulo by zero
+       >>> f2() # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
+       Traceback (most recent call last):
+       ...
+       ZeroDivisionError: integer division or modulo by zero
+       >>> f3()
+       IndexError: tuple index out of range
+       >>> f4()
+       1
+       """
+
+       def wrapper(f):
+               localExceptions = exceptions
+               if not localExceptions:
+                       localExceptions = [Exception]
+               t = [(ex, handler) for ex in localExceptions]
+               t.reverse()
+
+               def newfunc(t, *args, **kwargs):
+                       ex, handler = t[0]
+                       try:
+                               if len(t) == 1:
+                                       return f(*args, **kwargs)
+                               else:
+                                       #Recurse for embedded try/excepts
+                                       dec_func = functools.partial(newfunc, t[1:])
+                                       dec_func = functools.update_wrapper(dec_func, f)
+                                       return dec_func(*args, **kwargs)
+                       except ex, e:
+                               return handler(e)
+
+               dec_func = functools.partial(newfunc, t)
+               dec_func = functools.update_wrapper(dec_func, f)
+               return dec_func
+       return wrapper
+
+
+class bindclass(object):
+       """
+       >>> validate_decorator(bindclass)
+       >>> class Foo(BoundObject):
+       ...      @bindclass
+       ...      def foo(this_class, self):
+       ...              return this_class, self
+       ...
+       >>> class Bar(Foo):
+       ...      @bindclass
+       ...      def bar(this_class, self):
+       ...              return this_class, self
+       ...
+       >>> f = Foo()
+       >>> b = Bar()
+       >>>
+       >>> f.foo() # doctest: +ELLIPSIS
+       (<class '...Foo'>, <...Foo object at ...>)
+       >>> b.foo() # doctest: +ELLIPSIS
+       (<class '...Foo'>, <...Bar object at ...>)
+       >>> b.bar() # doctest: +ELLIPSIS
+       (<class '...Bar'>, <...Bar object at ...>)
+       """
+
+       def __init__(self, f):
+               self.f = f
+               self.__name__ = f.__name__
+               self.__doc__ = f.__doc__
+               self.__dict__.update(f.__dict__)
+               self.m = None
+
+       def bind(self, cls, attr):
+
+               def bound_m(*args, **kwargs):
+                       return self.f(cls, *args, **kwargs)
+               bound_m.__name__ = attr
+               self.m = bound_m
+
+       def __get__(self, obj, objtype=None):
+               return self.m.__get__(obj, objtype)
+
+
+class ClassBindingSupport(type):
+       "@see bindclass"
+
+       def __init__(mcs, name, bases, attrs):
+               type.__init__(mcs, name, bases, attrs)
+               for attr, val in attrs.iteritems():
+                       if isinstance(val, bindclass):
+                               val.bind(mcs, attr)
+
+
+class BoundObject(object):
+       "@see bindclass"
+       __metaclass__ = ClassBindingSupport
+
+
+def bindfunction(f):
+       """
+       >>> validate_decorator(bindfunction)
+       >>> @bindfunction
+       ... def factorial(thisfunction, n):
+       ...      # Within this function the name 'thisfunction' refers to the factorial
+       ...      # function(with only one argument), even after 'factorial' is bound
+       ...      # to another object
+       ...      if n > 0:
+       ...              return n * thisfunction(n - 1)
+       ...      else:
+       ...              return 1
+       ...
+       >>> factorial(3)
+       6
+       """
+
+       @functools.wraps(f)
+       def bound_f(*args, **kwargs):
+               return f(bound_f, *args, **kwargs)
+       return bound_f
+
+
+class Memoize(object):
+       """
+       Memoize(fn) - an instance which acts like fn but memoizes its arguments
+       Will only work on functions with non-mutable arguments
+       @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
+
+       >>> validate_decorator(Memoize)
+       """
+
+       def __init__(self, fn):
+               self.fn = fn
+               self.__name__ = fn.__name__
+               self.__doc__ = fn.__doc__
+               self.__dict__.update(fn.__dict__)
+               self.memo = {}
+
+       def __call__(self, *args):
+               if args not in self.memo:
+                       self.memo[args] = self.fn(*args)
+               return self.memo[args]
+
+
+class MemoizeMutable(object):
+       """Memoize(fn) - an instance which acts like fn but memoizes its arguments
+       Will work on functions with mutable arguments(slower than Memoize)
+       @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52201
+
+       >>> validate_decorator(MemoizeMutable)
+       """
+
+       def __init__(self, fn):
+               self.fn = fn
+               self.__name__ = fn.__name__
+               self.__doc__ = fn.__doc__
+               self.__dict__.update(fn.__dict__)
+               self.memo = {}
+
+       def __call__(self, *args, **kw):
+               text = cPickle.dumps((args, kw))
+               if text not in self.memo:
+                       self.memo[text] = self.fn(*args, **kw)
+               return self.memo[text]
+
+
+callTraceIndentationLevel = 0
+
+
+def call_trace(f):
+       """
+       Synchronization decorator.
+
+       >>> validate_decorator(call_trace)
+       >>> @call_trace
+       ... def a(a, b, c):
+       ...     pass
+       >>> a(1, 2, c=3)
+       Entering a((1, 2), {'c': 3})
+       Exiting a((1, 2), {'c': 3})
+       """
+
+       @functools.wraps(f)
+       def verboseTrace(*args, **kw):
+               global callTraceIndentationLevel
+
+               print "%sEntering %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
+               callTraceIndentationLevel += 1
+               try:
+                       result = f(*args, **kw)
+               except:
+                       callTraceIndentationLevel -= 1
+                       print "%sException %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
+                       raise
+               callTraceIndentationLevel -= 1
+               print "%sExiting %s(%s, %s)" % ("\t"*callTraceIndentationLevel, f.__name__, args, kw)
+               return result
+
+       @functools.wraps(f)
+       def smallTrace(*args, **kw):
+               global callTraceIndentationLevel
+
+               print "%sEntering %s" % ("\t"*callTraceIndentationLevel, f.__name__)
+               callTraceIndentationLevel += 1
+               try:
+                       result = f(*args, **kw)
+               except:
+                       callTraceIndentationLevel -= 1
+                       print "%sException %s" % ("\t"*callTraceIndentationLevel, f.__name__)
+                       raise
+               callTraceIndentationLevel -= 1
+               print "%sExiting %s" % ("\t"*callTraceIndentationLevel, f.__name__)
+               return result
+
+       #return smallTrace
+       return verboseTrace
+
+
+@contextlib.contextmanager
+def lexical_scope(*args):
+       """
+       @note Source: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/520586
+       Example:
+       >>> b = 0
+       >>> with lexical_scope(1) as (a):
+       ...     print a
+       ...
+       1
+       >>> with lexical_scope(1,2,3) as (a,b,c):
+       ...     print a,b,c
+       ...
+       1 2 3
+       >>> with lexical_scope():
+       ...     d = 10
+       ...     def foo():
+       ...             pass
+       ...
+       >>> print b
+       2
+       """
+
+       frame = inspect.currentframe().f_back.f_back
+       saved = frame.f_locals.keys()
+       try:
+               if not args:
+                       yield
+               elif len(args) == 1:
+                       yield args[0]
+               else:
+                       yield args
+       finally:
+               f_locals = frame.f_locals
+               for key in (x for x in f_locals.keys() if x not in saved):
+                       del f_locals[key]
+               del frame
diff --git a/src/libraries/recipes/operators.py b/src/libraries/recipes/operators.py
new file mode 100644 (file)
index 0000000..a6f7618
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+
+"""
+Example Operators for comparison
+>>> class C(object):
+...     def __init__(self, x):
+...         self.x = x
+...
+>>> x, y, z = C(1), C(1), C(2)
+>>> x == y, hash(x) == hash(y)
+(False, False)
+"""
+
+
+import operator
+import itertools
+
+
+class KeyedEqualityOperators(object):
+       """
+       Mixin for auto-implementing comparison operators
+       @note Requires inheriting class to implement a '__key__' function
+       Example:
+       >>> class C(KeyedEqualityOperators):
+       ...     def __init__(self, x):
+       ...         self.x = x
+       ...     def __key__(self):
+       ...         return self.x
+       ...
+       >>> x, y, z = C(1), C(1), C(2)
+       >>> x == y, hash(x) == hash(y)
+       (True, False)
+       """
+
+       def __init__(self):
+               self.__key__ = None
+
+       def __eq__(self, other):
+               return self.__key__() == other.__key__()
+
+       def __ne__(self, other):
+               return self.__key__() != other.__key__()
+
+
+class KeyedComparisonOperators(KeyedEqualityOperators):
+       """
+       Mixin for auto-implementing comparison operators
+       @note Requires inheriting class to implement a '__key__' function
+       Example:
+       >>> class C(KeyedComparisonOperators):
+       ...     def __init__(self, x):
+       ...         self.x = x
+       ...     def __key__(self):
+       ...         return self.x
+       ...
+       >>> x, y, z = C(1), C(1), C(2)
+       >>> x == y, y < z, hash(x) == hash(y)
+       (True, True, False)
+       """
+
+       def __init__(self):
+               self.__key__ = None
+
+       def __cmp__(self, other):
+               return cmp(self.__key__(), other.__key__())
+
+       def __lt__(self, other):
+               return self.__key__() < other.__key__()
+
+       def __le__(self, other):
+               return self.__key__() <= other.__key__()
+
+       def __gt__(self, other):
+               return self.__key__() > other.__key__()
+
+       def __ge__(self, other):
+               return self.__key__() >= other.__key__()
+
+
+class KeyedHashing(object):
+       """
+       Mixin for auto-implementing comparison operators
+       @note Requires inheriting class to implement a '__key__' function
+       Example:
+       >>> class C(KeyedHashing):
+       ...     def __init__(self, x):
+       ...         self.x = x
+       ...     def __key__(self):
+       ...         return self.x
+       ...
+       >>> x, y, z = C(1), C(1), C(2)
+       >>> x == y, hash(x) == hash(y)
+       (False, True)
+       """
+
+       def __init__(self):
+               self.__key__ = None
+
+       def __hash__(self):
+               return hash(self.__key__())
+
+
+class NotEqualOperator(object):
+       """
+       Mixin for auto-implementing comparison operators
+       @note Requires inheriting class to implement '__eq__' function
+       """
+
+       def __ne__(self, other):
+               return not (self == other)
+
+
+class ComparisonOperators(NotEqualOperator):
+       """
+       Mixin for auto-implementing comparison operators
+       @note Requires inheriting class to implement '__lt__' function
+       """
+
+       def __le__(self, other):
+               return(self < other) or(self == other)
+
+       def __gt__(self, other):
+               return not(self <= other)
+
+       def __ge__(self, other):
+               return not(self < other)
+
+
+class infix(object):
+       """
+       Recipe #384122
+       http://code.activestate.com/recipes/384122/
+
+       >>> import operator
+       >>> x = infix(operator.mul)
+       >>> 1 |x| 2 |x| 10
+       20
+       """
+
+       def __init__(self, func):
+               self.__name__ = func.__name__
+               self.__doc__ = func.__doc__
+               try:
+                       self.__dict__.update(func.__dict__)
+               except AttributeError:
+                       pass
+               self.function = func
+
+       def __ror__(self, other):
+               return infix(lambda x: self.function(other, x))
+
+       def __or__(self, other):
+               return self.function(other)
+
+       def __call__(self, lhs, rhs):
+               return self.function(lhs, rhs)
+
+
+class Just(object):
+       """
+       @see mreturn
+       """
+
+       def __init__(self, value):
+               self.value = value
+
+
+@infix
+def mbind(maybe, func):
+       """
+       @see mreturn
+       """
+       if maybe is None:
+               return None
+       else:
+               return func(maybe.value)
+
+
+def mreturn(value):
+       """
+       >>> class Sheep(object):
+       ...     def __init__(self, name):
+       ...             self.name = name
+       ...             self.mother = None
+       ...             self.father = None
+       ...
+       >>> def father(sheep):
+       ...     if sheep.father is None:
+       ...             return None
+       ...     else:
+       ...             return Just(sheep.father)
+       ...
+       >>> def mother(sheep):
+       ...     if sheep.mother is None:
+       ...             return None
+       ...     else:
+       ...             return Just(sheep.mother)
+       ...
+       >>> def mothersFather(sheep):
+       ...     return mreturn(sheep) |mbind| mother |mbind| father
+       ...
+       >>> def mothersPaternalGrandfather(sheep):
+       ...     return mreturn(sheep) |mbind| mother |mbind| father |mbind| father
+       ...
+       >>> shawn = Sheep("Shawn")
+       >>> gertrude = Sheep("Gertrude")
+       >>> ernie = Sheep("Ernie")
+       >>> frank = Sheep("Frank")
+       >>>
+       >>> shawn.mother = gertrude
+       >>> gertrude.father = ernie
+       >>> ernie.father = frank
+       >>>
+       >>> print mothersFather(shawn).value.name
+       Ernie
+       >>> print mothersPaternalGrandfather(shawn).value.name
+       Frank
+       >>> print mothersPaternalGrandfather(ernie)
+       None
+       """
+       return Just(value)
+
+
+def xor(*args):
+       truth = itertools.imap(operator.truth, args)
+       return reduce(operator.xor, truth)
+
+
+def equiv(*args):
+       truth = itertools.imap(operator.truth, args)
+       return reduce(lambda a, b: not operator.xor(a, b), truth)
diff --git a/src/libraries/recipes/overloading.py b/src/libraries/recipes/overloading.py
new file mode 100644 (file)
index 0000000..89cb738
--- /dev/null
@@ -0,0 +1,256 @@
+#!/usr/bin/env python
+import new
+
+# Make the environment more like Python 3.0
+__metaclass__ = type
+from itertools import izip as zip
+import textwrap
+import inspect
+
+
+__all__ = [
+       "AnyType",
+       "overloaded"
+]
+
+
+AnyType = object
+
+
+class overloaded:
+       """
+       Dynamically overloaded functions.
+
+       This is an implementation of (dynamically, or run-time) overloaded
+       functions; also known as generic functions or multi-methods.
+
+       The dispatch algorithm uses the types of all argument for dispatch,
+       similar to (compile-time) overloaded functions or methods in C++ and
+       Java.
+
+       Most of the complexity in the algorithm comes from the need to support
+       subclasses in call signatures.  For example, if an function is
+       registered for a signature (T1, T2), then a call with a signature (S1,
+       S2) is acceptable, assuming that S1 is a subclass of T1, S2 a subclass
+       of T2, and there are no other more specific matches (see below).
+
+       If there are multiple matches and one of those doesn't *dominate* all
+       others, the match is deemed ambiguous and an exception is raised.  A
+       subtlety here: if, after removing the dominated matches, there are
+       still multiple matches left, but they all map to the same function,
+       then the match is not deemed ambiguous and that function is used.
+       Read the method find_func() below for details.
+
+       @note Python 2.5 is required due to the use of predicates any() and all().
+       @note only supports positional arguments
+
+       @author http://www.artima.com/weblogs/viewpost.jsp?thread=155514
+
+       >>> import misc
+       >>> misc.validate_decorator (overloaded)
+       >>>
+       >>>
+       >>>
+       >>>
+       >>> #################
+       >>> #Basics, with reusing names and without
+       >>> @overloaded
+       ... def foo(x):
+       ...     "prints x"
+       ...     print x
+       ...
+       >>> @foo.register(int)
+       ... def foo(x):
+       ...     "prints the hex representation of x"
+       ...     print hex(x)
+       ...
+       >>> from types import DictType
+       >>> @foo.register(DictType)
+       ... def foo_dict(x):
+       ...     "prints the keys of x"
+       ...     print [k for k in x.iterkeys()]
+       ...
+       >>> #combines all of the doc strings to help keep track of the specializations
+       >>> foo.__doc__  # doctest: +ELLIPSIS
+       "prints x\\n\\n...overloading.foo (<type 'int'>):\\n\\tprints the hex representation of x\\n\\n...overloading.foo_dict (<type 'dict'>):\\n\\tprints the keys of x"
+       >>> foo ("text")
+       text
+       >>> foo (10) #calling the specialized foo
+       0xa
+       >>> foo ({3:5, 6:7}) #calling the specialization foo_dict
+       [3, 6]
+       >>> foo_dict ({3:5, 6:7}) #with using a unique name, you still have the option of calling the function directly
+       [3, 6]
+       >>>
+       >>>
+       >>>
+       >>>
+       >>> #################
+       >>> #Multiple arguments, accessing the default, and function finding
+       >>> @overloaded
+       ... def two_arg (x, y):
+       ...     print x,y
+       ...
+       >>> @two_arg.register(int, int)
+       ... def two_arg_int_int (x, y):
+       ...     print hex(x), hex(y)
+       ...
+       >>> @two_arg.register(float, int)
+       ... def two_arg_float_int (x, y):
+       ...     print x, hex(y)
+       ...
+       >>> @two_arg.register(int, float)
+       ... def two_arg_int_float (x, y):
+       ...     print hex(x), y
+       ...
+       >>> two_arg.__doc__ # doctest: +ELLIPSIS
+       "...overloading.two_arg_int_int (<type 'int'>, <type 'int'>):\\n\\n...overloading.two_arg_float_int (<type 'float'>, <type 'int'>):\\n\\n...overloading.two_arg_int_float (<type 'int'>, <type 'float'>):"
+       >>> two_arg(9, 10)
+       0x9 0xa
+       >>> two_arg(9.0, 10)
+       9.0 0xa
+       >>> two_arg(15, 16.0)
+       0xf 16.0
+       >>> two_arg.default_func(9, 10)
+       9 10
+       >>> two_arg.find_func ((int, float)) == two_arg_int_float
+       True
+       >>> (int, float) in two_arg
+       True
+       >>> (str, int) in two_arg
+       False
+       >>>
+       >>>
+       >>>
+       >>> #################
+       >>> #wildcard
+       >>> @two_arg.register(AnyType, str)
+       ... def two_arg_any_str (x, y):
+       ...     print x, y.lower()
+       ...
+       >>> two_arg("Hello", "World")
+       Hello world
+       >>> two_arg(500, "World")
+       500 world
+       """
+
+       def __init__(self, default_func):
+               # Decorator to declare new overloaded function.
+               self.registry = {}
+               self.cache = {}
+               self.default_func = default_func
+               self.__name__ = self.default_func.__name__
+               self.__doc__ = self.default_func.__doc__
+               self.__dict__.update (self.default_func.__dict__)
+
+       def __get__(self, obj, type=None):
+               if obj is None:
+                       return self
+               return new.instancemethod(self, obj)
+
+       def register(self, *types):
+               """
+               Decorator to register an implementation for a specific set of types.
+
+               .register(t1, t2)(f) is equivalent to .register_func((t1, t2), f).
+               """
+
+               def helper(func):
+                       self.register_func(types, func)
+
+                       originalDoc = self.__doc__ if self.__doc__ is not None else ""
+                       typeNames = ", ".join ([str(type) for type in types])
+                       typeNames = "".join ([func.__module__+".", func.__name__, " (", typeNames, "):"])
+                       overloadedDoc = ""
+                       if func.__doc__ is not None:
+                               overloadedDoc = textwrap.fill (func.__doc__, width=60, initial_indent="\t", subsequent_indent="\t")
+                       self.__doc__ = "\n".join ([originalDoc, "", typeNames, overloadedDoc]).strip()
+
+                       new_func = func
+
+                       #Masking the function, so we want to take on its traits
+                       if func.__name__ == self.__name__:
+                               self.__dict__.update (func.__dict__)
+                               new_func = self
+                       return new_func
+
+               return helper
+
+       def register_func(self, types, func):
+               """Helper to register an implementation."""
+               self.registry[tuple(types)] = func
+               self.cache = {} # Clear the cache (later we can optimize this).
+
+       def __call__(self, *args):
+               """Call the overloaded function."""
+               types = tuple(map(type, args))
+               func = self.cache.get(types)
+               if func is None:
+                       self.cache[types] = func = self.find_func(types)
+               return func(*args)
+
+       def __contains__ (self, types):
+               return self.find_func(types) is not self.default_func
+
+       def find_func(self, types):
+               """Find the appropriate overloaded function; don't call it.
+
+               @note This won't work for old-style classes or classes without __mro__
+               """
+               func = self.registry.get(types)
+               if func is not None:
+                       # Easy case -- direct hit in registry.
+                       return func
+
+               # Phillip Eby suggests to use issubclass() instead of __mro__.
+               # There are advantages and disadvantages.
+
+               # I can't help myself -- this is going to be intense functional code.
+               # Find all possible candidate signatures.
+               mros = tuple(inspect.getmro(t) for t in types)
+               n = len(mros)
+               candidates = [sig for sig in self.registry
+                               if len(sig) == n and
+                                       all(t in mro for t, mro in zip(sig, mros))]
+
+               if not candidates:
+                       # No match at all -- use the default function.
+                       return self.default_func
+               elif len(candidates) == 1:
+                       # Unique match -- that's an easy case.
+                       return self.registry[candidates[0]]
+
+               # More than one match -- weed out the subordinate ones.
+
+               def dominates(dom, sub,
+                               orders=tuple(dict((t, i) for i, t in enumerate(mro))
+                                                       for mro in mros)):
+                       # Predicate to decide whether dom strictly dominates sub.
+                       # Strict domination is defined as domination without equality.
+                       # The arguments dom and sub are type tuples of equal length.
+                       # The orders argument is a precomputed auxiliary data structure
+                       # giving dicts of ordering information corresponding to the
+                       # positions in the type tuples.
+                       # A type d dominates a type s iff order[d] <= order[s].
+                       # A type tuple (d1, d2, ...) dominates a type tuple of equal length
+                       # (s1, s2, ...) iff d1 dominates s1, d2 dominates s2, etc.
+                       if dom is sub:
+                               return False
+                       return all(order[d] <= order[s] for d, s, order in zip(dom, sub, orders))
+
+               # I suppose I could inline dominates() but it wouldn't get any clearer.
+               candidates = [cand
+                               for cand in candidates
+                                       if not any(dominates(dom, cand) for dom in candidates)]
+               if len(candidates) == 1:
+                       # There's exactly one candidate left.
+                       return self.registry[candidates[0]]
+
+               # Perhaps these multiple candidates all have the same implementation?
+               funcs = set(self.registry[cand] for cand in candidates)
+               if len(funcs) == 1:
+                       return funcs.pop()
+
+               # No, the situation is irreducibly ambiguous.
+               raise TypeError("ambigous call; types=%r; candidates=%r" %
+                                               (types, candidates))
diff --git a/src/libraries/recipes/test_utils.py b/src/libraries/recipes/test_utils.py
new file mode 100644 (file)
index 0000000..a2da797
--- /dev/null
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+import inspect
+import contextlib
+import functools
+
+
+def TODO(func):
+       """
+       unittest test method decorator that ignores
+       exceptions raised by test
+
+       Used to annotate test methods for code that may
+       not be written yet.  Ignores failures in the
+       annotated test method; fails if the text
+       unexpectedly succeeds.
+       !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html
+
+       Example:
+       >>> import unittest
+       >>> class ExampleTestCase(unittest.TestCase):
+       ...     @TODO
+       ...     def testToDo(self):
+       ...             MyModule.DoesNotExistYet('boo')
+       ...
+       """
+
+       @functools.wraps(func)
+       def wrapper(*args, **kw):
+               try:
+                       func(*args, **kw)
+                       succeeded = True
+               except:
+                       succeeded = False
+               assert succeeded is False, \
+                       "%s marked TODO but passed" % func.__name__
+       return wrapper
+
+
+def PlatformSpecific(platformList):
+       """
+       unittest test method decorator that only
+       runs test method if os.name is in the
+       given list of platforms
+       !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html
+       Example:
+       >>> import unittest
+       >>> class ExampleTestCase(unittest.TestCase):
+       ...     @PlatformSpecific(('mac', ))
+       ...     def testMacOnly(self):
+       ...             MyModule.SomeMacSpecificFunction()
+       ...
+       """
+
+       def decorator(func):
+               import os
+
+               @functools.wraps(func)
+               def wrapper(*args, **kw):
+                       if os.name in platformList:
+                               return func(*args, **kw)
+               return wrapper
+       return decorator
+
+
+def CheckReferences(func):
+       """
+       !author http://kbyanc.blogspot.com/2007/06/pythons-unittest-module-aint-that-bad.html
+       """
+
+       @functools.wraps(func)
+       def wrapper(*args, **kw):
+               refCounts = []
+               for i in range(5):
+                       func(*args, **kw)
+                       refCounts.append(XXXGetRefCount())
+               assert min(refCounts) != max(refCounts), "Reference counts changed - %r" % refCounts
+
+       return wrapper
+
+
+@contextlib.contextmanager
+def expected(exception):
+       """
+       >>> with expected2(ZeroDivisionError):
+       ...     1 / 0
+       >>> with expected2(AssertionError("expected ZeroDivisionError to have been thrown")):
+       ...     with expected(ZeroDivisionError):
+       ...             1 / 2
+       Traceback (most recent call last):
+               File "/usr/lib/python2.5/doctest.py", line 1228, in __run
+                       compileflags, 1) in test.globs
+               File "<doctest libraries.recipes.context.expected[1]>", line 3, in <module>
+                       1 / 2
+               File "/media/data/Personal/Development/bzr/Recollection-trunk/src/libraries/recipes/context.py", line 139, in __exit__
+                       assert t is not None, ("expected {0:%s} to have been thrown" % (self._t.__name__))
+       AssertionError: expected {0:ZeroDivisionError} to have been thrown
+       >>> with expected2(Exception("foo")):
+       ...     raise Exception("foo")
+       >>> with expected2(Exception("bar")):
+       ...     with expected(Exception("foo")): # this won't catch it
+       ...             raise Exception("bar")
+       ...     assert False, "should not see me"
+       >>> with expected2(Exception("can specify")):
+       ...     raise Exception("can specify prefixes")
+       >>> with expected2(Exception("Base class fun")):
+       True
+       >>> True
+       False
+       """
+       if isinstance(exception, Exception):
+               excType, excValue = type(exception), str(exception)
+       elif isinstance(exception, type):
+               excType, excValue = exception, ""
+
+       try:
+               yield
+       except Exception, e:
+               if not (excType in inspect.getmro(type(e)) and str(e).startswith(excValue)):
+                       raise
+       else:
+               raise AssertionError("expected {0:%s} to have been thrown" % excType.__name__)
+
+
+if __name__ == "__main__":
+       import doctest
+       doctest.testmod()
diff --git a/src/libraries/recipes/xml_utils.py b/src/libraries/recipes/xml_utils.py
new file mode 100755 (executable)
index 0000000..55be820
--- /dev/null
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+
+def flatten(elem, includeTail = False):
+       """
+       Recursively extract text content.
+
+       @note To get rid of all subelements to a given element, and keep just the text, you can do:
+               elem.text = flatten(elem); del elem[:]
+       """
+       text = elem.text or ""
+       for e in elem:
+               text += flatten(e)
+               if includeTail and e.tail:
+                       text += e.tail
+       return text
+
+
+def append(elem, item):
+       """
+       Universal append to an Element
+       @param elem ElementTree.Element
+       @param item Either None, Str/Unicode, or ElementTree.Element
+       """
+       if item is None:
+               return
+
+       if isinstance(item, basestring):
+               if len(elem):
+                       elem[-1].tail = (elem[-1].tail or "") + item
+               else:
+                       elem.text = (elem.text or "") + item
+       else:
+               elem.append(item)
+
+
+def indent(elem, level=0, indentation="    "):
+       """
+       Add indentation to the data of in an ElementTree
+
+       >>> from xml.etree import ElementTree
+       >>> xmlRoot = ElementTree.fromstring("<xml><tree><bird /></tree></xml>")
+       >>> indent(xmlRoot)
+       >>> ElementTree.dump(xmlRoot)
+       <xml>
+           <tree>
+               <bird />
+           </tree>
+       </xml>
+       """
+
+       i = "\n" + level*indentation
+       if len(elem):
+               if not elem.text or not elem.text.strip():
+                       elem.text = i + indentation
+               for e in elem:
+                       indent(e, level+1, indentation)
+                       if not e.tail or not e.tail.strip():
+                               e.tail = i + indentation
+                       if not e.tail or not e.tail.strip():
+                               e.tail = i
+       else:
+               if level and (not elem.tail or not elem.tail.strip()):
+                       elem.tail = i
+
+
+if __name__ == "__main__":
+       import sys
+       from xml.etree import ElementTree
+       if len(sys.argv) == 3:
+               xml = ElementTree.parse(sys.argv[1])
+               indent(xml.getroot())
+               with open(sys.argv[2], "w") as source:
+                       xml.write(source)
+       elif len(sys.argv) == 1:
+               xml = ElementTree.parse(sys.stdin)
+               indent(xml.getroot())
+               xml.write(sys.stdout)
diff --git a/src/operation.py b/src/operation.py
new file mode 100644 (file)
index 0000000..48659df
--- /dev/null
@@ -0,0 +1,420 @@
+#!/usr/bin/env python
+
+
+import itertools
+import functools
+import decimal
+
+from libraries.recipes import overloading
+from libraries.recipes import algorithms
+
+
+@overloading.overloaded
+def serialize_value(value, base, renderer):
+       yield renderer(value, base)
+
+
+@serialize_value.register(complex, overloading.AnyType, overloading.AnyType)
+def serialize_complex(value, base, renderer):
+       if value.real == 0.0:
+               yield renderer(value.imag*1j, base)
+       elif value.imag == 0.0:
+               yield renderer(value.real, base)
+       else:
+               yield renderer(value.real, base)
+               yield renderer(value.imag*1j, base)
+               yield "+"
+
+
+def render_float(value):
+       return str(value)
+
+
+def render_float_dec(value):
+       floatText = str(value)
+       dec = decimal.Decimal(floatText)
+       return str(dec)
+
+
+def render_float_eng(value):
+       floatText = str(value)
+       dec = decimal.Decimal(floatText)
+       return dec.to_eng_string()
+
+
+def render_float_sci(value):
+       floatText = str(value)
+       dec = decimal.Decimal(floatText)
+       return dec.to_sci_string()
+
+
+def render_complex(floatRender):
+
+       def render_complex_real(value):
+               realRendered = floatRender(value.real)
+               imagRendered = floatRender(value.imag)
+               rendered = "%s+%sj" % (realRendered, imagRendered)
+               return rendered
+
+       return render_complex_real
+
+
+def _seperate_num(rendered, sep, count):
+       """
+       >>> _seperate_num("123", ",", 3)
+       '123'
+       >>> _seperate_num("123456", ",", 3)
+       '123,456'
+       >>> _seperate_num("1234567", ",", 3)
+       '1,234,567'
+       """
+       leadCount = len(rendered) % count
+       choppyRest = algorithms.itergroup(rendered[leadCount:], count)
+       rest = (
+               "".join(group)
+               for group in choppyRest
+       )
+       if 0 < leadCount:
+               lead = rendered[0:leadCount]
+               parts = itertools.chain((lead, ), rest)
+       else:
+               parts = rest
+       return sep.join(parts)
+
+
+def render_integer_oct(value, sep=""):
+       rendered = oct(int(value))
+       if 0 < len(sep):
+               assert rendered.startswith("0")
+               rendered = "0o%s" % _seperate_num(rendered[1:], sep, 3)
+       return rendered
+
+
+def render_integer_dec(value, sep=""):
+       rendered = str(int(value))
+       if 0 < len(sep):
+               rendered = "%s" % _seperate_num(rendered, sep, 3)
+       return rendered
+
+
+def render_integer_hex(value, sep=""):
+       rendered = hex(int(value))
+       if 0 < len(sep):
+               assert rendered.startswith("0x")
+               rendered = "0x%s" % _seperate_num(rendered[2:], sep, 3)
+       return rendered
+
+
+def set_render_int_seperator(renderer, sep):
+
+       @functools.wrap(renderer)
+       def render_with_sep(value):
+               return renderer(value, sep)
+
+       return render_with_sep
+
+
+class render_number(object):
+
+       def __init__(self,
+               ints = None,
+               f = None,
+               c = None,
+       ):
+               if ints is not None:
+                       self.render_int = ints
+               else:
+                       self.render_int = {
+                               2: render_integer_hex,
+                               8: render_integer_oct,
+                               10: render_integer_dec,
+                               16: render_integer_hex,
+                       }
+               self.render_float = f if c is not None else render_float
+               self.render_complex = c if c is not None else self
+
+       def __call__(self, value, base):
+               return self.render(value, base)
+
+       @overloading.overloaded
+       def render(self, value, base):
+               return str(value)
+
+       @render.register(overloading.AnyType, int, overloading.AnyType)
+       def _render_int(self, value, base):
+               renderer = self.render_int.get(base, render_integer_dec)
+               return renderer(value)
+
+       @render.register(overloading.AnyType, float, overloading.AnyType)
+       def _render_float(self, value, base):
+               return self.render_float(value)
+
+       @render.register(overloading.AnyType, complex, overloading.AnyType)
+       def _render_complex(self, value, base):
+               return self.render_float(value)
+
+
+class Operation(object):
+
+       def __init__(self):
+               self._base = 10
+
+       def __str__(self):
+               raise NotImplementedError
+
+       @property
+       def base(self):
+               base = self._base
+               return base
+
+       def get_children(self):
+               return []
+
+       def serialize(self, renderer):
+               for child in self.get_children():
+                       for childItem in child.serialize(renderer):
+                               yield childItem
+
+       def simplify(self):
+               """
+               @returns an operation tree with all constant calculations performed and only variables left
+               """
+               raise NotImplementedError
+
+       def evaluate(self):
+               """
+               @returns a value that the tree represents, if it can't be evaluated,
+                       then an exception is throwd
+               """
+               raise NotImplementedError
+
+       def __call__(self):
+               return self.evaluate()
+
+
+class Value(Operation):
+
+       def __init__(self, value, base):
+               super(Value, self).__init__()
+               self.value = value
+               self._base = base
+
+       def serialize(self, renderer):
+               for item in super(Value, self).serialize(renderer):
+                       yield item
+               for component in serialize_value(self.value, self.base, renderer):
+                       yield component
+
+       def __str__(self):
+               return str(self.value)
+
+       def simplify(self):
+               return self
+
+       def evaluate(self):
+               return self.value
+
+
+class Constant(Operation):
+
+       def __init__(self, name, valueNode):
+               super(Constant, self).__init__()
+               self.name = name
+               self.__valueNode = valueNode
+
+       def serialize(self, renderer):
+               for item in super(Constant, self).serialize(renderer):
+                       yield item
+               yield self.name
+
+       def __str__(self):
+               return self.name
+
+       def simplify(self):
+               return self.__valueNode.simplify()
+
+       def evaluate(self):
+               return self.__valueNode.evaluate()
+
+
+class Variable(Operation):
+
+       def __init__(self, name):
+               super(Variable, self).__init__()
+               self.name = name
+
+       def serialize(self, renderer):
+               for item in super(Variable, self).serialize(renderer):
+                       yield item
+               yield self.name
+
+       def __str__(self):
+               return self.name
+
+       def simplify(self):
+               return self
+
+       def evaluate(self):
+               raise KeyError('Variable "%s" unable to evaluate to specific value' % self.name)
+
+
+class Function(Operation):
+
+       REP_FUNCTION = 0
+       REP_PREFIX = 1
+       REP_INFIX = 2
+       REP_POSTFIX = 3
+
+       _op = None
+       _rep = REP_FUNCTION
+       symbol = None
+       argumentCount = 0
+
+       def __init__(self, *args, **kwd):
+               super(Function, self).__init__()
+               self._base = None
+               self._args = args
+               self._kwd = kwd
+               self._simple = self._simplify()
+               self._str = self.pretty_print(args, kwd)
+
+       def serialize(self, renderer):
+               for item in super(Function, self).serialize(renderer):
+                       yield item
+               yield self.symbol
+
+       def get_children(self):
+               return (
+                       arg
+                       for arg in self._args
+               )
+
+       @property
+       def base(self):
+               base = self._base
+               if base is None:
+                       bases = [arg.base for arg in self._args]
+                       base = bases[0]
+               assert base is not None
+               return base
+
+       def __str__(self):
+               return self._str
+
+       def simplify(self):
+               return self._simple
+
+       def evaluate(self):
+               selfArgs = [arg.evaluate() for arg in self._args]
+               return Value(self._op(*selfArgs), self.base)
+
+       def _simplify(self):
+               selfArgs = [arg.simplify() for arg in self._args]
+               selfKwd = dict(
+                       (name, arg.simplify())
+                       for (name, arg) in self._kwd
+               )
+
+               try:
+                       args = [arg.evaluate() for arg in selfArgs]
+                       base = self.base
+                       result = self._op(*args)
+
+                       node = Value(result, base)
+               except KeyError:
+                       node = self
+
+               return node
+
+       @classmethod
+       def pretty_print(cls, args = None, kwds = None):
+               if args is None:
+                       args = []
+               if kwds is None:
+                       kwds = {}
+
+               if cls._rep == cls.REP_FUNCTION:
+                       positional = (str(arg) for arg in args)
+                       named = (
+                               "%s=%s" % (str(key), str(value))
+                               for (key, value) in kwds.iteritems()
+                       )
+                       return "%s(%s)" % (
+                               cls.symbol,
+                               ", ".join(itertools.chain(named, positional)),
+                       )
+               elif cls._rep == cls.REP_PREFIX:
+                       assert len(args) == 1
+                       return "%s %s" % (cls.symbol, args[0])
+               elif cls._rep == cls.REP_POSTFIX:
+                       assert len(args) == 1
+                       return "%s %s" % (args[0], cls.symbol)
+               elif cls._rep == cls.REP_INFIX:
+                       assert len(args) == 2
+                       return "(%s %s %s)" % (
+                               str(args[0]),
+                               str(cls.symbol),
+                               str(args[1]),
+                       )
+               else:
+                       raise AssertionError("Unsupported rep style")
+
+
+def generate_function(op, rep, style, numArgs):
+
+       class GenFunc(Function):
+
+               def __init__(self, *args, **kwd):
+                       super(GenFunc, self).__init__(*args, **kwd)
+
+               _op = op
+               _rep = style
+               symbol = rep
+               argumentCount = numArgs
+
+       GenFunc.__name__ = op.__name__
+       return GenFunc
+
+
+def change_base(base, rep):
+
+       class GenFunc(Function):
+
+               def __init__(self, *args, **kwd):
+                       super(GenFunc, self).__init__(*args, **kwd)
+                       self._base = base
+                       self._simple = self._simplify()
+                       self._str = self.pretty_print(args, kwd)
+
+               _op = lambda self, n: n
+               _rep = Function.REP_FUNCTION
+               symbol = rep
+               argumentCount = 1
+
+       GenFunc.__name__ = rep
+       return GenFunc
+
+
+@overloading.overloaded
+def render_operation(render_func, operation):
+       return str(operation)
+
+
+@render_operation.register(overloading.AnyType, Value)
+def render_value(render_func, operation):
+       return render_func(operation.value, operation.base)
+
+
+@render_operation.register(overloading.AnyType, Variable)
+@render_operation.register(overloading.AnyType, Constant)
+def render_variable(render_func, operation):
+       return operation.name
+
+
+@render_operation.register(overloading.AnyType, Function)
+def render_function(render_func, operation):
+       args = [
+               render_operation(render_func, arg)
+               for arg in operation.get_children()
+       ]
+       return operation.pretty_print(args)
diff --git a/src/plugin_utils.py b/src/plugin_utils.py
new file mode 100644 (file)
index 0000000..cda1f0a
--- /dev/null
@@ -0,0 +1,315 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+
+import sys
+import os
+import inspect
+import ConfigParser
+
+from libraries import gtkpieboard
+from libraries.recipes import io
+import operation
+
+
+class CommandStackHandler(object):
+
+       def __init__(self, stack, command, operator):
+               self.command = command
+
+               self.__stack = stack
+               self.__operator = operator
+
+       def handler(self, commandName, activeModifiers):
+               self.__stack.apply_operation(self.__operator)
+
+
+class PieKeyboardPlugin(object):
+
+       def __init__(self, name, factory):
+               self.name = name
+               self.factory = factory
+               self.__handler = None
+
+       def setup(self, calcStack, style, boardHandler):
+               self.__handler = boardHandler
+
+               with open(self.factory.mapFile, "r") as mapfile:
+                       boardTree = gtkpieboard.parse_keyboard_data("\n".join(mapfile.readlines()))
+
+               rows, columns = boardTree["dimensions"]
+               keyboardName = boardTree["name"]
+               keyTree = boardTree["keys"]
+
+               keyboard = gtkpieboard.PieKeyboard(style, rows, columns)
+               gtkpieboard.load_keyboard(keyboardName, keyTree, keyboard, self.__handler)
+
+               for commandName, operator in self.factory.commands.iteritems():
+                       handler = CommandStackHandler(calcStack, commandName, operator)
+                       self.__handler.register_command_handler(commandName, handler.handler)
+
+               return keyboard
+
+       def tear_down(self):
+               for commandName, operator in self.factory.commands.itervalues():
+                       self.__handler.unregister_command_handler(commandName)
+
+               # Leave our self completely unusable
+               self.name = None
+               self.factory = None
+               self.__handler = None
+
+
+class PieKeyboardPluginFactory(object):
+
+       def __init__(self, pluginName, keyboardMapFile):
+               self.name = pluginName
+               self.mapFile = keyboardMapFile
+               self.commands = {}
+
+       def register_operation(self, commandName, operator):
+               self.commands[commandName] = operator
+
+       def construct_keyboard(self):
+               plugin = PieKeyboardPlugin(self.name, self)
+               return plugin
+
+
+class PluginManager(object):
+
+       def __init__(self, pluginType):
+               self._pluginType = pluginType
+               self._plugins = {}
+               self._enabled = set()
+
+               self.__searchPaths = []
+
+       def add_path(self, *paths):
+               self.__searchPaths.append(paths)
+               self.__scan(paths)
+
+       def rescan(self):
+               self._plugins = {}
+               self.__scan(self.__searchPaths)
+
+       def plugin_info(self, pluginId):
+               pluginData = self._plugins[pluginId]
+               return pluginData["name"], pluginData["version"], pluginData["description"]
+
+       def plugins(self):
+               for id, pluginData in self._plugins.iteritems():
+                       yield id, pluginData["name"], pluginData["version"], pluginData["description"]
+
+       def enable_plugin(self, id):
+               assert id in self._plugins, "Can't find plugin %s in the search path %r" % (id, self.__searchPaths)
+               self._load_module(id)
+               self._enabled.add(id)
+
+       def disable_plugin(self, id):
+               self._enabled.remove(id)
+
+       def lookup_plugin(self, name):
+               for id, data in self._plugins.iteritems():
+                       if data["name"] == name:
+                               return id
+
+       def _load_module(self, id):
+               pluginData = self._plugins[id]
+
+               if "module" not in pluginData:
+                       pluginPath = pluginData["pluginpath"]
+                       dataPath = pluginData["datapath"]
+                       assert dataPath.endswith(".ini")
+
+                       dataPath = io.relpath(pluginPath, dataPath)
+                       pythonPath = dataPath[0:-len(".ini")]
+                       modulePath = fspath_to_ipath(pythonPath, "")
+
+                       sys.path.append(pluginPath)
+                       try:
+                               module = __import__(modulePath)
+                       finally:
+                               sys.path.remove(pluginPath)
+                       pluginData["module"] = module
+               else:
+                       # @todo Decide if want to call reload
+                       module = pluginData["module"]
+
+               return module
+
+       def __scan(self, paths):
+               pluginDataFiles = find_plugins(paths, ".ini")
+
+               for pluginPath, pluginDataFile in pluginDataFiles:
+                       config = ConfigParser.SafeConfigParser()
+                       config.read(pluginDataFile)
+
+                       name = config.get(self._pluginType, "name")
+                       version = config.get(self._pluginType, "version")
+                       description = config.get(self._pluginType, "description")
+
+                       self._plugins[pluginDataFile] = {
+                               "name": name,
+                               "version": version,
+                               "description": description,
+                               "datapath": pluginDataFile,
+                               "pluginpath": pluginPath,
+                       }
+
+
+class ConstantPluginManager(PluginManager):
+
+       def __init__(self):
+               super(ConstantPluginManager, self).__init__("Constants")
+               self.__constants = {}
+               self.__constantsCache = {}
+               self.__isCacheDirty = False
+
+       def enable_plugin(self, id):
+               super(ConstantPluginManager, self).enable_plugin(id)
+               self.__constants[id] = dict(
+                       extract_instance_from_plugin(self._plugins[id]["module"], operation.Operation)
+               )
+               self.__isCacheDirty = True
+
+       def disable_plugin(self, id):
+               super(ConstantPluginManager, self).disable_plugin(id)
+               self.__isCacheDirty = True
+
+       @property
+       def constants(self):
+               if self.__isCacheDirty:
+                       self.__update_cache()
+               return self.__constantsCache
+
+       def __update_cache(self):
+               self.__constantsCache.clear()
+               for pluginId in self._enabled:
+                       self.__constantsCache.update(self.__constants[pluginId])
+               self.__isCacheDirty = False
+
+
+class OperatorPluginManager(PluginManager):
+
+       def __init__(self):
+               super(OperatorPluginManager, self).__init__("Operator")
+               self.__operators = {}
+               self.__operatorsCache = {}
+               self.__isCacheDirty = False
+
+       def enable_plugin(self, id):
+               super(OperatorPluginManager, self).enable_plugin(id)
+               operators = (
+                       extract_class_from_plugin(self._plugins[id]["module"], operation.Operation)
+               )
+               self.__operators[id] = dict(
+                       (op.symbol, op)
+                       for op in operators
+               )
+               self.__isCacheDirty = True
+
+       def disable_plugin(self, id):
+               super(OperatorPluginManager, self).disable_plugin(id)
+               self.__isCacheDirty = True
+
+       @property
+       def operators(self):
+               if self.__isCacheDirty:
+                       self.__update_cache()
+               return self.__operatorsCache
+
+       def __update_cache(self):
+               self.__operatorsCache.clear()
+               for pluginId in self._enabled:
+                       self.__operatorsCache.update(self.__operators[pluginId])
+               self.__isCacheDirty = False
+
+
+class KeyboardPluginManager(PluginManager):
+
+       def __init__(self):
+               super(KeyboardPluginManager, self).__init__("Keyboard")
+               self.__keyboards = {}
+               self.__keyboardsCache = {}
+               self.__isCacheDirty = False
+
+       def enable_plugin(self, id):
+               super(KeyboardPluginManager, self).enable_plugin(id)
+               keyboards = (
+                       extract_instance_from_plugin(self._plugins[id]["module"], PieKeyboardPluginFactory)
+               )
+               self.__keyboards[id] = dict(
+                       (board.name, board)
+                       for boardVariableName, board in keyboards
+               )
+               self.__isCacheDirty = True
+
+       def disable_plugin(self, id):
+               super(KeyboardPluginManager, self).disable_plugin(id)
+               self.__isCacheDirty = True
+
+       @property
+       def keyboards(self):
+               if self.__isCacheDirty:
+                       self.__update_cache()
+               return self.__keyboardsCache
+
+       def __update_cache(self):
+               self.__keyboardsCache.clear()
+               for pluginId in self._enabled:
+                       self.__keyboardsCache.update(self.__keyboards[pluginId])
+               self.__isCacheDirty = False
+
+
+def fspath_to_ipath(fsPath, extension = ".py"):
+       """
+       >>> fspath_to_ipath("user/test/file.py")
+       'user.test.file'
+       """
+       assert fsPath.endswith(extension)
+       CURRENT_DIR = "."+os.sep
+       CURRENT_DIR_LEN = len(CURRENT_DIR)
+       if fsPath.startswith(CURRENT_DIR):
+               fsPath = fsPath[CURRENT_DIR_LEN:]
+
+       if extension:
+               fsPath = fsPath[0:-len(extension)]
+       parts = fsPath.split(os.sep)
+       return ".".join(parts)
+
+
+def find_plugins(searchPaths, fileType=".py"):
+       pythonFiles = (
+               (path, os.path.join(root, file))
+               for path in searchPaths
+               for root, dirs, files in os.walk(path)
+               for file in files
+                       if file.endswith(fileType)
+       )
+       return pythonFiles
+
+
+def extract_class_from_plugin(pluginModule, cls):
+       try:
+               for item in pluginModule.__dict__.itervalues():
+                       try:
+                               if cls in inspect.getmro(item):
+                                       yield item
+                       except AttributeError:
+                               pass
+       except AttributeError:
+               pass
+
+
+def extract_instance_from_plugin(pluginModule, cls):
+       try:
+               for name, item in pluginModule.__dict__.iteritems():
+                       try:
+                               if isinstance(item, cls):
+                                       yield name, item
+                       except AttributeError:
+                               pass
+       except AttributeError:
+               pass
diff --git a/src/plugins/__init__.py b/src/plugins/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/plugins/alphabet.ini b/src/plugins/alphabet.ini
new file mode 100644 (file)
index 0000000..cb845d1
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Alphabet
+version=0.1
+description=
+
+[Constants]
+name=Alphabet
+version=0.1
+description=
+
+[Keyboard]
+name=Alphabet
+version=0.1
+description=
diff --git a/src/plugins/alphabet.map b/src/plugins/alphabet.map
new file mode 100644 (file)
index 0000000..1d1d7c2
--- /dev/null
@@ -0,0 +1,59 @@
+{
+       "name": "Alphabet",
+       "dimensions": (3, 3),
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "e", "type": "text", "text": "E", },
+                       "SOUTH": {"action": "q", "type": "text", "text": "Q", },
+                       "EAST": {"action": "w", "type": "text", "text": "W", },
+                       "showAllSlices": True,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "t", "type": "text", "text": "T", },
+                       "WEST": {"action": "r", "type": "text", "text": "R", },
+                       "EAST": {"action": "y", "type": "text", "text": "Y", },
+                       "SOUTH": {"action": "u", "type": "text", "text": "U", },
+                       "showAllSlices": True,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "i", "type": "text", "text": "I", },
+                       "WEST": {"action": "o", "type": "text", "text": "O", },
+                       "SOUTH": {"action": "p", "type": "text", "text": "P", },
+                       "showAllSlices": True,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "a", "type": "text", "text": "A", },
+                       "EAST": {"action": "s", "type": "text", "text": "S", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "h", "type": "text", "text": "H", },
+                       "WEST": {"action": "d", "type": "text", "text": "D", },
+                       "NORTH": {"action": "f", "type": "text", "text": "F", },
+                       "EAST": {"action": "g", "type": "text", "text": "G", },
+                       "SOUTH": {"action": "j", "type": "text", "text": "J", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "l", "type": "text", "text": "L", },
+                       "WEST": {"action": "k", "type": "text", "text": "K", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "c", "type": "text", "text": "C", },
+                       "NORTH": {"action": "z", "type": "text", "text": "Z", },
+                       "EAST": {"action": "x", "type": "text", "text": "X", },
+                       "showAllSlices": True,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "b", "type": "text", "text": "B", },
+                       "NORTH": {"action": "v", "type": "text", "text": "V", },
+                       "showAllSlices": True,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "n", "type": "text", "text": "N", },
+                       "NORTH_WEST": {"action": "m", "type": "text", "text": "M", },
+                       "showAllSlices": True,
+               },
+       },
+}
diff --git a/src/plugins/alphabet.py b/src/plugins/alphabet.py
new file mode 100644 (file)
index 0000000..1977dba
--- /dev/null
@@ -0,0 +1,26 @@
+"""
+Keyboard Origin:
+
+qwe rtyu iop
+as dfghj kl
+zxc vb nm
+
+e t i
+a h l
+c b n
+"""
+
+from __future__ import division
+
+import os
+import operator
+
+import operation
+
+import sys
+sys.path.append("../")
+import plugin_utils
+
+
+_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "alphabet.map")
+PLUGIN = plugin_utils.PieKeyboardPluginFactory("Alphabet", _MAP_FILE_PATH)
diff --git a/src/plugins/builtins.ini b/src/plugins/builtins.ini
new file mode 100644 (file)
index 0000000..48acacd
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Builtin
+version=0.1
+description=
+
+[Constants]
+name=Builtin
+version=0.1
+description=
+
+[Keyboard]
+name=Builtin
+version=0.1
+description=
diff --git a/src/plugins/builtins.map b/src/plugins/builtins.map
new file mode 100644 (file)
index 0000000..6bbb840
--- /dev/null
@@ -0,0 +1,68 @@
+{
+       "name": "Builtins",
+       "dimensions": (4, 3),
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "7", "type": "text", "text": "7", },
+                       "showAllSlices": True,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "8", "type": "text", "text": "8", },
+                       "SOUTH": {"action": "[**]", "type": "text", "text": "**", },
+                       "EAST": {"action": "[sq]", "type": "text", "text": "sq", },
+                       "WEST": {"action": "[sqrt]", "type": "text", "text": "sqrt", },
+                       "showAllSlices": False,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "9", "type": "text", "text": "9", },
+                       "showAllSlices": True,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "4", "type": "text", "text": "4", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "5", "type": "text", "text": "5", },
+                       "EAST": {"action": "[+]", "type": "text", "text": "+", },
+                       "WEST": {"action": "[-]", "type": "text", "text": "-", },
+                       "NORTH": {"action": "[*]", "type": "text", "text": "*", },
+                       "SOUTH": {"action": "[/]", "type": "text", "text": "/", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "6", "type": "text", "text": "6", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "1", "type": "text", "text": "1", },
+                       "showAllSlices": True,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "2", "type": "text", "text": "2", },
+                       "NORTH": {"action": "[abs]", "type": "text", "text": "abs", },
+                       "showAllSlices": True,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "3", "type": "text", "text": "3", },
+                       "WEST": {"action": "[!]", "type": "text", "text": "!", },
+                       "showAllSlices": True,
+               },
+               (3, 0): {
+                       "CENTER": {"action": "[push]", "type": "image", "path": "newline.png", },
+                       "NORTH": {"action": "[unpush]", "type": "text", "text": "Undo", },
+                       "NORTH_WEST": {"action": "[clear]", "type": "image", "path": "clear.png", },
+                       "WEST": {"action": "[backspace]", "type": "image", "path": "backspace.png", },
+                       "showAllSlices": False,
+               },
+               (3, 1): {
+                       "CENTER": {"action": "0", "type": "text", "text": "0", },
+                       "showAllSlices": True,
+               },
+               (3, 2): {
+                       "CENTER": {"action": ".", "type": "text", "text": ".", },
+                       "NORTH": {"action": "j", "type": "text", "text": "j", },
+                       "WEST": {"action": "[+-]", "type": "text", "text": "+/-", },
+                       "showAllSlices": True,
+               },
+       },
+}
diff --git a/src/plugins/builtins.py b/src/plugins/builtins.py
new file mode 100644 (file)
index 0000000..f57520e
--- /dev/null
@@ -0,0 +1,47 @@
+from __future__ import division
+
+import os
+import operator
+import math
+
+import operation
+
+import sys
+sys.path.append("../")
+import plugin_utils
+
+
+_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "builtins.map")
+PLUGIN = plugin_utils.PieKeyboardPluginFactory("Builtin", _MAP_FILE_PATH)
+
+addition = operation.generate_function(operator.add, "+", operation.Function.REP_INFIX, 2)
+subtraction = operation.generate_function(operator.sub, "-", operation.Function.REP_INFIX, 2)
+multiplication = operation.generate_function(operator.mul, "*", operation.Function.REP_INFIX, 2)
+trueDivision = operation.generate_function(operator.truediv, "/", operation.Function.REP_INFIX, 2)
+
+PLUGIN.register_operation("+", addition)
+PLUGIN.register_operation("-", subtraction)
+PLUGIN.register_operation("*", multiplication)
+PLUGIN.register_operation("/", trueDivision)
+
+exponentiation = operation.generate_function(operator.pow, "**", operation.Function.REP_INFIX, 2)
+abs = operation.generate_function(operator.abs, "abs", operation.Function.REP_FUNCTION, 1)
+try:
+       fact_func = math.factorial
+except AttributeError:
+       def fact_func(self, num):
+               if num <= 0:
+                       return 1
+               return num * fact_func(self, num - 1)
+factorial = operation.generate_function(fact_func, "!", operation.Function.REP_POSTFIX, 1)
+negate = operation.generate_function(operator.neg, "+-", operation.Function.REP_PREFIX, 1)
+square = operation.generate_function((lambda self, x: x ** 2), "sq", operation.Function.REP_FUNCTION, 1)
+square_root = operation.generate_function((lambda self, x: x ** 0.5), "sqrt", operation.Function.REP_FUNCTION, 1)
+
+# @todo Possibly make a graphic for this of x^y
+PLUGIN.register_operation("**", exponentiation)
+PLUGIN.register_operation("abs", abs)
+PLUGIN.register_operation("!", factorial)
+PLUGIN.register_operation("+-", negate)
+PLUGIN.register_operation("sq", square)
+PLUGIN.register_operation("sqrt", square_root)
diff --git a/src/plugins/computer.ini b/src/plugins/computer.ini
new file mode 100644 (file)
index 0000000..e0651cb
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Computer
+version=0.1
+description=
+
+[Constants]
+name=Computer
+version=0.1
+description=
+
+[Keyboard]
+name=Computer
+version=0.1
+description=
diff --git a/src/plugins/computer.map b/src/plugins/computer.map
new file mode 100644 (file)
index 0000000..a1193d3
--- /dev/null
@@ -0,0 +1,51 @@
+{
+       "name": "Computer",
+       "dimensions": (3, 3),
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "[//]", "type": "text", "text": "//", },
+                       "showAllSlices": False,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "[dec]", "type": "text", "text": "dec", },
+                       "showAllSlices": True,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "[%]", "type": "text", "text": "%", },
+                       "showAllSlices": False,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "[&]", "type": "text", "text": "and", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "[|]", "type": "text", "text": "or", },
+                       "NORTH": {"action": "[~]", "type": "text", "text": "not", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "[^]", "type": "text", "text": "xor", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "0x", "type": "text", "text": "0x", },
+                       "SOUTH": {"action": "[hex]", "type": "text", "text": "hex", },
+                       "NORTH_WEST": {"action": "a", "type": "text", "text": "A", },
+                       "WEST": {"action": "b", "type": "text", "text": "B", },
+                       "SOUTH_WEST": {"action": "c", "type": "text", "text": "C", },
+                       "NORTH_EAST": {"action": "d", "type": "text", "text": "D", },
+                       "EAST": {"action": "e", "type": "text", "text": "E", },
+                       "SOUTH_EAST": {"action": "f", "type": "text", "text": "F", },
+                       "showAllSlices": True,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "0o", "type": "text", "text": "0o", },
+                       "SOUTH": {"action": "[oct]", "type": "text", "text": "oct", },
+                       "showAllSlices": True,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "0b", "type": "text", "text": "0b", },
+                       "showAllSlices": True,
+               },
+       },
+}
diff --git a/src/plugins/computer.py b/src/plugins/computer.py
new file mode 100644 (file)
index 0000000..6f338c6
--- /dev/null
@@ -0,0 +1,43 @@
+from __future__ import division
+
+import os
+import operator
+import math
+
+import operation
+
+import sys
+sys.path.append("../")
+import plugin_utils
+
+
+_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "computer.map")
+PLUGIN = plugin_utils.PieKeyboardPluginFactory("Computer", _MAP_FILE_PATH)
+
+hex = operation.change_base(16, "hex")
+oct = operation.change_base(8, "oct")
+dec = operation.change_base(10, "dec")
+ceil = operation.generate_function(math.ceil, "ceil", operation.Function.REP_FUNCTION, 1)
+floor = operation.generate_function(math.floor, "floor", operation.Function.REP_FUNCTION, 1)
+
+PLUGIN.register_operation("hex", hex)
+PLUGIN.register_operation("oct", oct)
+PLUGIN.register_operation("dec", dec)
+PLUGIN.register_operation("ceil", ceil)
+PLUGIN.register_operation("floor", floor)
+
+floorDivision = operation.generate_function(operator.floordiv, "//", operation.Function.REP_INFIX, 2)
+modulo = operation.generate_function(operator.mod, "%", operation.Function.REP_INFIX, 2)
+
+PLUGIN.register_operation("//", floorDivision)
+PLUGIN.register_operation("%", modulo)
+
+bitAnd = operation.generate_function(operator.and_, "&", operation.Function.REP_INFIX, 2)
+bitOr = operation.generate_function(operator.or_, "|", operation.Function.REP_INFIX, 2)
+bitXor = operation.generate_function(operator.xor, "^", operation.Function.REP_INFIX, 2)
+bitInvert = operation.generate_function(operator.invert, "~", operation.Function.REP_PREFIX, 1)
+
+PLUGIN.register_operation("&", bitAnd)
+PLUGIN.register_operation("|", bitOr)
+PLUGIN.register_operation("^", bitXor)
+PLUGIN.register_operation("~", bitInvert)
diff --git a/src/plugins/trig.ini b/src/plugins/trig.ini
new file mode 100644 (file)
index 0000000..fb9111f
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Trigonometry
+version=0.1
+description=
+
+[Constants]
+name=Trigonometry
+version=0.1
+description=
+
+[Keyboard]
+name=Trigonometry
+version=0.1
+description=
diff --git a/src/plugins/trig.map b/src/plugins/trig.map
new file mode 100644 (file)
index 0000000..a524a88
--- /dev/null
@@ -0,0 +1,51 @@
+{
+       "name": "Trigonometry",
+       "dimensions": (3, 3),
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "[sinh]", "type": "text", "text": "sinh", },
+                       "SOUTH": {"action": "[asinh]", "type": "text", "text": "asinh", },
+                       "showAllSlices": False,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "[cosh]", "type": "text", "text": "cosh", },
+                       "SOUTH": {"action": "[acosh]", "type": "text", "text": "acosh", },
+                       "showAllSlices": False,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "[tanh]", "type": "text", "text": "tanh", },
+                       "SOUTH": {"action": "[atanh]", "type": "text", "text": "atanh", },
+                       "showAllSlices": False,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "[exp]", "type": "text", "text": "exp", },
+                       "NORTH": {"action": "[log]", "type": "text", "text": "log", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "pi", "type": "text", "text": "pi", },
+                       "NORTH": {"action": "e", "type": "text", "text": "e", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "[rad]", "type": "text", "text": "rad", },
+                       "NORTH": {"action": "[deg]", "type": "text", "text": "deg", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "[sin]", "type": "text", "text": "sin", },
+                       "SOUTH": {"action": "[asin]", "type": "text", "text": "asin", },
+                       "showAllSlices": False,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "[cos]", "type": "text", "text": "cos", },
+                       "SOUTH": {"action": "[acos]", "type": "text", "text": "acos", },
+                       "showAllSlices": False,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "[tan]", "type": "text", "text": "tan", },
+                       "SOUTH": {"action": "[atan]", "type": "text", "text": "atan", },
+                       "showAllSlices": False,
+               },
+       },
+}
diff --git a/src/plugins/trig.py b/src/plugins/trig.py
new file mode 100644 (file)
index 0000000..6962794
--- /dev/null
@@ -0,0 +1,80 @@
+from __future__ import division
+
+import os
+import operator
+import math
+import cmath
+
+import operation
+
+import sys
+sys.path.append("../")
+import plugin_utils
+
+
+_MAP_FILE_PATH = os.path.join(os.path.dirname(__file__), "trig.map")
+PLUGIN = plugin_utils.PieKeyboardPluginFactory("Trigonometry", _MAP_FILE_PATH)
+
+pi = operation.Constant("pi", operation.Value(math.pi, operation.render_float_eng))
+e = operation.Constant("e", operation.Value(math.e, operation.render_float_eng))
+
+def float_or_complex(float_func, complex_func):
+
+       def switching_func(self, *args, **kwd):
+               if any(
+                       isinstance(arg, complex)
+                       for arg in args
+               ):
+                       return complex_func(*args, **kwd)
+               else:
+                       return float_func(*args, **kwd)
+
+       switching_func.__name__ = complex_func.__name__
+       switching_func.__doc__ = complex_func.__doc__
+       return switching_func
+
+exp = operation.generate_function(float_or_complex(math.exp, cmath.exp), "exp", operation.Function.REP_FUNCTION, 1)
+log = operation.generate_function(float_or_complex(math.log, cmath.log), "log", operation.Function.REP_FUNCTION, 1)
+
+PLUGIN.register_operation("exp", exp)
+PLUGIN.register_operation("log", log)
+
+cos = operation.generate_function(float_or_complex(math.cos, cmath.cos), "cos", operation.Function.REP_FUNCTION, 1)
+acos = operation.generate_function(float_or_complex(math.acos, cmath.acos), "acos", operation.Function.REP_FUNCTION, 1)
+sin = operation.generate_function(float_or_complex(math.sin, cmath.sin), "sin", operation.Function.REP_FUNCTION, 1)
+asin = operation.generate_function(float_or_complex(math.asin, cmath.asin), "asin", operation.Function.REP_FUNCTION, 1)
+tan = operation.generate_function(float_or_complex(math.tan, cmath.tan), "tan", operation.Function.REP_FUNCTION, 1)
+atan = operation.generate_function(float_or_complex(math.atan, cmath.atan), "atan", operation.Function.REP_FUNCTION, 1)
+
+PLUGIN.register_operation("cos", cos)
+PLUGIN.register_operation("acos", acos)
+PLUGIN.register_operation("sin", sin)
+PLUGIN.register_operation("asin", asin)
+PLUGIN.register_operation("tan", tan)
+PLUGIN.register_operation("atan", atan)
+
+cosh = operation.generate_function(float_or_complex(math.cosh, cmath.cosh), "cosh", operation.Function.REP_FUNCTION, 1)
+acosh = operation.generate_function(cmath.acosh, "acosh", operation.Function.REP_FUNCTION, 1)
+sinh = operation.generate_function(float_or_complex(math.sinh, cmath.sinh), "sinh", operation.Function.REP_FUNCTION, 1)
+asinh = operation.generate_function(cmath.asinh, "asinh", operation.Function.REP_FUNCTION, 1)
+tanh = operation.generate_function(float_or_complex(math.tanh, cmath.tanh), "tanh", operation.Function.REP_FUNCTION, 1)
+atanh = operation.generate_function(cmath.atanh, "atanh", operation.Function.REP_FUNCTION, 1)
+
+PLUGIN.register_operation("cosh", cosh)
+PLUGIN.register_operation("acosh", acosh)
+PLUGIN.register_operation("sinh", sinh)
+PLUGIN.register_operation("asinh", asinh)
+PLUGIN.register_operation("tanh", tanh)
+PLUGIN.register_operation("atanh", atanh)
+
+deg = operation.generate_function(math.degrees, "deg", operation.Function.REP_FUNCTION, 1)
+rad = operation.generate_function(math.radians, "rad", operation.Function.REP_FUNCTION, 1)
+
+PLUGIN.register_operation("deg", deg)
+PLUGIN.register_operation("rad", rad)
+
+# In 2.6
+#phase = operation.generate_function(cmath.phase, "phase", operation.Function.REP_FUNCTION, 1)
+#polar = operation.generate_function(cmath.polar, "polar", operation.Function.REP_FUNCTION, 1)
+#rect = operation.generate_function(cmath.rect, "rect", operation.Function.REP_FUNCTION, 1)
+
index d12305a..1469e19 100755 (executable)
@@ -12,17 +12,22 @@ import constants
 
 
 __appname__ = constants.__app_name__
-__description__ = """REPLACEME
-REPLACEME
+__description__ = """A Touch Screen Optimized RPN Calculator using Pie Menus
 .
-Homepage: REPLACEME
+RPN: Stack based math, come on it is fun
+.
+Pie Menus: Press them or press-drag them
+.
+History: Its such a drag, so drag them around, delete them, etc
+.
+Homepage: http://ejpi.garage.maemo.org/
 """
 __author__ = "Ed Page"
 __email__ = "eopage@byu.net"
 __version__ = constants.__version__
 __build__ = constants.__build__
 __changelog__ = """
-REPLACEME
+* Port to QT
 """.strip()
 
 
@@ -66,7 +71,7 @@ def build_package(distribution):
        p = py2deb.Py2deb(__appname__)
        p.prettyName = constants.__pretty_app_name__
        p.description = __description__
-       p.bugTracker = "REPLACEME"
+       p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=ejpi"
        p.author = __author__
        p.mail = __email__
        p.license = "lgpl"
@@ -79,15 +84,13 @@ def build_package(distribution):
        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": "math",
+               "diablo": "user/science",
+               "fremantle": "user/science",
        }[distribution]
        p.arch = "all"
        p.urgency = "low"
@@ -95,11 +98,10 @@ def build_package(distribution):
        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": "26x26-ejpi.png",
+               "diablo": "26x26-ejpi.png",
+               "fremantle": "64x64-ejpi.png", # Fremantle natively uses 48x48
        }[distribution]
        p["/opt/%s/bin" % constants.__appname__] = [ "%s.py" % constants.__appname__ ]
        for relPath, files in unflatten_files(find_files("src", ".")).iteritems():
@@ -111,9 +113,9 @@ def build_package(distribution):
                        for (oldName, newName) in files
                )
        p["/usr/share/applications/hildon"] = ["%s.desktop" % constants.__appname__]
-       p["/usr/share/icons/hicolor/26x26/hildon"] = ["%s.png" % constants.__appname__]
-       p["/usr/share/icons/hicolor/64x64/hildon"] = ["%s.png" % constants.__appname__]
-       p["/usr/share/icons/hicolor/scalable/hildon"] = ["%s.png" % constants.__appname__]
+       p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-ejpi.png|ejpi.png"]
+       p["/usr/share/icons/hicolor/64x64/hildon"] = ["64x64-ejpi.png|ejpi.png"]
+       p["/usr/share/icons/hicolor/scalable/hildon"] = ["scale-ejpi.png|ejpi.png"]
 
        if distribution == "debian":
                print p
diff --git a/support/ejpi.desktop b/support/ejpi.desktop
new file mode 100644 (file)
index 0000000..6c3e232
--- /dev/null
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Name=ejpi
+Exec=/usr/bin/run-standalone.sh /usr/bin/ejpi.py
+Icon=ejpi
diff --git a/support/icons/26.png b/support/icons/26.png
new file mode 100644 (file)
index 0000000..35327e9
Binary files /dev/null and b/support/icons/26.png differ
diff --git a/support/icons/64.png b/support/icons/64.png
new file mode 100644 (file)
index 0000000..96503e5
Binary files /dev/null and b/support/icons/64.png differ
diff --git a/support/icons/scalable.png b/support/icons/scalable.png
new file mode 100644 (file)
index 0000000..e65e834
Binary files /dev/null and b/support/icons/scalable.png differ
index a2b137b..8f58190 100644 (file)
@@ -1,36 +1,37 @@
 <html>
        <head>
-               <title></title>
+               <title>e^(j pi) + 1 = 0 - RPN Calculator for Maemo</title>
        </head>
        <body>
                <h1>
+                       e^(j pi) + 1 = 0
                </h1>
-               <h2></h2>
-               <h3><a href="index.html">[About]</a> <a href="screenshots.html">[Screenshots]</a> <a href="download.html">[Download]</a>
+               <h2>An RPN Calculator for Maemo</h2>
+               <h3><a href="index.html">[About]</a> <a href="download.html">[Download]</a>
                <h3>Download</h3>
 
                <h4>Packages</h4>
-               <p>Maemo 5 and Later: Go to the application installer, enable Extras, and download</p>
+               <p>Maemo 5 and Later: Go to the application installer, enable Extras, and download ejpi (Note: technically its still in extras-testing because people haven't been reviewing it)</p>
 
-               <p>Maemo 4.1 and Earlier: Add the <a href="http://wiki.maemo.org/Extras">Extras Repository</a> and check the App Manager</p>
+               <p>Maemo 4.1 and Earlier: Add the <a href="http://wiki.maemo.org/Extras">Extras Repository</a> and check the App Manager for DialCentral</p>
 
-               <p>Linux: <a href="">.deb files</a></p>
+               <p>Linux: <a href="ejpi.deb">.deb files</a></p>
 
                <h3>Development</h3>
                <h4>Source</h4>
-               <p>For the most up to date version check out <a href="">git</a>.
+               <p>For the most up to date version check out <a href="https://garage.maemo.org/scm/?group_id=887">git</a>.
                </p>
                <p>Requires</p>
                <ul>
                        <li>PyGTK / Glade</li>
-                       <li>Python bindings for hildon, osso, dbus, and conic</li>
+                       <li>Python bindings for hildon, and osso</li>
                </ul>
 
                <h4>Bugs</h4>
 
-               <p>Discuss your issue on <a href="">Maemo.Org Talk</a></p>
+               <p>Discuss your issue on <a href="http://talk.maemo.org/showthread.php?p=260919#post260919">Maemo.Org Talk</a></p>
 
-               <p>File a <a href="">bug report</a></p>
-               <p>View  <a href="">existing bug reports</a></p>
+               <p>File a <a href="https://bugs.maemo.org/enter_bug.cgi?product=ejpi">bug report</a> against Extras-&gt;ejpi</p>
+               <p>View  <a href="https://bugs.maemo.org/buglist.cgi?query_format=specific&order=relevance+desc&bug_status=__open__&product=ejpi&content=">existing bug reports</a></p>
        </body>
 </html>
diff --git a/www/ejpi.deb b/www/ejpi.deb
new file mode 100644 (file)
index 0000000..391c246
Binary files /dev/null and b/www/ejpi.deb differ
diff --git a/www/images/screenshot-0.9.0-ubuntu.jpg b/www/images/screenshot-0.9.0-ubuntu.jpg
new file mode 100644 (file)
index 0000000..9d864b7
Binary files /dev/null and b/www/images/screenshot-0.9.0-ubuntu.jpg differ
index b794594..abcbeb0 100644 (file)
@@ -1,30 +1,54 @@
 <html>
        <head>
-               <title></title>
+               <title>e^(j pi) + 1 = 0 - RPN Calculator for Maemo</title>
        </head>
        <body>
                <h1>
+                       e^(j pi) + 1 = 0
                </h1>
-               <h2></h2>
+               <h2>An RPN Calculator for Maemo</h2>
+               <h3><a href="index.html">[About]</a> <a href="download.html">[Download]</a>
 
-               <h3><a href="">[About]</a> <a href="screenshots.html">[Screenshots]</a> <a href="download.html">[Download]</a>
+               <h3>Documentation</h3>
 
                <h3>About</h3>
                <p>
+               The interface focuses on history manipulation and quick access to functionality through something like pie menus. It takes advantage of pythons type system under the hood for support of integer, float, and complex operations. For integers it tracks the base used and tries to persist that through the operations.
                </p>
 
+               <p>The name (e^(j pi) + 1 = 0, or shortened ejpi) is <a href="http://en.wikipedia.org/wiki/Eulers_identity">Euler's Identity</a>.  If you do not understand the beauty of it, I recommend reading the article.  Why "j"?  I come from an <a href="http://en.wikipedia.org/wiki/Imaginary_unit#Alternative_notations">engineering background</a>.  Yeah, it put a smile on my face when I learned Python and found out it used "j".</p>
 
-               <p>This has been tested on Maemo 5, Maemo 4.1, and Ubuntu 9.04.  Go check out some <a href="">reviews</a>  or join the <a href="">conversation</a></p>
+               <p><a href="http://en.wikipedia.org/wiki/Reverse_Polish_notation">RPN</a>?  I've just always been a fan of its simplicity to understand, simplicity to code, uniformity in action, less mental tracking, etc.  Yes, I own several HP calculators.</p>
 
-               <p>This is Free Software and available under the <a href="http://www.gnu.org/licenses/lgpl-2.1.html">LGPLv2.1</a>.
+               <p>ejpi has been tested on Ubuntu 9.05, Maemo 5, and Maemo 4.1</p>
 
-               <h4>Features</h4>
+               <p>ejpi is Free Software and available under the <a href="http://www.gnu.org/licenses/lgpl-2.1.html">LGPLv2.1</a>.
 
-               <p>(See <a href="">Maemo.Org Talk</a>)</p>
+               <h4>Features</h4>
+               <p>ejpi v0.9.4 (See <a href="http://talk.maemo.org/showthread.php?p=260919#post260919">t.m.o Thread</a>)</p>
 
                <ul>
-                       <li></li>
+                       <li>Pie Menu like system for buttons: Quick access to functionality without cluttering the display</li>
+                       <li>History manipulation: Just drag the items around, click on the "x" to delete individual items, click on the entered equation or the result to copy it, etc</li>
+                       <li>Persistent history</li>
+                       <li>Plugin system:  Add all the constants or custom equations you want</li>
+                       <li>Tracks numbers as integers (with base), floating point numbers, and complex numbers</li>
+                       <li>Comes by default with some basic operations, logic operations, and trig</li>
+                       <li>Keyboard shortcuts:
+                       <ul>
+                               <li>Ctrl+enter to toggle fullscreen</li>
+                               <li>Ctrl+l to copy debug logs to the clipboard</li>
+                       </ul>
+                       </li>
                </ul>
 
+               <h4>Sample Screenshots</h4>
+
+               <div>
+                       <p><img src="./images/screenshot-0.9.0-ubuntu.jpg" alt="Screenshot of ejpi v0.9.0 on Ubuntu"/></p>
+                       <p>ejpi v0.9.0 on Ubuntu</p>
+                       <p>Notice the tracking of base through the operations.  Just prefix your number with the appropriate prefix (see poorly named "Computer" tab)</p>
+                       <p>Notice Each square has a value in the middle and is sometimes surrounded by others.  Just clicking on the button will cause the center item's operation to be performed.  Click and drag will in the direction of any of the other symbols will cause that operation to occur.  Note: Not all click-and-drag symbols are shown by default so explore a bit.</p>
+               </div>
        </body>
 </html>