First pass at packaging for Harmattan
authorEd Page <eopage@byu.net>
Wed, 3 Aug 2011 00:12:31 +0000 (19:12 -0500)
committerEd Page <eopage@byu.net>
Thu, 4 Aug 2011 03:08:50 +0000 (22:08 -0500)
123 files changed:
Makefile
data/ejpi-base.svg [new file with mode: 0644]
data/ejpi.colors [new file with mode: 0644]
data/ejpi.desktop [new file with mode: 0644]
data/ejpi.png [new file with mode: 0644]
data/ejpi.svg [new file with mode: 0644]
ejpi-calc [new file with mode: 0755]
ejpi/__init__.py [new file with mode: 0644]
ejpi/constants.py [new file with mode: 0644]
ejpi/ejpi_qt.py [new file with mode: 0755]
ejpi/history.py [new file with mode: 0644]
ejpi/operation.py [new file with mode: 0644]
ejpi/plugin_utils.py [new file with mode: 0644]
ejpi/plugins/__init__.py [new file with mode: 0644]
ejpi/plugins/alphabet.ini [new file with mode: 0644]
ejpi/plugins/alphabet.py [new file with mode: 0644]
ejpi/plugins/builtins.ini [new file with mode: 0644]
ejpi/plugins/builtins.py [new file with mode: 0644]
ejpi/plugins/computer.ini [new file with mode: 0644]
ejpi/plugins/computer.py [new file with mode: 0644]
ejpi/plugins/entry.ini [new file with mode: 0644]
ejpi/plugins/entry.py [new file with mode: 0644]
ejpi/plugins/images/alphabet.png [new file with mode: 0644]
ejpi/plugins/images/alt.png [new file with mode: 0644]
ejpi/plugins/images/arrows.png [new file with mode: 0644]
ejpi/plugins/images/backspace.png [new file with mode: 0644]
ejpi/plugins/images/builtins.png [new file with mode: 0644]
ejpi/plugins/images/clear.png [new file with mode: 0644]
ejpi/plugins/images/computer.png [new file with mode: 0644]
ejpi/plugins/images/control.png [new file with mode: 0644]
ejpi/plugins/images/newline.png [new file with mode: 0644]
ejpi/plugins/images/shift.png [new file with mode: 0644]
ejpi/plugins/images/space.png [new file with mode: 0644]
ejpi/plugins/images/super.png [new file with mode: 0644]
ejpi/plugins/images/symbols.dia [new file with mode: 0644]
ejpi/plugins/images/tab.png [new file with mode: 0644]
ejpi/plugins/images/trig.png [new file with mode: 0644]
ejpi/plugins/trig.ini [new file with mode: 0644]
ejpi/plugins/trig.py [new file with mode: 0644]
ejpi/qhistory.py [new file with mode: 0644]
ejpi/util/__init__.py [new file with mode: 0644]
ejpi/util/algorithms.py [new file with mode: 0644]
ejpi/util/concurrent.py [new file with mode: 0644]
ejpi/util/coroutines.py [new file with mode: 0755]
ejpi/util/go_utils.py [new file with mode: 0644]
ejpi/util/gtk_utils.py [new file with mode: 0644]
ejpi/util/hildonize.py [new file with mode: 0644]
ejpi/util/io.py [new file with mode: 0644]
ejpi/util/linux.py [new file with mode: 0644]
ejpi/util/misc.py [new file with mode: 0644]
ejpi/util/overloading.py [new file with mode: 0644]
ejpi/util/qml_utils.py [new file with mode: 0644]
ejpi/util/qore_utils.py [new file with mode: 0755]
ejpi/util/qt_compat.py [new file with mode: 0644]
ejpi/util/qtpie.py [new file with mode: 0755]
ejpi/util/qtpieboard.py [new file with mode: 0755]
ejpi/util/qui_utils.py [new file with mode: 0644]
ejpi/util/qwrappers.py [new file with mode: 0644]
ejpi/util/time_utils.py [new file with mode: 0644]
ejpi/util/tp_utils.py [new file with mode: 0644]
setup.py [new file with mode: 0755]
src [new symlink]
src/__init__.py [deleted file]
src/constants.py [deleted file]
src/ejpi.py [deleted file]
src/ejpi_qt.py [deleted file]
src/history.py [deleted file]
src/operation.py [deleted file]
src/plugin_utils.py [deleted file]
src/plugins/__init__.py [deleted file]
src/plugins/alphabet.ini [deleted file]
src/plugins/alphabet.py [deleted file]
src/plugins/builtins.ini [deleted file]
src/plugins/builtins.py [deleted file]
src/plugins/computer.ini [deleted file]
src/plugins/computer.py [deleted file]
src/plugins/entry.ini [deleted file]
src/plugins/entry.py [deleted file]
src/plugins/images/alphabet.png [deleted file]
src/plugins/images/alt.png [deleted file]
src/plugins/images/arrows.png [deleted file]
src/plugins/images/backspace.png [deleted file]
src/plugins/images/builtins.png [deleted file]
src/plugins/images/clear.png [deleted file]
src/plugins/images/computer.png [deleted file]
src/plugins/images/control.png [deleted file]
src/plugins/images/newline.png [deleted file]
src/plugins/images/shift.png [deleted file]
src/plugins/images/space.png [deleted file]
src/plugins/images/super.png [deleted file]
src/plugins/images/symbols.dia [deleted file]
src/plugins/images/tab.png [deleted file]
src/plugins/images/trig.png [deleted file]
src/plugins/trig.ini [deleted file]
src/plugins/trig.py [deleted file]
src/qhistory.py [deleted file]
src/util/__init__.py [deleted file]
src/util/algorithms.py [deleted file]
src/util/concurrent.py [deleted file]
src/util/coroutines.py [deleted file]
src/util/go_utils.py [deleted file]
src/util/gtk_utils.py [deleted file]
src/util/hildonize.py [deleted file]
src/util/io.py [deleted file]
src/util/linux.py [deleted file]
src/util/misc.py [deleted file]
src/util/overloading.py [deleted file]
src/util/qore_utils.py [deleted file]
src/util/qt_compat.py [deleted file]
src/util/qtpie.py [deleted file]
src/util/qtpieboard.py [deleted file]
src/util/qui_utils.py [deleted file]
src/util/qwrappers.py [deleted file]
src/util/time_utils.py [deleted file]
src/util/tp_utils.py [deleted file]
support/builddeb.py [deleted file]
support/ejpi.desktop [deleted file]
support/fake_py2deb.py [deleted file]
support/icons/26.png [deleted file]
support/icons/64.png [deleted file]
support/icons/scalable.png [deleted file]
support/py2deb.py [deleted file]
support/scale.py [new file with mode: 0755]

index c239d05..d62b1ba 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,11 +1,12 @@
 PROJECT_NAME=ejpi
-SOURCE_PATH=src
+PACKAGE_NAME=$(PROJECT_NAME)
+SOURCE_PATH=$(PACKAGE_NAME)
 SOURCE=$(shell find $(SOURCE_PATH) -iname "*.py")
-PROGRAM=$(SOURCE_PATH)/$(PROJECT_NAME).py
-DATA_TYPES=*.ini *.png
-DATA=$(foreach type, $(DATA_TYPES), $(shell find $(SOURCE_PATH) -iname "$(type)"))
+PROGRAM=$(PROJECT_NAME)
 OBJ=$(SOURCE:.py=.pyc)
-BUILD_PATH=./build
+DIST_BASE_PATH=./dist
+ICON_SIZES=22 28 32 48
+ICONS=$(foreach size, $(ICON_SIZES), data/icons/$(size)/$(PROJECT_NAME).png)
 TAG_FILE=~/.ctags/$(PROJECT_NAME).tags
 TODO_FILE=./TODO
 
@@ -25,7 +26,7 @@ CTAGS=ctags-exuberant
 all: test
 
 run: $(OBJ)
-       $(SOURCE_PATH)/$(PROJECT_NAME).py
+       $(PROGRAM)
 
 profile: $(OBJ)
        $(PROFILE_GEN) $(PROGRAM)
@@ -37,36 +38,22 @@ debug: $(OBJ)
 test: $(OBJ)
        $(UNIT_TEST)
 
-package: $(OBJ)
-       rm -Rf $(BUILD_PATH)
-
-       mkdir -p $(BUILD_PATH)/generic
-       cp $(SOURCE_PATH)/constants.py  $(BUILD_PATH)/generic
-       cp $(SOURCE_PATH)/$(PROJECT_NAME).py  $(BUILD_PATH)/generic
-       $(foreach file, $(DATA), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
-       $(foreach file, $(SOURCE), cp $(file) $(BUILD_PATH)/generic/$(subst /,-,$(file)) ; )
-       cp support/$(PROJECT_NAME).desktop $(BUILD_PATH)/generic
-       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
-
-       mkdir -p $(BUILD_PATH)/diablo
-       cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/diablo
-       cd $(BUILD_PATH)/diablo ; python builddeb.py diablo
-       mkdir -p $(BUILD_PATH)/fremantle
-       cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/fremantle
-       cd $(BUILD_PATH)/fremantle ; python builddeb.py fremantle
-       mkdir -p $(BUILD_PATH)/debian
-       cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/debian
-       cd $(BUILD_PATH)/debian ; python builddeb.py debian
+setup.fremantle.py: setup.py
+       cog.py -D desktopFilePath=/usr/share/applications/hildon -o ./setup.fremantle.py ./setup.py
+       chmod +x ./setup.fremantle.py
+
+setup.harmattan.py: setup.py
+       cog.py -D desktopFilePath=/usr/share/applications -o ./setup.harmattan.py ./setup.py
+       chmod +x ./setup.harmattan.py
+
+package: $(OBJ) $(ICONS) setup.harmattan.py setup.fremantle.py
+       ./setup.fremantle.py sdist_diablo -d $(DIST_BASE_PATH)_diablo
+       ./setup.fremantle.py sdist_fremantle -d $(DIST_BASE_PATH)_fremantle
+       ./setup.harmattan.py sdist_harmattan -d $(DIST_BASE_PATH)_harmattan
 
 upload:
-       dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes
-       dput diablo-extras-builder $(BUILD_PATH)/diablo/$(PROJECT_NAME)*.changes
-       cp $(BUILD_PATH)/debian/*.deb ./www/$(PROJECT_NAME).deb
+       dput diablo-extras-builder $(DIST_BASE_PATH)_diablo/$(PROJECT_NAME)*.changes
+       dput fremantle-extras-builder $(DIST_BASE_PATH)_fremantle/$(PROJECT_NAME)*.changes
 
 lint: $(OBJ)
        $(foreach file, $(SOURCE), $(LINT) $(file) ; )
@@ -77,18 +64,23 @@ todo: $(TODO_FILE)
 
 clean:
        rm -Rf $(OBJ)
-       rm -Rf $(BUILD_PATH)
+       rm -f $(ICONS)
        rm -Rf $(TODO_FILE)
+       rm -f setup.harmattan.py setup.fremantle.py
+       rm -Rf $(DIST_BASE_PATH)_diablo build
+       rm -Rf $(DIST_BASE_PATH)_fremantle build
+       rm -Rf $(DIST_BASE_PATH)_harmattan build
 
-distclean:
-       rm -Rf $(OBJ)
-       rm -Rf $(BUILD_PATH)
-       rm -Rf $(TAG_FILE)
+distclean: clean
        find $(SOURCE_PATH) -name "*.*~" | xargs rm -f
        find $(SOURCE_PATH) -name "*.swp" | xargs rm -f
        find $(SOURCE_PATH) -name "*.bak" | xargs rm -f
        find $(SOURCE_PATH) -name ".*.swp" | xargs rm -f
 
+$(ICONS): data/$(PROJECT_NAME).png support/scale.py
+       mkdir -p $(dir $(ICONS))
+       $(foreach size, $(ICON_SIZES), support/scale.py --input data/$(PROJECT_NAME).png --output data/icons/$(size)/$(PROJECT_NAME).png --size $(size) ; )
+
 $(TAG_FILE): $(OBJ)
        mkdir -p $(dir $(TAG_FILE))
        $(CTAGS) -o $(TAG_FILE) $(SOURCE)
diff --git a/data/ejpi-base.svg b/data/ejpi-base.svg
new file mode 100644 (file)
index 0000000..4f8edbc
--- /dev/null
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="116.32533"
+   height="158.89505"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="ejpi-base.svg">
+  <defs
+     id="defs4">
+    <pattern
+       inkscape:collect="always"
+       xlink:href="#pattern3095"
+       id="pattern4605"
+       patternTransform="matrix(0.77039422,0,0,0.77039422,-396.55415,198.23649)" />
+    <pattern
+       patternUnits="userSpaceOnUse"
+       width="137.68516"
+       height="102.49221"
+       patternTransform="translate(-259.51537,281.93555)"
+       id="pattern3095">
+      <path
+         transform="matrix(0.34491466,0,0,0.34491466,146.49126,-63.454854)"
+         d="m -90.887574,478.18354 -29.779156,-40.85689 -50.53918,1.36897 29.65494,-40.94713 -16.91943,-47.64259 48.10692,15.55016 40.082399,-30.8137 0.07677,50.55766 41.691717,28.59868 -48.059472,15.69618 z"
+         inkscape:randomized="0"
+         inkscape:rounded="0"
+         inkscape:flatsided="false"
+         sodipodi:arg2="2.0277357"
+         sodipodi:arg1="1.3994172"
+         sodipodi:r2="38.066818"
+         sodipodi:r1="76.133636"
+         sodipodi:cy="403.16522"
+         sodipodi:cx="-103.87151"
+         sodipodi:sides="5"
+         id="path3075"
+         style="color:#000000;fill:none;stroke:#ffffff;stroke-width:2.65499997;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         sodipodi:type="star" />
+      <path
+         transform="matrix(0.56327644,0,0,0.56327644,97.922089,-187.09579)"
+         d="m -90.887574,478.18354 -29.779156,-40.85689 -50.53918,1.36897 29.65494,-40.94713 -16.91943,-47.64259 48.10692,15.55016 40.082399,-30.8137 0.07677,50.55766 41.691717,28.59868 -48.059472,15.69618 z"
+         inkscape:randomized="0"
+         inkscape:rounded="0"
+         inkscape:flatsided="false"
+         sodipodi:arg2="2.0277357"
+         sodipodi:arg1="1.3994172"
+         sodipodi:r2="38.066818"
+         sodipodi:r1="76.133636"
+         sodipodi:cy="403.16522"
+         sodipodi:cx="-103.87151"
+         sodipodi:sides="5"
+         id="path3075-6"
+         style="color:#000000;fill:none;stroke:#ffffff;stroke-width:2.65499997;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         sodipodi:type="star" />
+    </pattern>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.8284271"
+     inkscape:cx="26.687438"
+     inkscape:cy="79.746747"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0"
+     inkscape:window-width="1920"
+     inkscape:window-height="1176"
+     inkscape:window-x="0"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3056"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-209.62387,-366.6245)">
+    <g
+       transform="matrix(1.1477086,0,0,1.117677,245.5637,378.94339)"
+       style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+       id="flowRoot2996">
+      <path
+         d="m 22.372404,81.427841 0,4.169426 -33.334985,0 c 0.345147,3.433662 1.5532231,6.008893 3.6242351,7.725702 2.070966,1.716832 4.965041,2.575243 8.682229,2.575235 3.000248,8e-6 6.066905,-0.449634 9.1999779,-1.348932 3.159546,-0.926528 6.398786,-2.316336 9.717726,-4.169428 l 0,11.281976 c -3.372043,1.30806 -6.744037,2.2891 -10.115992,2.94313 -3.3720349,0.68128 -6.7440289,1.02192 -10.11599386,1.02192 -8.07157304,0 -14.35091714,-2.09834 -18.83805014,-6.29502 -4.460601,-4.223921 -6.690896,-10.137416 -6.690893,-17.740503 -3e-6,-7.466787 2.190466,-13.339405 6.571413,-17.617873 4.407478,-4.278388 10.4611391,-6.417601 18.16099514,-6.417647 7.00947196,4.6e-5 12.61176186,2.16651 16.80688686,6.499399 4.221585,4.332971 6.3324,10.123835 6.332451,17.372615 M 7.7161621,76.563508 c -3.7e-5,-2.779586 -0.796569,-5.01418 -2.389605,-6.703784 -1.56655,-1.716786 -3.624262,-2.575197 -6.17314296,-2.575235 -2.76134704,3.8e-5 -5.00491904,0.803946 -6.73072004,2.411728 -1.725846,1.5806 -2.8011681,3.869695 -3.2259681,6.867291 l 18.5194361,0"
+         id="path3032"
+         inkscape:connector-curvature="0" />
+    </g>
+    <g
+       transform="matrix(1.3506797,0,0,1.5025343,262.91872,344.76678)"
+       style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold"
+       id="flowRoot2996-9">
+      <path
+         d="m 6.546875,36.869966 6.992188,0 0,21.484375 c -1.1e-5,2.929685 -0.703135,5.162756 -2.109375,6.699219 -1.406257,1.549472 -3.4440157,2.32421 -6.1132818,2.324219 l -3.4570312,0 0,-4.589844 1.2109375,0 c 1.3281238,-4e-6 2.2395812,-0.299483 2.734375,-0.898437 0.4947886,-0.598961 0.7421841,-1.777345 0.7421875,-3.535157 l 0,-21.484375 m 0,-8.515625 6.992188,0 0,5.703125 -6.992188,0 0,-5.703125"
+         id="path3035"
+         inkscape:connector-curvature="0" />
+      <path
+         d="m 18.617187,36.869966 27.597657,0 0,5 -3.828125,0 0,9.804688 c -2.6e-5,1.679693 0.156224,2.727869 0.46875,3.144531 0.325494,0.40365 0.852838,0.605472 1.582031,0.605469 0.742159,3e-6 1.321586,-0.02604 1.738281,-0.07813 l 0,3.671875 c -0.80732,0.32552 -2.591172,0.48828 -5.351562,0.488281 -0.872419,-1e-6 -1.731793,-0.123698 -2.578125,-0.371094 -1.354187,-0.403645 -2.220071,-1.197915 -2.597657,-2.382812 -0.169289,-0.559893 -0.253924,-1.412757 -0.253906,-2.558594 l 0,-12.324219 -5.957031,0 0,16.875 -6.992188,0 0,-16.875 -3.828125,0 0,-5"
+         id="path3037"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/data/ejpi.colors b/data/ejpi.colors
new file mode 100644 (file)
index 0000000..662edf5
--- /dev/null
@@ -0,0 +1,3 @@
+Your icon's dominant color is #808080
+A suggested disabled color is #dadada
+A suggested pressed color is #585858
diff --git a/data/ejpi.desktop b/data/ejpi.desktop
new file mode 100644 (file)
index 0000000..703cec5
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Name=ejpi
+Comment=RPN Calculator
+Exec=/usr/bin/run-standalone.sh /opt/ejpi/bin/ejpi.py
+Icon=ejpi
+Categories=Engineering;Science;Education;Utility;Qt;
+Type=Application
+Encoding=UTF-8
+Version=1.0
+X-Osso-Type=application/x-executable
diff --git a/data/ejpi.png b/data/ejpi.png
new file mode 100644 (file)
index 0000000..c7012bb
Binary files /dev/null and b/data/ejpi.png differ
diff --git a/data/ejpi.svg b/data/ejpi.svg
new file mode 100644 (file)
index 0000000..f357c4d
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="80" width="80">
+ <defs>
+  <linearGradient id="hicg_overlay_grad" gradientUnits="userSpaceOnUse" x1="39.9995" y1="5.1816" x2="39.9995" y2="58.8019">
+   <stop offset="0" style="stop-color:#FFFFFF"/>
+   <stop offset="1" style="stop-color:#000000"/>
+  </linearGradient>
+  <filter id="hicg_drop_shadow">
+    <feOffset in="SourceAlpha" dx="0" dy="4"/>
+    <feGaussianBlur stdDeviation="4"/>
+       <feColorMatrix type="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 0.5 0" result="shadow"/>
+    <feBlend in="SourceGraphic" in2="shadow" mode="normal"/>
+  </filter>
+ </defs>
+ <g>
+  <path id="hicg_background" fill="#808080" d="M79,40c0,28.893-10.105,39-39,39S1,68.893,1,40C1,11.106,11.105,1,40,1S79,11.106,79,40z"/>
+  <path id="hicg_highlight" fill="#fff" opacity="0.25" d="M39.999,1C11.105,1,1,11.106,1,40c0,28.893,10.105,39,38.999,39   C68.896,79,79,68.893,79,40C79,11.106,68.896,1,39.999,1z M39.999,78.025C11.57,78.025,1.976,68.43,1.976,40   c0-28.429,9.595-38.024,38.023-38.024c28.43,0,38.024,9.596,38.024,38.024C78.023,68.43,68.429,78.025,39.999,78.025z"/>
+  <path id="hicg_overlay" opacity="0.4" fill="url(#hicg_overlay_grad)" d="M78.977,40c0,28.893-10.1,39-38.977,39S1.023,68.893,1.023,40c0-28.894,10.1-39,38.977-39S78.977,11.106,78.977,40z"/>
+ </g>
+<g xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" transform="translate(19.443037974684, 12) scale(0.35443037974684)">
+  <defs xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" id="defs4">
+    <pattern inkscape:collect="always" xlink:href="#pattern3095" id="pattern4605" patternTransform="matrix(0.77039422,0,0,0.77039422,-396.55415,198.23649)"/>
+    <pattern patternUnits="userSpaceOnUse" width="137.68516" height="102.49221" patternTransform="translate(-259.51537,281.93555)" id="pattern3095">
+      <path transform="matrix(0.34491466,0,0,0.34491466,146.49126,-63.454854)" d="m -90.887574,478.18354 -29.779156,-40.85689 -50.53918,1.36897 29.65494,-40.94713 -16.91943,-47.64259 48.10692,15.55016 40.082399,-30.8137 0.07677,50.55766 41.691717,28.59868 -48.059472,15.69618 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="false" sodipodi:arg2="2.0277357" sodipodi:arg1="1.3994172" sodipodi:r2="38.066818" sodipodi:r1="76.133636" sodipodi:cy="403.16522" sodipodi:cx="-103.87151" sodipodi:sides="5" id="path3075" style="color:#000000;fill:none;stroke:#ffffff;stroke-width:2.65499997;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" sodipodi:type="star"/>
+      <path transform="matrix(0.56327644,0,0,0.56327644,97.922089,-187.09579)" d="m -90.887574,478.18354 -29.779156,-40.85689 -50.53918,1.36897 29.65494,-40.94713 -16.91943,-47.64259 48.10692,15.55016 40.082399,-30.8137 0.07677,50.55766 41.691717,28.59868 -48.059472,15.69618 z" inkscape:randomized="0" inkscape:rounded="0" inkscape:flatsided="false" sodipodi:arg2="2.0277357" sodipodi:arg1="1.3994172" sodipodi:r2="38.066818" sodipodi:r1="76.133636" sodipodi:cy="403.16522" sodipodi:cx="-103.87151" sodipodi:sides="5" id="path3075-6" style="color:#000000;fill:none;stroke:#ffffff;stroke-width:2.65499997;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" sodipodi:type="star"/>
+    </pattern>
+  </defs>
+  <sodipodi:namedview xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.8284271" inkscape:cx="26.687438" inkscape:cy="79.746747" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="false" showguides="true" inkscape:guide-bbox="true" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:window-width="1920" inkscape:window-height="1176" inkscape:window-x="0" inkscape:window-y="24" inkscape:window-maximized="1">
+    <inkscape:grid type="xygrid" id="grid3056" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true"/>
+  </sodipodi:namedview>
+  <metadata xmlns="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" id="metadata7">
+    <rdf:RDF>
+      <cc:Work rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+        <dc:title/>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-209.62387,-366.6245)">
+    <g transform="matrix(1.1477086,0,0,1.117677,245.5637,378.94339)" style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold" id="flowRoot2996">
+      <path d="m 22.372404,81.427841 0,4.169426 -33.334985,0 c 0.345147,3.433662 1.5532231,6.008893 3.6242351,7.725702 2.070966,1.716832 4.965041,2.575243 8.682229,2.575235 3.000248,8e-6 6.066905,-0.449634 9.1999779,-1.348932 3.159546,-0.926528 6.398786,-2.316336 9.717726,-4.169428 l 0,11.281976 c -3.372043,1.30806 -6.744037,2.2891 -10.115992,2.94313 -3.3720349,0.68128 -6.7440289,1.02192 -10.11599386,1.02192 -8.07157304,0 -14.35091714,-2.09834 -18.83805014,-6.29502 -4.460601,-4.223921 -6.690896,-10.137416 -6.690893,-17.740503 -3e-6,-7.466787 2.190466,-13.339405 6.571413,-17.617873 4.407478,-4.278388 10.4611391,-6.417601 18.16099514,-6.417647 7.00947196,4.6e-5 12.61176186,2.16651 16.80688686,6.499399 4.221585,4.332971 6.3324,10.123835 6.332451,17.372615 M 7.7161621,76.563508 c -3.7e-5,-2.779586 -0.796569,-5.01418 -2.389605,-6.703784 -1.56655,-1.716786 -3.624262,-2.575197 -6.17314296,-2.575235 -2.76134704,3.8e-5 -5.00491904,0.803946 -6.73072004,2.411728 -1.725846,1.5806 -2.8011681,3.869695 -3.2259681,6.867291 l 18.5194361,0" id="path3032" inkscape:connector-curvature="0"/>
+    </g>
+    <g transform="matrix(1.3506797,0,0,1.5025343,262.91872,344.76678)" style="font-size:40px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans Bold" id="flowRoot2996-9">
+      <path d="m 6.546875,36.869966 6.992188,0 0,21.484375 c -1.1e-5,2.929685 -0.703135,5.162756 -2.109375,6.699219 -1.406257,1.549472 -3.4440157,2.32421 -6.1132818,2.324219 l -3.4570312,0 0,-4.589844 1.2109375,0 c 1.3281238,-4e-6 2.2395812,-0.299483 2.734375,-0.898437 0.4947886,-0.598961 0.7421841,-1.777345 0.7421875,-3.535157 l 0,-21.484375 m 0,-8.515625 6.992188,0 0,5.703125 -6.992188,0 0,-5.703125" id="path3035" inkscape:connector-curvature="0"/>
+      <path d="m 18.617187,36.869966 27.597657,0 0,5 -3.828125,0 0,9.804688 c -2.6e-5,1.679693 0.156224,2.727869 0.46875,3.144531 0.325494,0.40365 0.852838,0.605472 1.582031,0.605469 0.742159,3e-6 1.321586,-0.02604 1.738281,-0.07813 l 0,3.671875 c -0.80732,0.32552 -2.591172,0.48828 -5.351562,0.488281 -0.872419,-1e-6 -1.731793,-0.123698 -2.578125,-0.371094 -1.354187,-0.403645 -2.220071,-1.197915 -2.597657,-2.382812 -0.169289,-0.559893 -0.253924,-1.412757 -0.253906,-2.558594 l 0,-12.324219 -5.957031,0 0,16.875 -6.992188,0 0,-16.875 -3.828125,0 0,-5" id="path3037" inkscape:connector-curvature="0"/>
+    </g>
+  </g>
+</g></svg>
diff --git a/ejpi-calc b/ejpi-calc
new file mode 100755 (executable)
index 0000000..6c8a483
--- /dev/null
+++ b/ejpi-calc
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+import logging
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+from ejpi import ejpi_qt
+
+
+if __name__ == "__main__":
+       ejpi_qt.run()
diff --git a/ejpi/__init__.py b/ejpi/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/ejpi/constants.py b/ejpi/constants.py
new file mode 100644 (file)
index 0000000..1c8d555
--- /dev/null
@@ -0,0 +1,11 @@
+import os
+
+__pretty_app_name__ = "e**(j pi) + 1 = 0"
+__app_name__ = "ejpi"
+__version__ = "1.0.6"
+__build__ = 0
+__app_magic__ = 0xdeadbeef
+_data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
+_user_settings_ = "%s/settings.ini" % _data_path_
+_user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__)
+IS_MAEMO = True
diff --git a/ejpi/ejpi_qt.py b/ejpi/ejpi_qt.py
new file mode 100755 (executable)
index 0000000..b5d58d8
--- /dev/null
@@ -0,0 +1,380 @@
+#!/usr/bin/env python
+# -*- coding: UTF8 -*-
+
+from __future__ import with_statement
+
+import os
+import simplejson
+import string
+import logging
+import logging.handlers
+
+import util.qt_compat as qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+import constants
+from util import misc as misc_utils
+
+from util import qui_utils
+from util import qwrappers
+from util import qtpie
+from util import qtpieboard
+import plugin_utils
+import history
+import qhistory
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class Calculator(qwrappers.ApplicationWrapper):
+
+       def __init__(self, app):
+               self._recent = []
+               self._hiddenCategories = set()
+               self._hiddenUnits = {}
+               qwrappers.ApplicationWrapper.__init__(self, app, constants)
+
+       def load_settings(self):
+               try:
+                       with open(constants._user_settings_, "r") as settingsFile:
+                               settings = simplejson.load(settingsFile)
+               except IOError, e:
+                       _moduleLogger.info("No settings")
+                       settings = {}
+               except ValueError:
+                       _moduleLogger.info("Settings were corrupt")
+                       settings = {}
+
+               isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
+               self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
+               self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
+
+       def save_settings(self):
+               settings = {
+                       "isFullScreen": self._fullscreenAction.isChecked(),
+                       "isPortrait": self._orientationAction.isChecked(),
+               }
+               with open(constants._user_settings_, "w") as settingsFile:
+                       simplejson.dump(settings, settingsFile)
+
+       @property
+       def dataPath(self):
+               return self._dataPath
+
+       def _new_main_window(self):
+               return MainWindow(None, self)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_about(self, checked = True):
+               raise NotImplementedError("Booh")
+
+
+class QValueEntry(object):
+
+       def __init__(self):
+               self._widget = QtGui.QLineEdit("")
+               qui_utils.mark_numbers_preferred(self._widget)
+
+       @property
+       def toplevel(self):
+               return self._widget
+
+       @property
+       def entry(self):
+               return self._widget
+
+       def get_value(self):
+               value = str(self._widget.text()).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._widget.setText(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 MainWindow(qwrappers.WindowWrapper):
+
+       _plugin_search_paths = [
+               os.path.join(os.path.dirname(__file__), "plugins/"),
+       ]
+
+       _user_history = "%s/history.stack" % constants._data_path_
+
+       def __init__(self, parent, app):
+               qwrappers.WindowWrapper.__init__(self, parent, app)
+               self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
+               #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
+
+               self._historyView = qhistory.QCalcHistory(self._app.errorLog)
+               self._userEntry = QValueEntry()
+               self._userEntry.entry.returnPressed.connect(self._on_push)
+               self._userEntryLayout = QtGui.QHBoxLayout()
+               self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
+               self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
+
+               self._controlLayout = QtGui.QVBoxLayout()
+               self._controlLayout.setContentsMargins(0, 0, 0, 0)
+               self._controlLayout.addWidget(self._historyView.toplevel, 1000)
+               self._controlLayout.addLayout(self._userEntryLayout, 0)
+
+               self._keyboardTabs = QtGui.QTabWidget()
+
+               self._layout.addLayout(self._controlLayout)
+               self._layout.addWidget(self._keyboardTabs)
+
+               self._copyItemAction = QtGui.QAction(None)
+               self._copyItemAction.setText("Copy")
+               self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
+               self._copyItemAction.triggered.connect(self._on_copy)
+
+               self._pasteItemAction = QtGui.QAction(None)
+               self._pasteItemAction.setText("Paste")
+               self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
+               self._pasteItemAction.triggered.connect(self._on_paste)
+
+               self._closeWindowAction = QtGui.QAction(None)
+               self._closeWindowAction.setText("Close")
+               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
+               self._closeWindowAction.triggered.connect(self._on_close_window)
+
+               self._window.addAction(self._copyItemAction)
+               self._window.addAction(self._pasteItemAction)
+
+               self._constantPlugins = plugin_utils.ConstantPluginManager()
+               self._constantPlugins.add_path(*self._plugin_search_paths)
+               for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
+                       try:
+                               pluginId = self._constantPlugins.lookup_plugin(pluginName)
+                               self._constantPlugins.enable_plugin(pluginId)
+                       except:
+                               _moduleLogger.info("Failed to load plugin %s" % pluginName)
+
+               self._operatorPlugins = plugin_utils.OperatorPluginManager()
+               self._operatorPlugins.add_path(*self._plugin_search_paths)
+               for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
+                       try:
+                               pluginId = self._operatorPlugins.lookup_plugin(pluginName)
+                               self._operatorPlugins.enable_plugin(pluginId)
+                       except:
+                               _moduleLogger.info("Failed to load plugin %s" % pluginName)
+
+               self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
+               self._keyboardPlugins.add_path(*self._plugin_search_paths)
+               self._activeKeyboards = []
+
+               self._history = history.RpnCalcHistory(
+                       self._historyView,
+                       self._userEntry, self._app.errorLog,
+                       self._constantPlugins.constants, self._operatorPlugins.operators
+               )
+               self._load_history()
+
+               # Basic keyboard stuff
+               self._handler = qtpieboard.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
+               entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
+               self._keyboardPlugins.enable_plugin(entryKeyboardId)
+               entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
+               entryKeyboard = entryPlugin.setup(self._history, self._handler)
+               self._userEntryLayout.addWidget(entryKeyboard.toplevel)
+
+               # Plugins
+               self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
+               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._scrollTimer = QtCore.QTimer()
+               self._scrollTimer.setInterval(0)
+               self._scrollTimer.setSingleShot(True)
+               self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
+               self._scrollTimer.start()
+
+               self.set_fullscreen(self._app.fullscreenAction.isChecked())
+               self.update_orientation(self._app.orientation)
+
+       def walk_children(self):
+               return ()
+
+       def update_orientation(self, orientation):
+               qwrappers.WindowWrapper.update_orientation(self, orientation)
+               windowOrientation = self.idealWindowOrientation
+               if windowOrientation == QtCore.Qt.Horizontal:
+                       defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
+                       tabPosition = QtGui.QTabWidget.North
+               else:
+                       defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
+                       #tabPosition = QtGui.QTabWidget.South
+                       tabPosition = QtGui.QTabWidget.West
+               self._layout.setDirection(defaultLayoutOrientation)
+               self._keyboardTabs.setTabPosition(tabPosition)
+
+       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()
+               relIcon = self._keyboardPlugins.keyboards[pluginName].icon
+               for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
+                       absIconPath = os.path.join(iconPath, relIcon)
+                       if os.path.exists(absIconPath):
+                               icon = QtGui.QIcon(absIconPath)
+                               break
+               else:
+                       icon = None
+               pluginKeyboard = plugin.setup(self._history, self._handler)
+
+               self._activeKeyboards.append({
+                       "pluginName": pluginName,
+                       "plugin": plugin,
+                       "pluginKeyboard": pluginKeyboard,
+               })
+               if icon is None:
+                       self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
+               else:
+                       self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
+
+       def close(self):
+               qwrappers.WindowWrapper.close(self)
+               self._save_history()
+
+       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)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_delayed_scroll_to_bottom(self):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._historyView.scroll_to_bottom()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_child_close(self, something = None):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._child = None
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_copy(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       eqNode = self._historyView.peek()
+                       resultNode = eqNode.simplify()
+                       self._app._clipboard.setText(str(resultNode))
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_paste(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       result = str(self._app._clipboard.text())
+                       self._userEntry.append(result)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_entry_direct(self, keys, modifiers):
+               with qui_utils.notify_error(self._app.errorLog):
+                       if "shift" in modifiers:
+                               keys = keys.upper()
+                       self._userEntry.append(keys)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_push(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._history.push_entry()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_unpush(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._historyView.unpush()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_entry_backspace(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._userEntry.pop()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_entry_clear(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._userEntry.clear()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_clear_all(self, *args):
+               with qui_utils.notify_error(self._app.errorLog):
+                       self._history.clear()
+
+
+def run():
+       try:
+               os.makedirs(constants._data_path_)
+       except OSError, e:
+               if e.errno != 17:
+                       raise
+
+       logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
+       logging.basicConfig(level=logging.DEBUG, format=logFormat)
+       rotating = logging.handlers.RotatingFileHandler(constants._user_logpath_, maxBytes=512*1024, backupCount=1)
+       rotating.setFormatter(logging.Formatter(logFormat))
+       root = logging.getLogger()
+       root.addHandler(rotating)
+       _moduleLogger.info("%s %s-%s" % (constants.__app_name__, 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])
+
+       app = QtGui.QApplication([])
+       handle = Calculator(app)
+       qtpie.init_pies()
+       return app.exec_()
+
+
+if __name__ == "__main__":
+       import sys
+       val = run()
+       sys.exit(val)
diff --git a/ejpi/history.py b/ejpi/history.py
new file mode 100644 (file)
index 0000000..c525027
--- /dev/null
@@ -0,0 +1,209 @@
+#!/usr/bin/env python
+
+
+import re
+import weakref
+
+from util import algorithms
+import operation
+
+
+__BASE_MAPPINGS = {
+       "0x": 16,
+       "0o": 8,
+       "0b": 2,
+}
+
+
+_VARIABLE_VALIDATION_RE = re.compile("^[a-zA-Z0-9]+$")
+
+
+def validate_variable_name(variableName):
+       match = _VARIABLE_VALIDATION_RE.match(variableName)
+       if match is None:
+               raise RuntimeError("Invalid characters in '%s'" % variableName)
+
+
+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.history._parse_value = self._parse_value
+               self.__entry = weakref.ref(entry)
+
+               self.__errorReporter = errorReporting
+               self.__constants = constants
+               self.__operations = operations
+
+               self.__serialRenderer = operation.render_number()
+
+       @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
+
+               validate_variable_name(userInput)
+               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/ejpi/operation.py b/ejpi/operation.py
new file mode 100644 (file)
index 0000000..9a0cd5a
--- /dev/null
@@ -0,0 +1,420 @@
+#!/usr/bin/env python
+
+
+import itertools
+import functools
+import decimal
+
+from util import overloading
+from util 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/ejpi/plugin_utils.py b/ejpi/plugin_utils.py
new file mode 100644 (file)
index 0000000..087f8db
--- /dev/null
@@ -0,0 +1,315 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+
+import sys
+import os
+import inspect
+import ConfigParser
+
+from util import qtpieboard
+from util 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, boardHandler):
+               self.__handler = boardHandler
+
+               boardTree = self.factory.map
+
+               keyboardName = boardTree["name"]
+               keyTree = boardTree["keys"]
+
+               keyboard = qtpieboard.PieKeyboard()
+               qtpieboard.load_keyboard(keyboardName, keyTree, keyboard, self.__handler, self.factory.iconPaths)
+
+               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, icon, keyboardMap, iconPaths):
+               self.name = pluginName
+               self.map = keyboardMap
+               self.commands = {}
+               self.icon = icon
+               self.iconPaths = iconPaths
+
+       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/ejpi/plugins/__init__.py b/ejpi/plugins/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/ejpi/plugins/alphabet.ini b/ejpi/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/ejpi/plugins/alphabet.py b/ejpi/plugins/alphabet.py
new file mode 100644 (file)
index 0000000..64662ba
--- /dev/null
@@ -0,0 +1,81 @@
+"""
+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
+
+from ejpi import plugin_utils
+
+
+_NAME = "Alphabet"
+_ICON = "alphabet.png"
+_MAP = {
+       "name": _NAME,
+       "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,
+               },
+       },
+}
+_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
+PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_PATH)
diff --git a/ejpi/plugins/builtins.ini b/ejpi/plugins/builtins.ini
new file mode 100644 (file)
index 0000000..3e74fd2
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Builtins
+version=0.1
+description=
+
+[Constants]
+name=Builtins
+version=0.1
+description=
+
+[Keyboard]
+name=Builtins
+version=0.1
+description=
diff --git a/ejpi/plugins/builtins.py b/ejpi/plugins/builtins.py
new file mode 100644 (file)
index 0000000..239835b
--- /dev/null
@@ -0,0 +1,100 @@
+from __future__ import division
+
+import os
+import operator
+import math
+
+from ejpi import operation
+from ejpi import plugin_utils
+
+
+_NAME = "Builtins"
+_ICON = "builtins.png"
+_MAP = {
+       "name": _NAME,
+       "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": "x ** y", },
+                       "EAST": {"action": "[sq]", "type": "text", "text": "x ** 2", },
+                       "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", },
+                       "NORTH": {"action": ".", "type": "text", "text": ".", },
+                       "EAST": {"action": "0", "type": "text", "text": "0", },
+                       "showAllSlices": True,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "2", "type": "text", "text": "2", },
+                       "EAST": {"action": "[abs]", "type": "text", "text": "abs", },
+                       "WEST": {"action": "[+-]", "type": "text", "text": "+/-", },
+                       "showAllSlices": True,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "3", "type": "text", "text": "3", },
+                       "NORTH": {"action": "[!]", "type": "text", "text": "x !", },
+                       "WEST": {"action": "j", "type": "text", "text": "j", },
+                       "showAllSlices": True,
+               },
+       },
+}
+_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
+PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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/ejpi/plugins/computer.ini b/ejpi/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/ejpi/plugins/computer.py b/ejpi/plugins/computer.py
new file mode 100644 (file)
index 0000000..da69454
--- /dev/null
@@ -0,0 +1,102 @@
+from __future__ import division
+
+import os
+import operator
+import math
+
+from ejpi import operation
+from ejpi import plugin_utils
+
+
+_NAME = "Computer"
+_ICON = "computer.png"
+_MAP = {
+       "name": _NAME,
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "7", "type": "text", "text": "7", },
+                       "SOUTH": {"action": "d", "type": "text", "text": "D", },
+                       "showAllSlices": False,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "8", "type": "text", "text": "8", },
+                       "SOUTH": {"action": "e", "type": "text", "text": "E", },
+                       "showAllSlices": False,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "9", "type": "text", "text": "9", },
+                       "SOUTH": {"action": "f", "type": "text", "text": "F", },
+                       "showAllSlices": False,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "4", "type": "text", "text": "4", },
+                       "NORTH_EAST": {"action": "0o", "type": "text", "text": "0o", },
+                       "EAST": {"action": "0x", "type": "text", "text": "0x", },
+                       "SOUTH_EAST": {"action": "0b", "type": "text", "text": "0b", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "5", "type": "text", "text": "5", },
+                       "NORTH": {"action": "[&]", "type": "text", "text": "and", },
+                       "WEST": {"action": "[|]", "type": "text", "text": "or", },
+                       "SOUTH": {"action": "[~]", "type": "text", "text": "not", },
+                       "EAST": {"action": "[^]", "type": "text", "text": "xor", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "6", "type": "text", "text": "6", },
+                       "NORTH_WEST": {"action": "[oct]", "type": "text", "text": "-> oct", },
+                       "WEST": {"action": "[dec]", "type": "text", "text": "-> dec", },
+                       "SOUTH_WEST": {"action": "[hex]", "type": "text", "text": "-> hex", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "1", "type": "text", "text": "1", },
+                       "NORTH": {"action": "a", "type": "text", "text": "A", },
+                       "EAST": {"action": "0", "type": "text", "text": "0", },
+                       "showAllSlices": False,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "2", "type": "text", "text": "2", },
+                       "NORTH": {"action": "b", "type": "text", "text": "B", },
+                       "EAST": {"action": "[//]", "type": "text", "text": "x // y", },
+                       "WEST": {"action": "[%]", "type": "text", "text": "x % y", },
+                       "showAllSlices": False,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "3", "type": "text", "text": "3", },
+                       "NORTH": {"action": "c", "type": "text", "text": "C", },
+                       "showAllSlices": False,
+               },
+       },
+}
+_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
+PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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/ejpi/plugins/entry.ini b/ejpi/plugins/entry.ini
new file mode 100644 (file)
index 0000000..cbbaa4d
--- /dev/null
@@ -0,0 +1,14 @@
+[Operator]
+name=Entry
+version=0.1
+description=
+
+[Constants]
+name=Entry
+version=0.1
+description=
+
+[Keyboard]
+name=Entry
+version=0.1
+description=
diff --git a/ejpi/plugins/entry.py b/ejpi/plugins/entry.py
new file mode 100644 (file)
index 0000000..65b33f7
--- /dev/null
@@ -0,0 +1,23 @@
+from __future__ import division
+
+import os
+
+from ejpi import plugin_utils
+
+
+_NAME = "Entry"
+_ICON = "newline.png"
+_MAP = {
+       "name": _NAME,
+       "keys": {
+               (0, 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,
+               },
+       },
+}
+_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
+PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_PATH)
diff --git a/ejpi/plugins/images/alphabet.png b/ejpi/plugins/images/alphabet.png
new file mode 100644 (file)
index 0000000..696449e
Binary files /dev/null and b/ejpi/plugins/images/alphabet.png differ
diff --git a/ejpi/plugins/images/alt.png b/ejpi/plugins/images/alt.png
new file mode 100644 (file)
index 0000000..2e5c5c1
Binary files /dev/null and b/ejpi/plugins/images/alt.png differ
diff --git a/ejpi/plugins/images/arrows.png b/ejpi/plugins/images/arrows.png
new file mode 100644 (file)
index 0000000..16bb714
Binary files /dev/null and b/ejpi/plugins/images/arrows.png differ
diff --git a/ejpi/plugins/images/backspace.png b/ejpi/plugins/images/backspace.png
new file mode 100644 (file)
index 0000000..20dc09a
Binary files /dev/null and b/ejpi/plugins/images/backspace.png differ
diff --git a/ejpi/plugins/images/builtins.png b/ejpi/plugins/images/builtins.png
new file mode 100644 (file)
index 0000000..4cce92a
Binary files /dev/null and b/ejpi/plugins/images/builtins.png differ
diff --git a/ejpi/plugins/images/clear.png b/ejpi/plugins/images/clear.png
new file mode 100644 (file)
index 0000000..fb07cd2
Binary files /dev/null and b/ejpi/plugins/images/clear.png differ
diff --git a/ejpi/plugins/images/computer.png b/ejpi/plugins/images/computer.png
new file mode 100644 (file)
index 0000000..306e4bc
Binary files /dev/null and b/ejpi/plugins/images/computer.png differ
diff --git a/ejpi/plugins/images/control.png b/ejpi/plugins/images/control.png
new file mode 100644 (file)
index 0000000..b938b0f
Binary files /dev/null and b/ejpi/plugins/images/control.png differ
diff --git a/ejpi/plugins/images/newline.png b/ejpi/plugins/images/newline.png
new file mode 100644 (file)
index 0000000..0ae2c53
Binary files /dev/null and b/ejpi/plugins/images/newline.png differ
diff --git a/ejpi/plugins/images/shift.png b/ejpi/plugins/images/shift.png
new file mode 100644 (file)
index 0000000..e1687c8
Binary files /dev/null and b/ejpi/plugins/images/shift.png differ
diff --git a/ejpi/plugins/images/space.png b/ejpi/plugins/images/space.png
new file mode 100644 (file)
index 0000000..4efb4c2
Binary files /dev/null and b/ejpi/plugins/images/space.png differ
diff --git a/ejpi/plugins/images/super.png b/ejpi/plugins/images/super.png
new file mode 100644 (file)
index 0000000..2f57f62
Binary files /dev/null and b/ejpi/plugins/images/super.png differ
diff --git a/ejpi/plugins/images/symbols.dia b/ejpi/plugins/images/symbols.dia
new file mode 100644 (file)
index 0000000..a04fe5a
Binary files /dev/null and b/ejpi/plugins/images/symbols.dia differ
diff --git a/ejpi/plugins/images/tab.png b/ejpi/plugins/images/tab.png
new file mode 100644 (file)
index 0000000..1450d81
Binary files /dev/null and b/ejpi/plugins/images/tab.png differ
diff --git a/ejpi/plugins/images/trig.png b/ejpi/plugins/images/trig.png
new file mode 100644 (file)
index 0000000..c03a585
Binary files /dev/null and b/ejpi/plugins/images/trig.png differ
diff --git a/ejpi/plugins/trig.ini b/ejpi/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/ejpi/plugins/trig.py b/ejpi/plugins/trig.py
new file mode 100644 (file)
index 0000000..5dbce12
--- /dev/null
@@ -0,0 +1,140 @@
+from __future__ import division
+
+import os
+import math
+import cmath
+
+from ejpi import operation
+from ejpi import plugin_utils
+
+
+_NAME = "Trigonometry"
+_ICON = "trig.png"
+_MAP = {
+       "name": _NAME,
+       "keys": {
+               (0, 0): {
+                       "CENTER": {"action": "7", "type": "text", "text": "7", },
+                       "SOUTH": {"action": "[sinh]", "type": "text", "text": "sinh", },
+                       "SOUTH_EAST": {"action": "[cosh]", "type": "text", "text": "cosh", },
+                       "EAST": {"action": "[tanh]", "type": "text", "text": "tanh", },
+                       "showAllSlices": False,
+               },
+               (0, 1): {
+                       "CENTER": {"action": "8", "type": "text", "text": "8", },
+                       "showAllSlices": False,
+               },
+               (0, 2): {
+                       "CENTER": {"action": "9", "type": "text", "text": "9", },
+                       "SOUTH": {"action": "[asinh]", "type": "text", "text": "asinh", },
+                       "SOUTH_WEST": {"action": "[acosh]", "type": "text", "text": "acosh", },
+                       "WEST": {"action": "[atanh]", "type": "text", "text": "atanh", },
+                       "showAllSlices": True,
+               },
+               (1, 0): {
+                       "CENTER": {"action": "4", "type": "text", "text": "4", },
+                       "showAllSlices": True,
+               },
+               (1, 1): {
+                       "CENTER": {"action": "5", "type": "text", "text": "5", },
+                       "NORTH": {"action": "[exp]", "type": "text", "text": "e ** x", },
+                       "SOUTH": {"action": "[log]", "type": "text", "text": "ln", },
+                       "WEST": {"action": "e", "type": "text", "text": "e", },
+                       "EAST": {"action": "j", "type": "text", "text": "j", },
+                       "showAllSlices": True,
+               },
+               (1, 2): {
+                       "CENTER": {"action": "6", "type": "text", "text": "6", },
+                       "WEST": {"action": "pi", "type": "text", "text": "pi", },
+                       "NORTH": {"action": "[rad]", "type": "text", "text": "-> rad", },
+                       "SOUTH": {"action": "[deg]", "type": "text", "text": "-> deg", },
+                       "showAllSlices": True,
+               },
+               (2, 0): {
+                       "CENTER": {"action": "1", "type": "text", "text": "1", },
+                       "NORTH": {"action": ".", "type": "text", "text": ".", },
+                       "EAST": {"action": "0", "type": "text", "text": "0", },
+                       "showAllSlices": True,
+               },
+               (2, 1): {
+                       "CENTER": {"action": "2", "type": "text", "text": "2", },
+                       "WEST": {"action": "[sin]", "type": "text", "text": "sin", },
+                       "NORTH": {"action": "[cos]", "type": "text", "text": "cos", },
+                       "EAST": {"action": "[tan]", "type": "text", "text": "tan", },
+                       "showAllSlices": True,
+               },
+               (2, 2): {
+                       "CENTER": {"action": "3", "type": "text", "text": "3", },
+                       "NORTH": {"action": "[asin]", "type": "text", "text": "asin", },
+                       "NORTH_WEST": {"action": "[acos]", "type": "text", "text": "acos", },
+                       "WEST": {"action": "[atan]", "type": "text", "text": "atan", },
+                       "showAllSlices": False,
+               },
+       },
+}
+_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
+PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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)
+
diff --git a/ejpi/qhistory.py b/ejpi/qhistory.py
new file mode 100644 (file)
index 0000000..1dea45d
--- /dev/null
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+
+"""
+http://www.grigoriev.ru/svgmath/ (MathML->SVG in Python)
+http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++)
+"""
+
+from __future__ import with_statement
+
+import logging
+
+import util.qt_compat as qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+from util import qui_utils
+import util.misc as misc_utils
+import history
+import operation
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class QCalcHistory(history.AbstractHistory):
+
+       _CLOSE_COLUMN = 0
+       _EQ_COLUMN = 1
+       _RESULT_COLUMN = 2
+
+       def __init__(self, errorReporter):
+               super(QCalcHistory, self).__init__()
+               self._prettyRenderer = operation.render_number()
+               self._errorLog = errorReporter
+
+               self._historyStore = QtGui.QStandardItemModel()
+               self._historyStore.setHorizontalHeaderLabels(["", "Equation", "Result"])
+               self._historyStore.itemChanged.connect(self._on_item_changed)
+
+               self._historyView = QtGui.QTreeView()
+               self._historyView.setModel(self._historyStore)
+               self._historyView.setUniformRowHeights(True)
+               self._historyView.setRootIsDecorated(False)
+               self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+               self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
+               self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
+               self._historyView.setHeaderHidden(True)
+               self._historyView.activated.connect(self._on_row_activated)
+
+               viewHeader = self._historyView.header()
+               viewHeader.setSortIndicatorShown(True)
+               viewHeader.setClickable(True)
+
+               viewHeader.setResizeMode(self._CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
+               viewHeader.setResizeMode(self._EQ_COLUMN, QtGui.QHeaderView.Stretch)
+               viewHeader.setResizeMode(self._RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
+               viewHeader.setStretchLastSection(False)
+
+               self._rowCount = 0
+               self._programmaticUpdate = False
+               self._closeIcon = qui_utils.get_theme_icon(("window-close", "general_close", "gtk-close"))
+
+       @property
+       def toplevel(self):
+               return self._historyView
+
+       def push(self, node):
+               simpleNode = node.simplify()
+
+               closeIcon = self._closeIcon
+               icon = QtGui.QStandardItem(closeIcon, "")
+               icon.setEditable(False)
+               icon.setCheckable(False)
+               equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
+               equation.setData(node)
+               equation.setCheckable(False)
+               eqFont = equation.font()
+               eqFont.setPointSize(max(eqFont.pointSize() - 3, 5))
+               equation.setFont(eqFont)
+
+               result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
+               result.setData(simpleNode)
+               result.setEditable(False)
+               result.setCheckable(False)
+
+               row = (icon, equation, result)
+               self._historyStore.appendRow(row)
+
+               index = result.index()
+               self._historyView.scrollToBottom()
+               self._rowCount += 1
+
+       def pop(self):
+               if len(self) == 0:
+                       raise IndexError("Not enough items in the history for the operation")
+
+               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
+               self._rowCount -= 1
+               return equation.data()
+
+       def peek(self):
+               if len(self) == 0:
+                       raise IndexError("Not enough items in the history for the operation")
+
+               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
+               row = (icon, equation, result)
+               self._historyStore.appendRow(row)
+
+               return equation.data()
+
+       def clear(self):
+               self._historyStore.clear()
+               self._rowCount = 0
+
+       def scroll_to_bottom(self):
+               self._historyView.scrollToBottom()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_row_activated(self, index):
+               with qui_utils.notify_error(self._errorLog):
+                       if index.column() == self._CLOSE_COLUMN:
+                               self._historyStore.removeRow(index.row(), index.parent())
+                               self._rowCount -= 1
+                       elif index.column() == self._EQ_COLUMN:
+                               self._duplicate_row(index)
+                       elif index.column() == self._RESULT_COLUMN:
+                               self._duplicate_row(index)
+                       else:
+                               raise NotImplementedError("Unsupported column to activate %s" % index.column())
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_item_changed(self, item):
+               with qui_utils.notify_error(self._errorLog):
+                       if self._programmaticUpdate:
+                               _moduleLogger.info("Blocking updating %r recursively" % item)
+                               return
+                       self._programmaticUpdate = True
+                       try:
+                               if item.column() in [self._EQ_COLUMN, self._RESULT_COLUMN]:
+                                       self._update_input(item)
+                               else:
+                                       raise NotImplementedError("Unsupported column to edit %s" % item.column())
+                       except StandardError, e:
+                               self._errorReporter.push_exception()
+                       finally:
+                               self._programmaticUpdate = False
+
+       def _duplicate_row(self, index):
+               item = self._historyStore.item(index.row(), self._EQ_COLUMN)
+               self.push(item.data())
+
+       def _parse_value(self, value):
+               raise NotImplementedError("What?")
+
+       def _update_input(self, item):
+               node = item.data()
+               try:
+                       eqNode = self._parse_value(str(item.text()))
+                       newText = operation.render_operation(self._prettyRenderer, eqNode)
+
+                       eqItem = self._historyStore.item(item.row(), self._EQ_COLUMN)
+                       eqItem.setData(eqNode)
+                       eqItem.setText(newText)
+
+                       resultNode = eqNode.simplify()
+                       resultText = operation.render_operation(self._prettyRenderer, resultNode)
+                       resultItem = self._historyStore.item(item.row(), self._RESULT_COLUMN)
+                       resultItem.setData(resultNode)
+                       resultItem.setText(resultText)
+               except:
+                       oldText = operation.render_operation(self._prettyRenderer, node)
+                       item.setText(oldText)
+                       raise
+
+       def __len__(self):
+               return self._rowCount
+
+       def __iter__(self):
+               for i in xrange(self._rowCount):
+                       item = self._historyStore.item(i, self._EQ_COLUMN)
+                       if item is None:
+                               continue
+                       yield item.data()
diff --git a/ejpi/util/__init__.py b/ejpi/util/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/ejpi/util/algorithms.py b/ejpi/util/algorithms.py
new file mode 100644 (file)
index 0000000..e94fb61
--- /dev/null
@@ -0,0 +1,664 @@
+#!/usr/bin/env python
+
+"""
+@note Source http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66448
+"""
+
+import itertools
+import functools
+import datetime
+import types
+import array
+import random
+
+
+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 product(*args, **kwds):
+       # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
+       # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
+       pools = map(tuple, args) * kwds.get('repeat', 1)
+       result = [[]]
+       for pool in pools:
+               result = [x+[y] for x in result for y in pool]
+       for prod in result:
+               yield tuple(prod)
+
+
+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
+
+
+def itr_available(queue, initiallyBlock = False):
+       if initiallyBlock:
+               yield queue.get()
+       while not queue.empty():
+               yield queue.get_nowait()
+
+
+class BloomFilter(object):
+       """
+       http://en.wikipedia.org/wiki/Bloom_filter
+       Sources:
+       http://code.activestate.com/recipes/577684-bloom-filter/
+       http://code.activestate.com/recipes/577686-bloom-filter/
+
+       >>> from random import sample
+       >>> from string import ascii_letters
+       >>> states = '''Alabama Alaska Arizona Arkansas California Colorado Connecticut
+       ... Delaware Florida Georgia Hawaii Idaho Illinois Indiana Iowa Kansas
+       ... Kentucky Louisiana Maine Maryland Massachusetts Michigan Minnesota
+       ... Mississippi Missouri Montana Nebraska Nevada NewHampshire NewJersey
+       ... NewMexico NewYork NorthCarolina NorthDakota Ohio Oklahoma Oregon
+       ... Pennsylvania RhodeIsland SouthCarolina SouthDakota Tennessee Texas Utah
+       ... Vermont Virginia Washington WestVirginia Wisconsin Wyoming'''.split()
+       >>> bf = BloomFilter(num_bits=1000, num_probes=14)
+       >>> for state in states:
+       ...     bf.add(state)
+       >>> numStatesFound = sum(state in bf for state in states)
+       >>> numStatesFound, len(states)
+       (50, 50)
+       >>> trials = 100
+       >>> numGarbageFound = sum(''.join(sample(ascii_letters, 5)) in bf for i in range(trials))
+       >>> numGarbageFound, trials
+       (0, 100)
+       """
+
+       def __init__(self, num_bits, num_probes):
+               num_words = (num_bits + 31) // 32
+               self._arr = array.array('B', [0]) * num_words
+               self._num_probes = num_probes
+
+       def add(self, key):
+               for i, mask in self._get_probes(key):
+                       self._arr[i] |= mask
+
+       def union(self, bfilter):
+               if self._match_template(bfilter):
+                       for i, b in enumerate(bfilter._arr):
+                               self._arr[i] |= b
+               else:
+                       # Union b/w two unrelated bloom filter raises this
+                       raise ValueError("Mismatched bloom filters")
+
+       def intersection(self, bfilter):
+               if self._match_template(bfilter):
+                       for i, b in enumerate(bfilter._arr):
+                               self._arr[i] &= b
+               else:
+                       # Intersection b/w two unrelated bloom filter raises this
+                       raise ValueError("Mismatched bloom filters")
+
+       def __contains__(self, key):
+               return all(self._arr[i] & mask for i, mask in self._get_probes(key))
+
+       def _match_template(self, bfilter):
+               return self.num_bits == bfilter.num_bits and self.num_probes == bfilter.num_probes
+
+       def _get_probes(self, key):
+               hasher = random.Random(key).randrange
+               for _ in range(self._num_probes):
+                       array_index = hasher(len(self._arr))
+                       bit_index = hasher(32)
+                       yield array_index, 1 << bit_index
+
+
+if __name__ == "__main__":
+       import doctest
+       print doctest.testmod()
diff --git a/ejpi/util/concurrent.py b/ejpi/util/concurrent.py
new file mode 100644 (file)
index 0000000..f5f6e1d
--- /dev/null
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import os
+import errno
+import time
+import functools
+import contextlib
+import logging
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class AsyncTaskQueue(object):
+
+       def __init__(self, taskPool):
+               self._asyncs = []
+               self._taskPool = taskPool
+
+       def add_async(self, func):
+               self.flush()
+               a = AsyncGeneratorTask(self._taskPool, func)
+               self._asyncs.append(a)
+               return a
+
+       def flush(self):
+               self._asyncs = [a for a in self._asyncs if not a.isDone]
+
+
+class AsyncGeneratorTask(object):
+
+       def __init__(self, pool, func):
+               self._pool = pool
+               self._func = func
+               self._run = None
+               self._isDone = False
+
+       @property
+       def isDone(self):
+               return self._isDone
+
+       def start(self, *args, **kwds):
+               assert self._run is None, "Task already started"
+               self._run = self._func(*args, **kwds)
+               trampoline, args, kwds = self._run.send(None) # priming the function
+               self._pool.add_task(
+                       trampoline,
+                       args,
+                       kwds,
+                       self.on_success,
+                       self.on_error,
+               )
+
+       @misc.log_exception(_moduleLogger)
+       def on_success(self, result):
+               _moduleLogger.debug("Processing success for: %r", self._func)
+               try:
+                       trampoline, args, kwds = self._run.send(result)
+               except StopIteration, e:
+                       self._isDone = True
+               else:
+                       self._pool.add_task(
+                               trampoline,
+                               args,
+                               kwds,
+                               self.on_success,
+                               self.on_error,
+                       )
+
+       @misc.log_exception(_moduleLogger)
+       def on_error(self, error):
+               _moduleLogger.debug("Processing error for: %r", self._func)
+               try:
+                       trampoline, args, kwds = self._run.throw(error)
+               except StopIteration, e:
+                       self._isDone = True
+               else:
+                       self._pool.add_task(
+                               trampoline,
+                               args,
+                               kwds,
+                               self.on_success,
+                               self.on_error,
+                       )
+
+       def __repr__(self):
+               return "<async %s at 0x%x>" % (self._func.__name__, id(self))
+
+       def __hash__(self):
+               return hash(self._func)
+
+       def __eq__(self, other):
+               return self._func == other._func
+
+       def __ne__(self, other):
+               return self._func != other._func
+
+
+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)
diff --git a/ejpi/util/coroutines.py b/ejpi/util/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/ejpi/util/go_utils.py b/ejpi/util/go_utils.py
new file mode 100644 (file)
index 0000000..61e731d
--- /dev/null
@@ -0,0 +1,274 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import time
+import functools
+import threading
+import Queue
+import logging
+
+import gobject
+
+import algorithms
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+def make_idler(func):
+       """
+       Decorator that makes a generator-function into a function that will continue execution on next call
+       """
+       a = []
+
+       @functools.wraps(func)
+       def decorated_func(*args, **kwds):
+               if not a:
+                       a.append(func(*args, **kwds))
+               try:
+                       a[0].next()
+                       return True
+               except StopIteration:
+                       del a[:]
+                       return False
+
+       return decorated_func
+
+
+def async(func):
+       """
+       Make a function mainloop friendly. the function will be called at the
+       next mainloop idle state.
+
+       >>> import misc
+       >>> misc.validate_decorator(async)
+       """
+
+       @functools.wraps(func)
+       def new_function(*args, **kwargs):
+
+               def async_function():
+                       func(*args, **kwargs)
+                       return False
+
+               gobject.idle_add(async_function)
+
+       return new_function
+
+
+class Async(object):
+
+       def __init__(self, func, once = True):
+               self.__func = func
+               self.__idleId = None
+               self.__once = once
+
+       def start(self):
+               assert self.__idleId is None
+               if self.__once:
+                       self.__idleId = gobject.idle_add(self._on_once)
+               else:
+                       self.__idleId = gobject.idle_add(self.__func)
+
+       def is_running(self):
+               return self.__idleId is not None
+
+       def cancel(self):
+               if self.__idleId is not None:
+                       gobject.source_remove(self.__idleId)
+                       self.__idleId = None
+
+       def __call__(self):
+               return self.start()
+
+       @misc.log_exception(_moduleLogger)
+       def _on_once(self):
+               self.cancel()
+               try:
+                       self.__func()
+               except Exception:
+                       pass
+               return False
+
+
+class Timeout(object):
+
+       def __init__(self, func, once = True):
+               self.__func = func
+               self.__timeoutId = None
+               self.__once = once
+
+       def start(self, **kwds):
+               assert self.__timeoutId is None
+
+               callback = self._on_once if self.__once else self.__func
+
+               assert len(kwds) == 1
+               timeoutInSeconds = kwds["seconds"]
+               assert 0 <= timeoutInSeconds
+
+               if timeoutInSeconds == 0:
+                       self.__timeoutId = gobject.idle_add(callback)
+               else:
+                       self.__timeoutId = timeout_add_seconds(timeoutInSeconds, callback)
+
+       def is_running(self):
+               return self.__timeoutId is not None
+
+       def cancel(self):
+               if self.__timeoutId is not None:
+                       gobject.source_remove(self.__timeoutId)
+                       self.__timeoutId = None
+
+       def __call__(self, **kwds):
+               return self.start(**kwds)
+
+       @misc.log_exception(_moduleLogger)
+       def _on_once(self):
+               self.cancel()
+               try:
+                       self.__func()
+               except Exception:
+                       pass
+               return False
+
+
+_QUEUE_EMPTY = object()
+
+
+class FutureThread(object):
+
+       def __init__(self):
+               self.__workQueue = Queue.Queue()
+               self.__thread = threading.Thread(
+                       name = type(self).__name__,
+                       target = self.__consume_queue,
+               )
+               self.__isRunning = True
+
+       def start(self):
+               self.__thread.start()
+
+       def stop(self):
+               self.__isRunning = False
+               for _ in algorithms.itr_available(self.__workQueue):
+                       pass # eat up queue to cut down dumb work
+               self.__workQueue.put(_QUEUE_EMPTY)
+
+       def clear_tasks(self):
+               for _ in algorithms.itr_available(self.__workQueue):
+                       pass # eat up queue to cut down dumb work
+
+       def add_task(self, func, args, kwds, on_success, on_error):
+               task = func, args, kwds, on_success, on_error
+               self.__workQueue.put(task)
+
+       @misc.log_exception(_moduleLogger)
+       def __trampoline_callback(self, on_success, on_error, isError, result):
+               if not self.__isRunning:
+                       if isError:
+                               _moduleLogger.error("Masking: %s" % (result, ))
+                       isError = True
+                       result = StopIteration("Cancelling all callbacks")
+               callback = on_success if not isError else on_error
+               try:
+                       callback(result)
+               except Exception:
+                       _moduleLogger.exception("Callback errored")
+               return False
+
+       @misc.log_exception(_moduleLogger)
+       def __consume_queue(self):
+               while True:
+                       task = self.__workQueue.get()
+                       if task is _QUEUE_EMPTY:
+                               break
+                       func, args, kwds, on_success, on_error = task
+
+                       try:
+                               result = func(*args, **kwds)
+                               isError = False
+                       except Exception, e:
+                               _moduleLogger.error("Error, passing it back to the main thread")
+                               result = e
+                               isError = True
+                       self.__workQueue.task_done()
+
+                       gobject.idle_add(self.__trampoline_callback, on_success, on_error, isError, result)
+               _moduleLogger.debug("Shutting down worker thread")
+
+
+class AutoSignal(object):
+
+       def __init__(self, toplevel):
+               self.__disconnectPool = []
+               toplevel.connect("destroy", self.__on_destroy)
+
+       def connect_auto(self, widget, *args):
+               id = widget.connect(*args)
+               self.__disconnectPool.append((widget, id))
+
+       @misc.log_exception(_moduleLogger)
+       def __on_destroy(self, widget):
+               _moduleLogger.info("Destroy: %r (%s to clean up)" % (self, len(self.__disconnectPool)))
+               for widget, id in self.__disconnectPool:
+                       widget.disconnect(id)
+               del self.__disconnectPool[:]
+
+
+def throttled(minDelay, queue):
+       """
+       Throttle the calls to a function by queueing all the calls that happen
+       before the minimum delay
+
+       >>> import misc
+       >>> import Queue
+       >>> misc.validate_decorator(throttled(0, Queue.Queue()))
+       """
+
+       def actual_decorator(func):
+
+               lastCallTime = [None]
+
+               def process_queue():
+                       if 0 < len(queue):
+                               func, args, kwargs = queue.pop(0)
+                               lastCallTime[0] = time.time() * 1000
+                               func(*args, **kwargs)
+                       return False
+
+               @functools.wraps(func)
+               def new_function(*args, **kwargs):
+                       now = time.time() * 1000
+                       if (
+                               lastCallTime[0] is None or
+                               (now - lastCallTime >= minDelay)
+                       ):
+                               lastCallTime[0] = now
+                               func(*args, **kwargs)
+                       else:
+                               queue.append((func, args, kwargs))
+                               lastCallDelta = now - lastCallTime[0]
+                               processQueueTimeout = int(minDelay * len(queue) - lastCallDelta)
+                               gobject.timeout_add(processQueueTimeout, process_queue)
+
+               return new_function
+
+       return actual_decorator
+
+
+def _old_timeout_add_seconds(timeout, callback):
+       return gobject.timeout_add(timeout * 1000, callback)
+
+
+def _timeout_add_seconds(timeout, callback):
+       return gobject.timeout_add_seconds(timeout, callback)
+
+
+try:
+       gobject.timeout_add_seconds
+       timeout_add_seconds = _timeout_add_seconds
+except AttributeError:
+       timeout_add_seconds = _old_timeout_add_seconds
diff --git a/ejpi/util/gtk_utils.py b/ejpi/util/gtk_utils.py
new file mode 100644 (file)
index 0000000..342feae
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+from __future__ import division
+
+import contextlib
+import logging
+
+import gtk
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def gtk_lock():
+       gtk.gdk.threads_enter()
+       try:
+               yield
+       finally:
+               gtk.gdk.threads_leave()
+
+
+def find_parent_window(widget):
+       while True:
+               parent = widget.get_parent()
+               if isinstance(parent, gtk.Window):
+                       return parent
+               widget = parent
+
+
+if __name__ == "__main__":
+       pass
+
diff --git a/ejpi/util/hildonize.py b/ejpi/util/hildonize.py
new file mode 100644 (file)
index 0000000..339eb2a
--- /dev/null
@@ -0,0 +1,766 @@
+#!/usr/bin/env python
+
+"""
+Open Issues
+       @bug not all of a message is shown
+       @bug Buttons are too small
+"""
+
+
+import gobject
+import gtk
+import dbus
+
+
+class _NullHildonModule(object):
+       pass
+
+
+try:
+       import hildon as _hildon
+       hildon  = _hildon # Dumb but gets around pyflakiness
+except (ImportError, OSError):
+       hildon = _NullHildonModule
+
+
+IS_HILDON_SUPPORTED = hildon is not _NullHildonModule
+
+
+class _NullHildonProgram(object):
+
+       def add_window(self, window):
+               pass
+
+
+def _hildon_get_app_class():
+       return hildon.Program
+
+
+def _null_get_app_class():
+       return _NullHildonProgram
+
+
+try:
+       hildon.Program
+       get_app_class = _hildon_get_app_class
+except AttributeError:
+       get_app_class = _null_get_app_class
+
+
+def _hildon_set_application_name(name):
+       gtk.set_application_name(name)
+
+
+def _null_set_application_name(name):
+       pass
+
+
+try:
+       gtk.set_application_name
+       set_application_name = _hildon_set_application_name
+except AttributeError:
+       set_application_name = _null_set_application_name
+
+
+def _fremantle_hildonize_window(app, window):
+       oldWindow = window
+       newWindow = hildon.StackableWindow()
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
+       app.add_window(newWindow)
+       return newWindow
+
+
+def _hildon_hildonize_window(app, window):
+       oldWindow = window
+       newWindow = hildon.Window()
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
+       app.add_window(newWindow)
+       return newWindow
+
+
+def _null_hildonize_window(app, window):
+       return window
+
+
+try:
+       hildon.StackableWindow
+       hildonize_window = _fremantle_hildonize_window
+except AttributeError:
+       try:
+               hildon.Window
+               hildonize_window = _hildon_hildonize_window
+       except AttributeError:
+               hildonize_window = _null_hildonize_window
+
+
+def _fremantle_hildonize_menu(window, gtkMenu):
+       appMenu = hildon.AppMenu()
+       window.set_app_menu(appMenu)
+       gtkMenu.get_parent().remove(gtkMenu)
+       return appMenu
+
+
+def _hildon_hildonize_menu(window, gtkMenu):
+       hildonMenu = gtk.Menu()
+       for child in gtkMenu.get_children():
+               child.reparent(hildonMenu)
+       window.set_menu(hildonMenu)
+       gtkMenu.destroy()
+       return hildonMenu
+
+
+def _null_hildonize_menu(window, gtkMenu):
+       return gtkMenu
+
+
+try:
+       hildon.AppMenu
+       GTK_MENU_USED = False
+       IS_FREMANTLE_SUPPORTED = True
+       hildonize_menu = _fremantle_hildonize_menu
+except AttributeError:
+       GTK_MENU_USED = True
+       IS_FREMANTLE_SUPPORTED = False
+       if IS_HILDON_SUPPORTED:
+               hildonize_menu = _hildon_hildonize_menu
+       else:
+               hildonize_menu = _null_hildonize_menu
+
+
+def _hildon_set_button_auto_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT)
+
+
+def _null_set_button_auto_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_AUTO_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_auto_selectable = _hildon_set_button_auto_selectable
+except AttributeError:
+       set_button_auto_selectable = _null_set_button_auto_selectable
+
+
+def _hildon_set_button_finger_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT)
+
+
+def _null_set_button_finger_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_FINGER_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_finger_selectable = _hildon_set_button_finger_selectable
+except AttributeError:
+       set_button_finger_selectable = _null_set_button_finger_selectable
+
+
+def _hildon_set_button_thumb_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT)
+
+
+def _null_set_button_thumb_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_THUMB_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_thumb_selectable = _hildon_set_button_thumb_selectable
+except AttributeError:
+       set_button_thumb_selectable = _null_set_button_thumb_selectable
+
+
+def _hildon_set_cell_thumb_selectable(renderer):
+       renderer.set_property("scale", 1.5)
+
+
+def _null_set_cell_thumb_selectable(renderer):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable
+else:
+       set_cell_thumb_selectable = _null_set_cell_thumb_selectable
+
+
+def _hildon_set_pix_cell_thumb_selectable(renderer):
+       renderer.set_property("stock-size", 48)
+
+
+def _null_set_pix_cell_thumb_selectable(renderer):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable
+else:
+       set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable
+
+
+def _fremantle_show_information_banner(parent, message):
+       hildon.hildon_banner_show_information(parent, "", message)
+
+
+def _hildon_show_information_banner(parent, message):
+       hildon.hildon_banner_show_information(parent, None, message)
+
+
+def _null_show_information_banner(parent, message):
+       pass
+
+
+if IS_FREMANTLE_SUPPORTED:
+       show_information_banner = _fremantle_show_information_banner
+else:
+       try:
+               hildon.hildon_banner_show_information
+               show_information_banner = _hildon_show_information_banner
+       except AttributeError:
+               show_information_banner = _null_show_information_banner
+
+
+def _fremantle_show_busy_banner_start(parent, message):
+       hildon.hildon_gtk_window_set_progress_indicator(parent, True)
+       return parent
+
+
+def _fremantle_show_busy_banner_end(banner):
+       hildon.hildon_gtk_window_set_progress_indicator(banner, False)
+
+
+def _hildon_show_busy_banner_start(parent, message):
+       return hildon.hildon_banner_show_animation(parent, None, message)
+
+
+def _hildon_show_busy_banner_end(banner):
+       banner.destroy()
+
+
+def _null_show_busy_banner_start(parent, message):
+       return None
+
+
+def _null_show_busy_banner_end(banner):
+       assert banner is None
+
+
+try:
+       hildon.hildon_gtk_window_set_progress_indicator
+       show_busy_banner_start = _fremantle_show_busy_banner_start
+       show_busy_banner_end = _fremantle_show_busy_banner_end
+except AttributeError:
+       try:
+               hildon.hildon_banner_show_animation
+               show_busy_banner_start = _hildon_show_busy_banner_start
+               show_busy_banner_end = _hildon_show_busy_banner_end
+       except AttributeError:
+               show_busy_banner_start = _null_show_busy_banner_start
+               show_busy_banner_end = _null_show_busy_banner_end
+
+
+def _hildon_hildonize_text_entry(textEntry):
+       textEntry.set_property('hildon-input-mode', 7)
+
+
+def _null_hildonize_text_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_text_entry = _hildon_hildonize_text_entry
+else:
+       hildonize_text_entry = _null_hildonize_text_entry
+
+
+def _hildon_window_to_portrait(window):
+       # gtk documentation is unclear whether this does a "=" or a "|="
+       flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST
+       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+
+def _hildon_window_to_landscape(window):
+       # gtk documentation is unclear whether this does a "=" or a "&= ~"
+       flags = hildon.PORTRAIT_MODE_SUPPORT
+       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+
+def _null_window_to_portrait(window):
+       pass
+
+
+def _null_window_to_landscape(window):
+       pass
+
+
+try:
+       hildon.PORTRAIT_MODE_SUPPORT
+       hildon.PORTRAIT_MODE_REQUEST
+       hildon.hildon_gtk_window_set_portrait_flags
+
+       window_to_portrait = _hildon_window_to_portrait
+       window_to_landscape = _hildon_window_to_landscape
+except AttributeError:
+       window_to_portrait = _null_window_to_portrait
+       window_to_landscape = _null_window_to_landscape
+
+
+def get_device_orientation():
+       bus = dbus.SystemBus()
+       try:
+               rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
+               mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request")
+               orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation()
+       except dbus.exception.DBusException:
+               # catching for documentation purposes that when a system doesn't
+               # support this, this is what to expect
+               raise
+
+       if orientation == "":
+               return gtk.ORIENTATION_HORIZONTAL
+       elif orientation == "":
+               return gtk.ORIENTATION_VERTICAL
+       else:
+               raise RuntimeError("Unknown orientation: %s" % orientation)
+
+
+def _hildon_hildonize_password_entry(textEntry):
+       textEntry.set_property('hildon-input-mode', 7 | (1 << 29))
+
+
+def _null_hildonize_password_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_password_entry = _hildon_hildonize_password_entry
+else:
+       hildonize_password_entry = _null_hildonize_password_entry
+
+
+def _hildon_hildonize_combo_entry(comboEntry):
+       comboEntry.set_property('hildon-input-mode', 1 << 4)
+
+
+def _null_hildonize_combo_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_combo_entry = _hildon_hildonize_combo_entry
+else:
+       hildonize_combo_entry = _null_hildonize_combo_entry
+
+
+def _null_create_seekbar():
+       adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
+       seek = gtk.HScale(adjustment)
+       seek.set_draw_value(False)
+       return seek
+
+
+def _fremantle_create_seekbar():
+       seek = hildon.Seekbar()
+       seek.set_range(0.0, 100)
+       seek.set_draw_value(False)
+       seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
+       return seek
+
+
+try:
+       hildon.Seekbar
+       create_seekbar = _fremantle_create_seekbar
+except AttributeError:
+       create_seekbar = _null_create_seekbar
+
+
+def _fremantle_hildonize_scrollwindow(scrolledWindow):
+       pannableWindow = hildon.PannableArea()
+
+       child = scrolledWindow.get_child()
+       scrolledWindow.remove(child)
+       pannableWindow.add(child)
+
+       parent = scrolledWindow.get_parent()
+       if parent is not None:
+               parent.remove(scrolledWindow)
+               parent.add(pannableWindow)
+
+       return pannableWindow
+
+
+def _hildon_hildonize_scrollwindow(scrolledWindow):
+       hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
+       return scrolledWindow
+
+
+def _null_hildonize_scrollwindow(scrolledWindow):
+       return scrolledWindow
+
+
+try:
+       hildon.PannableArea
+       hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
+       hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
+except AttributeError:
+       try:
+               hildon.hildon_helper_set_thumb_scrollbar
+               hildonize_scrollwindow = _hildon_hildonize_scrollwindow
+               hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
+       except AttributeError:
+               hildonize_scrollwindow = _null_hildonize_scrollwindow
+               hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
+
+
+def _hildon_request_number(parent, title, range, default):
+       spinner = hildon.NumberEditor(*range)
+       spinner.set_value(default)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(spinner)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return spinner.get_value()
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+def _null_request_number(parent, title, range, default):
+       adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
+       spinner = gtk.SpinButton(adjustment, 0, 0)
+       spinner.set_wrap(False)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(spinner)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return spinner.get_value_as_int()
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.NumberEditor # TODO deprecated in fremantle
+       request_number = _hildon_request_number
+except AttributeError:
+       request_number = _null_request_number
+
+
+def _hildon_touch_selector(parent, title, items, defaultIndex):
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       for item in items:
+               model.append((item, ))
+
+       selector = hildon.TouchSelector()
+       selector.append_text_column(model, True)
+       selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
+       selector.set_active(0, defaultIndex)
+
+       dialog = hildon.PickerDialog(parent)
+       dialog.set_selector(selector)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return selector.get_active(0)
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
+       dialog.response(gtk.RESPONSE_OK)
+       pathData[0] = path
+
+
+def _null_touch_selector(parent, title, items, defaultIndex = -1):
+       parentSize = parent.get_size()
+
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       for item in items:
+               model.append((item, ))
+
+       cell = gtk.CellRendererText()
+       set_cell_thumb_selectable(cell)
+       column = gtk.TreeViewColumn(title)
+       column.pack_start(cell, expand=True)
+       column.add_attribute(cell, "text", 0)
+
+       treeView = gtk.TreeView()
+       treeView.set_model(model)
+       treeView.append_column(column)
+       selection = treeView.get_selection()
+       selection.set_mode(gtk.SELECTION_SINGLE)
+       if 0 < defaultIndex:
+               selection.select_path((defaultIndex, ))
+
+       scrolledWin = gtk.ScrolledWindow()
+       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+       scrolledWin.add(treeView)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(scrolledWin)
+       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
+
+       scrolledWin = hildonize_scrollwindow(scrolledWin)
+       pathData = [None]
+       treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       if pathData[0] is None:
+                               raise RuntimeError("No selection made")
+                       return pathData[0][0]
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.PickerDialog
+       hildon.TouchSelector
+       touch_selector = _hildon_touch_selector
+except AttributeError:
+       touch_selector = _null_touch_selector
+
+
+def _hildon_touch_selector_entry(parent, title, items, defaultItem):
+       # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
+       try:
+               selector = hildon.TouchSelectorEntry(text=True)
+       except TypeError:
+               selector = hildon.hildon_touch_selector_entry_new_text()
+       defaultIndex = -1
+       for i, item in enumerate(items):
+               selector.append_text(item)
+               if item == defaultItem:
+                       defaultIndex = i
+
+       dialog = hildon.PickerDialog(parent)
+       dialog.set_selector(selector)
+
+       if 0 < defaultIndex:
+               selector.set_active(0, defaultIndex)
+       else:
+               selector.get_entry().set_text(defaultItem)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+       finally:
+               dialog.hide()
+
+       if response == gtk.RESPONSE_OK:
+               return selector.get_entry().get_text()
+       elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+               raise RuntimeError("User cancelled request")
+       else:
+               raise RuntimeError("Unrecognized response %r", response)
+
+
+def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
+       custom = entry.get_text().strip()
+       if custom:
+               result[0] = custom
+               selection.unselect_all()
+       else:
+               result[0] = None
+               selection.select_path((defaultIndex, ))
+
+
+def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
+       dialog.response(gtk.RESPONSE_OK)
+       result[0] = customEntry.get_text()
+
+
+def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
+       dialog.response(gtk.RESPONSE_OK)
+       model = treeView.get_model()
+       itr = model.get_iter(path)
+       if itr is not None:
+               result[0] = model.get_value(itr, 0)
+
+
+def _null_touch_selector_entry(parent, title, items, defaultItem):
+       parentSize = parent.get_size()
+
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       defaultIndex = -1
+       for i, item in enumerate(items):
+               model.append((item, ))
+               if item == defaultItem:
+                       defaultIndex = i
+
+       cell = gtk.CellRendererText()
+       set_cell_thumb_selectable(cell)
+       column = gtk.TreeViewColumn(title)
+       column.pack_start(cell, expand=True)
+       column.add_attribute(cell, "text", 0)
+
+       treeView = gtk.TreeView()
+       treeView.set_model(model)
+       treeView.append_column(column)
+       selection = treeView.get_selection()
+       selection.set_mode(gtk.SELECTION_SINGLE)
+
+       scrolledWin = gtk.ScrolledWindow()
+       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+       scrolledWin.add(treeView)
+
+       customEntry = gtk.Entry()
+
+       layout = gtk.VBox()
+       layout.pack_start(customEntry, expand=False)
+       layout.pack_start(scrolledWin)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(layout)
+       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
+
+       scrolledWin = hildonize_scrollwindow(scrolledWin)
+
+       result = [None]
+       if 0 < defaultIndex:
+               selection.select_path((defaultIndex, ))
+               result[0] = defaultItem
+       else:
+               customEntry.set_text(defaultItem)
+
+       customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
+       customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
+       treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       _, itr = selection.get_selected()
+                       if itr is not None:
+                               return model.get_value(itr, 0)
+                       else:
+                               enteredText = customEntry.get_text().strip()
+                               if enteredText:
+                                       return enteredText
+                               elif result[0] is not None:
+                                       return result[0]
+                               else:
+                                       raise RuntimeError("No selection made")
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.PickerDialog
+       hildon.TouchSelectorEntry
+       touch_selector_entry = _hildon_touch_selector_entry
+except AttributeError:
+       touch_selector_entry = _null_touch_selector_entry
+
+
+if __name__ == "__main__":
+       app = get_app_class()()
+
+       label = gtk.Label("Hello World from a Label!")
+
+       win = gtk.Window()
+       win.add(label)
+       win = hildonize_window(app, win)
+       if False and IS_FREMANTLE_SUPPORTED:
+               appMenu = hildon.AppMenu()
+               for i in xrange(5):
+                       b = gtk.Button(str(i))
+                       appMenu.append(b)
+               win.set_app_menu(appMenu)
+               win.show_all()
+               appMenu.show_all()
+               gtk.main()
+       elif False:
+               print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
+       elif False:
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
+       elif False:
+               import pprint
+               name, value = "", ""
+               goodLocals = [
+                       (name, value) for (name, value) in locals().iteritems()
+                       if not name.startswith("_")
+               ]
+               pprint.pprint(goodLocals)
+       elif False:
+               import time
+               show_information_banner(win, "Hello World")
+               time.sleep(5)
+       elif False:
+               import time
+               banner = show_busy_banner_start(win, "Hello World")
+               time.sleep(5)
+               show_busy_banner_end(banner)
diff --git a/ejpi/util/io.py b/ejpi/util/io.py
new file mode 100644 (file)
index 0000000..4198f4b
--- /dev/null
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+
+
+from __future__ import with_statement
+
+import os
+import pickle
+import contextlib
+import itertools
+import codecs
+from xml.sax import saxutils
+import csv
+try:
+       import cStringIO as StringIO
+except ImportError:
+       import StringIO
+
+
+@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
+
+
+class UTF8Recoder(object):
+       """
+       Iterator that reads an encoded stream and reencodes the input to UTF-8
+       """
+       def __init__(self, f, encoding):
+               self.reader = codecs.getreader(encoding)(f)
+
+       def __iter__(self):
+               return self
+
+       def next(self):
+               return self.reader.next().encode("utf-8")
+
+
+class UnicodeReader(object):
+       """
+       A CSV reader which will iterate over lines in the CSV file "f",
+       which is encoded in the given encoding.
+       """
+
+       def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
+               f = UTF8Recoder(f, encoding)
+               self.reader = csv.reader(f, dialect=dialect, **kwds)
+
+       def next(self):
+               row = self.reader.next()
+               return [unicode(s, "utf-8") for s in row]
+
+       def __iter__(self):
+               return self
+
+class UnicodeWriter(object):
+       """
+       A CSV writer which will write rows to CSV file "f",
+       which is encoded in the given encoding.
+       """
+
+       def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
+               # Redirect output to a queue
+               self.queue = StringIO.StringIO()
+               self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
+               self.stream = f
+               self.encoder = codecs.getincrementalencoder(encoding)()
+
+       def writerow(self, row):
+               self.writer.writerow([s.encode("utf-8") for s in row])
+               # Fetch UTF-8 output from the queue ...
+               data = self.queue.getvalue()
+               data = data.decode("utf-8")
+               # ... and reencode it into the target encoding
+               data = self.encoder.encode(data)
+               # write to the target stream
+               self.stream.write(data)
+               # empty queue
+               self.queue.truncate(0)
+
+       def writerows(self, rows):
+               for row in rows:
+                       self.writerow(row)
+
+
+def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
+       # csv.py doesn't do Unicode; encode temporarily as UTF-8:
+       csv_reader = csv.reader(utf_8_encoder(unicode_csv_data),
+                                                       dialect=dialect, **kwargs)
+       for row in csv_reader:
+               # decode UTF-8 back to Unicode, cell by cell:
+               yield [unicode(cell, 'utf-8') for cell in row]
+
+
+def utf_8_encoder(unicode_csv_data):
+       for line in unicode_csv_data:
+               yield line.encode('utf-8')
+
+
+_UNESCAPE_ENTITIES = {
+ "&quot;": '"',
+ "&nbsp;": " ",
+ "&#39;": "'",
+}
+
+
+_ESCAPE_ENTITIES = dict((v, k) for (v, k) in zip(_UNESCAPE_ENTITIES.itervalues(), _UNESCAPE_ENTITIES.iterkeys()))
+del _ESCAPE_ENTITIES[" "]
+
+
+def unescape(text):
+       plain = saxutils.unescape(text, _UNESCAPE_ENTITIES)
+       return plain
+
+
+def escape(text):
+       fancy = saxutils.escape(text, _ESCAPE_ENTITIES)
+       return fancy
diff --git a/ejpi/util/linux.py b/ejpi/util/linux.py
new file mode 100644 (file)
index 0000000..4e77445
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+
+import os
+import logging
+
+try:
+       from xdg import BaseDirectory as _BaseDirectory
+       BaseDirectory = _BaseDirectory
+except ImportError:
+       BaseDirectory = None
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+_libc = None
+
+
+def set_process_name(name):
+       try: # change process name for killall
+               global _libc
+               if _libc is None:
+                       import ctypes
+                       _libc = ctypes.CDLL('libc.so.6')
+               _libc.prctl(15, name, 0, 0, 0)
+       except Exception, e:
+               _moduleLogger.warning('Unable to set processName: %s" % e')
+
+
+def get_new_resource(resourceType, resource, name):
+       if BaseDirectory is not None:
+               if resourceType == "data":
+                       base = BaseDirectory.xdg_data_home
+                       if base == "/usr/share/mime":
+                               # Ugly hack because somehow Maemo 4.1 seems to be set to this
+                               base = os.path.join(os.path.expanduser("~"), ".%s" % resource)
+               elif resourceType == "config":
+                       base = BaseDirectory.xdg_config_home
+               elif resourceType == "cache":
+                       base = BaseDirectory.xdg_cache_home
+               else:
+                       raise RuntimeError("Unknown type: "+resourceType)
+       else:
+               base = os.path.join(os.path.expanduser("~"), ".%s" % resource)
+
+       filePath = os.path.join(base, resource, name)
+       dirPath = os.path.dirname(filePath)
+       if not os.path.exists(dirPath):
+               # Looking before I leap to not mask errors
+               os.makedirs(dirPath)
+
+       return filePath
+
+
+def get_existing_resource(resourceType, resource, name):
+       if BaseDirectory is not None:
+               if resourceType == "data":
+                       base = BaseDirectory.xdg_data_home
+               elif resourceType == "config":
+                       base = BaseDirectory.xdg_config_home
+               elif resourceType == "cache":
+                       base = BaseDirectory.xdg_cache_home
+               else:
+                       raise RuntimeError("Unknown type: "+resourceType)
+       else:
+               base = None
+
+       if base is not None:
+               finalPath = os.path.join(base, name)
+               if os.path.exists(finalPath):
+                       return finalPath
+
+       altBase = os.path.join(os.path.expanduser("~"), ".%s" % resource)
+       finalPath = os.path.join(altBase, name)
+       if os.path.exists(finalPath):
+               return finalPath
+       else:
+               raise RuntimeError("Resource not found: %r" % ((resourceType, resource, name), ))
diff --git a/ejpi/util/misc.py b/ejpi/util/misc.py
new file mode 100644 (file)
index 0000000..9b8d88c
--- /dev/null
@@ -0,0 +1,900 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+
+import sys
+import re
+import cPickle
+
+import functools
+import contextlib
+import inspect
+
+import optparse
+import traceback
+import warnings
+import string
+
+
+class AnyData(object):
+
+       pass
+
+
+_indentationLevel = [0]
+
+
+def log_call(logger):
+
+       def log_call_decorator(func):
+
+               @functools.wraps(func)
+               def wrapper(*args, **kwds):
+                       logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, ))
+                       _indentationLevel[0] += 1
+                       try:
+                               return func(*args, **kwds)
+                       finally:
+                               _indentationLevel[0] -= 1
+                               logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, ))
+
+               return wrapper
+
+       return log_call_decorator
+
+
+def log_exception(logger):
+
+       def log_exception_decorator(func):
+
+               @functools.wraps(func)
+               def wrapper(*args, **kwds):
+                       try:
+                               return func(*args, **kwds)
+                       except Exception:
+                               logger.exception(func.__name__)
+                               raise
+
+               return wrapper
+
+       return log_exception_decorator
+
+
+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
+
+
+def into_debugger(func):
+       """
+       >>> validate_decorator(into_debugger)
+       """
+
+       @functools.wraps(func)
+       def newFunc(*args, **kwargs):
+               try:
+                       return func(*args, **kwargs)
+               except:
+                       import pdb
+                       pdb.post_mortem()
+
+       return newFunc
+
+
+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 nested_break():
+       """
+       >>> with nested_break() as mylabel:
+       ...     for i in xrange(3):
+       ...             print "Outer", i
+       ...             for j in xrange(3):
+       ...                     if i == 2: raise mylabel
+       ...                     if j == 2: break
+       ...                     print "Inner", j
+       ...             print "more processing"
+       Outer 0
+       Inner 0
+       Inner 1
+       Outer 1
+       Inner 0
+       Inner 1
+       Outer 2
+       """
+
+       class NestedBreakException(Exception):
+               pass
+
+       try:
+               yield NestedBreakException
+       except NestedBreakException:
+               pass
+
+
+@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
+
+
+def normalize_number(prettynumber):
+       """
+       function to take a phone number and strip out all non-numeric
+       characters
+
+       >>> normalize_number("+012-(345)-678-90")
+       '+01234567890'
+       >>> normalize_number("1-(345)-678-9000")
+       '+13456789000'
+       >>> normalize_number("+1-(345)-678-9000")
+       '+13456789000'
+       """
+       uglynumber = re.sub('[^0-9+]', '', prettynumber)
+       if uglynumber.startswith("+"):
+               pass
+       elif uglynumber.startswith("1"):
+               uglynumber = "+"+uglynumber
+       elif 10 <= len(uglynumber):
+               assert uglynumber[0] not in ("+", "1"), "Number format confusing"
+               uglynumber = "+1"+uglynumber
+       else:
+               pass
+
+       return uglynumber
+
+
+_VALIDATE_RE = re.compile("^\+?[0-9]{10,}$")
+
+
+def is_valid_number(number):
+       """
+       @returns If This number be called ( syntax validation only )
+       """
+       return _VALIDATE_RE.match(number) is not None
+
+
+def make_ugly(prettynumber):
+       """
+       function to take a phone number and strip out all non-numeric
+       characters
+
+       >>> make_ugly("+012-(345)-678-90")
+       '+01234567890'
+       """
+       return normalize_number(prettynumber)
+
+
+def _make_pretty_with_areacode(phonenumber):
+       prettynumber = "(%s)" % (phonenumber[0:3], )
+       if 3 < len(phonenumber):
+               prettynumber += " %s" % (phonenumber[3:6], )
+               if 6 < len(phonenumber):
+                       prettynumber += "-%s" % (phonenumber[6:], )
+       return prettynumber
+
+
+def _make_pretty_local(phonenumber):
+       prettynumber = "%s" % (phonenumber[0:3], )
+       if 3 < len(phonenumber):
+               prettynumber += "-%s" % (phonenumber[3:], )
+       return prettynumber
+
+
+def _make_pretty_international(phonenumber):
+       prettynumber = phonenumber
+       if phonenumber.startswith("1"):
+               prettynumber = "1 "
+               prettynumber += _make_pretty_with_areacode(phonenumber[1:])
+       return prettynumber
+
+
+def make_pretty(phonenumber):
+       """
+       Function to take a phone number and return the pretty version
+       pretty numbers:
+               if phonenumber begins with 0:
+                       ...-(...)-...-....
+               if phonenumber begins with 1: ( for gizmo callback numbers )
+                       1 (...)-...-....
+               if phonenumber is 13 digits:
+                       (...)-...-....
+               if phonenumber is 10 digits:
+                       ...-....
+       >>> make_pretty("12")
+       '12'
+       >>> make_pretty("1234567")
+       '123-4567'
+       >>> make_pretty("2345678901")
+       '+1 (234) 567-8901'
+       >>> make_pretty("12345678901")
+       '+1 (234) 567-8901'
+       >>> make_pretty("01234567890")
+       '+012 (345) 678-90'
+       >>> make_pretty("+01234567890")
+       '+012 (345) 678-90'
+       >>> make_pretty("+12")
+       '+1 (2)'
+       >>> make_pretty("+123")
+       '+1 (23)'
+       >>> make_pretty("+1234")
+       '+1 (234)'
+       """
+       if phonenumber is None or phonenumber == "":
+               return ""
+
+       phonenumber = normalize_number(phonenumber)
+
+       if phonenumber == "":
+               return ""
+       elif phonenumber[0] == "+":
+               prettynumber = _make_pretty_international(phonenumber[1:])
+               if not prettynumber.startswith("+"):
+                       prettynumber = "+"+prettynumber
+       elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
+               prettynumber = _make_pretty_international(phonenumber)
+       elif 7 < len(phonenumber):
+               prettynumber = _make_pretty_with_areacode(phonenumber)
+       elif 3 < len(phonenumber):
+               prettynumber = _make_pretty_local(phonenumber)
+       else:
+               prettynumber = phonenumber
+       return prettynumber.strip()
+
+
+def similar_ugly_numbers(lhs, rhs):
+       return (
+               lhs == rhs or
+               lhs[1:] == rhs and lhs.startswith("1") or
+               lhs[2:] == rhs and lhs.startswith("+1") or
+               lhs == rhs[1:] and rhs.startswith("1") or
+               lhs == rhs[2:] and rhs.startswith("+1")
+       )
+
+
+def abbrev_relative_date(date):
+       """
+       >>> abbrev_relative_date("42 hours ago")
+       '42 h'
+       >>> abbrev_relative_date("2 days ago")
+       '2 d'
+       >>> abbrev_relative_date("4 weeks ago")
+       '4 w'
+       """
+       parts = date.split(" ")
+       return "%s %s" % (parts[0], parts[1][0])
+
+
+def parse_version(versionText):
+       """
+       >>> parse_version("0.5.2")
+       [0, 5, 2]
+       """
+       return [
+               int(number)
+               for number in versionText.split(".")
+       ]
+
+
+def compare_versions(leftParsedVersion, rightParsedVersion):
+       """
+       >>> compare_versions([0, 1, 2], [0, 1, 2])
+       0
+       >>> compare_versions([0, 1, 2], [0, 1, 3])
+       -1
+       >>> compare_versions([0, 1, 2], [0, 2, 2])
+       -1
+       >>> compare_versions([0, 1, 2], [1, 1, 2])
+       -1
+       >>> compare_versions([0, 1, 3], [0, 1, 2])
+       1
+       >>> compare_versions([0, 2, 2], [0, 1, 2])
+       1
+       >>> compare_versions([1, 1, 2], [0, 1, 2])
+       1
+       """
+       for left, right in zip(leftParsedVersion, rightParsedVersion):
+               if left < right:
+                       return -1
+               elif right < left:
+                       return 1
+       else:
+               return 0
diff --git a/ejpi/util/overloading.py b/ejpi/util/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/ejpi/util/qml_utils.py b/ejpi/util/qml_utils.py
new file mode 100644 (file)
index 0000000..fbbca93
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""
+QML Tips:
+       Large images:
+               QML asynchronous = true; cache = false; [1]
+       Insert properties at top of element declarations [1]
+       Non-visible items: set opacity to 0 [2]
+       Use Loader [1]
+       Keep QML files small [1]
+
+[1] http://sf2011.meego.com/program/sessions/performance-tips-and-tricks-qtqml-applications-0
+[2] http://doc.qt.nokia.com/4.7/qdeclarativeperformance.html
+"""
+
+from __future__ import with_statement
+from __future__ import division
+
+import logging
+
+import util.qt_compat as qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+QtDeclarative = qt_compat.import_module("QtDeclarative")
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class DeclarativeView(QtDeclarative.QDeclarativeView):
+
+       def __init__(self):
+               QtDeclarative.QDeclarativeView.__init__(self)
+
+       closing = qt_compat.Signal()
+
+       def closeEvent(self, event):
+               self.closing.emit()
+               event.ignore()
+
+
+def disable_default_window_painting(view):
+       """
+       See http://doc.qt.nokia.com/4.7-snapshot/qdeclarativeperformance.html
+       """
+       view.setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
+       view.setAttribute(QtCore.Qt.WA_NoSystemBackground)
+       view.viewport().setAttribute(QtCore.Qt.WA_OpaquePaintEvent)
+       view.viewport().setAttribute(QtCore.Qt.WA_NoSystemBackground)
+
+
+if __name__ == "__main__":
+       pass
+
diff --git a/ejpi/util/qore_utils.py b/ejpi/util/qore_utils.py
new file mode 100755 (executable)
index 0000000..8f085ac
--- /dev/null
@@ -0,0 +1,422 @@
+#!/usr/bin/env python
+
+import contextlib
+import logging
+
+import qt_compat
+QtCore = qt_compat.QtCore
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class QThread44(QtCore.QThread):
+       """
+       This is to imitate QThread in Qt 4.4+ for when running on older version
+       See http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong
+       (On Lucid I have Qt 4.7 and this is still an issue)
+       """
+
+       def __init__(self, parent = None):
+               QtCore.QThread.__init__(self, parent)
+
+       def run(self):
+               self.exec_()
+
+
+class _WorkerThread(QtCore.QObject):
+
+       _taskComplete  = qt_compat.Signal(object)
+
+       def __init__(self, futureThread):
+               QtCore.QObject.__init__(self)
+               self._futureThread = futureThread
+               self._futureThread._addTask.connect(self._on_task_added)
+               self._taskComplete.connect(self._futureThread._on_task_complete)
+
+       @qt_compat.Slot(object)
+       def _on_task_added(self, task):
+               self.__on_task_added(task)
+
+       @misc.log_exception(_moduleLogger)
+       def __on_task_added(self, task):
+               if not self._futureThread._isRunning:
+                       _moduleLogger.error("Dropping task")
+
+               func, args, kwds, on_success, on_error = task
+
+               try:
+                       result = func(*args, **kwds)
+                       isError = False
+               except Exception, e:
+                       _moduleLogger.error("Error, passing it back to the main thread")
+                       result = e
+                       isError = True
+
+               taskResult = on_success, on_error, isError, result
+               self._taskComplete.emit(taskResult)
+
+
+class FutureThread(QtCore.QObject):
+
+       _addTask = qt_compat.Signal(object)
+
+       def __init__(self):
+               QtCore.QObject.__init__(self)
+               self._thread = QThread44()
+               self._isRunning = False
+               self._worker = _WorkerThread(self)
+               self._worker.moveToThread(self._thread)
+
+       def start(self):
+               self._thread.start()
+               self._isRunning = True
+
+       def stop(self):
+               self._isRunning = False
+               self._thread.quit()
+
+       def add_task(self, func, args, kwds, on_success, on_error):
+               assert self._isRunning, "Task queue not started"
+               task = func, args, kwds, on_success, on_error
+               self._addTask.emit(task)
+
+       @qt_compat.Slot(object)
+       def _on_task_complete(self, taskResult):
+               self.__on_task_complete(taskResult)
+
+       @misc.log_exception(_moduleLogger)
+       def __on_task_complete(self, taskResult):
+               on_success, on_error, isError, result = taskResult
+               if not self._isRunning:
+                       if isError:
+                               _moduleLogger.error("Masking: %s" % (result, ))
+                       isError = True
+                       result = StopIteration("Cancelling all callbacks")
+               callback = on_success if not isError else on_error
+               try:
+                       callback(result)
+               except Exception:
+                       _moduleLogger.exception("Callback errored")
+
+
+def create_single_column_list_model(columnName, **kwargs):
+       """
+       >>> class Single(object): pass
+       >>> SingleListModel = create_single_column_list_model("s")
+       >>> slm = SingleListModel([Single(), Single(), Single()])
+       """
+
+       class SingleColumnListModel(QtCore.QAbstractListModel):
+
+               def __init__(self, l = None):
+                       QtCore.QAbstractListModel.__init__(self)
+                       self._list = l if l is not None else []
+                       self.setRoleNames({0: columnName})
+
+               def __len__(self):
+                       return len(self._list)
+
+               def __getitem__(self, key):
+                       return self._list[key]
+
+               def __setitem__(self, key, value):
+                       with scoped_model_reset(self):
+                               self._list[key] = value
+
+               def __delitem__(self, key):
+                       with scoped_model_reset(self):
+                               del self._list[key]
+
+               def __iter__(self):
+                       return iter(self._list)
+
+               def __repr__(self):
+                       return '<%s (%s)>' % (
+                               self.__class__.__name__,
+                               columnName,
+                       )
+
+               def rowCount(self, parent=QtCore.QModelIndex()):
+                       return len(self._list)
+
+               def data(self, index, role):
+                       if index.isValid() and role == 0:
+                               return self._list[index.row()]
+                       return None
+
+       if "name" in kwargs:
+               SingleColumnListModel.__name__ = kwargs["name"]
+
+       return SingleColumnListModel
+
+
+def create_tupled_list_model(*columnNames, **kwargs):
+       """
+       >>> class Column0(object): pass
+       >>> class Column1(object): pass
+       >>> class Column2(object): pass
+       >>> MultiColumnedListModel = create_tupled_list_model("c0", "c1", "c2")
+       >>> mclm = MultiColumnedListModel([(Column0(), Column1(), Column2())])
+       """
+
+       class TupledListModel(QtCore.QAbstractListModel):
+
+               def __init__(self, l = None):
+                       QtCore.QAbstractListModel.__init__(self)
+                       self._list = l if l is not None else []
+                       self.setRoleNames(dict(enumerate(columnNames)))
+
+               def __len__(self):
+                       return len(self._list)
+
+               def __getitem__(self, key):
+                       return self._list[key]
+
+               def __setitem__(self, key, value):
+                       with scoped_model_reset(self):
+                               self._list[key] = value
+
+               def __delitem__(self, key):
+                       with scoped_model_reset(self):
+                               del self._list[key]
+
+               def __iter__(self):
+                       return iter(self._list)
+
+               def __repr__(self):
+                       return '<%s (%s)>' % (
+                               self.__class__.__name__,
+                               ', '.join(columnNames),
+                       )
+
+               def rowCount(self, parent=QtCore.QModelIndex()):
+                       return len(self._list)
+
+               def data(self, index, role):
+                       if index.isValid() and 0 <= role and role < len(columnNames):
+                               return self._list[index.row()][role]
+                       return None
+
+       if "name" in kwargs:
+               TupledListModel.__name__ = kwargs["name"]
+
+       return TupledListModel
+
+
+class FileSystemModel(QtCore.QAbstractListModel):
+       """
+       Wrapper around QtGui.QFileSystemModel
+       """
+
+       FILEINFOS = [
+               "fileName",
+               "isDir",
+               "filePath",
+               "completeSuffix",
+               "baseName",
+       ]
+
+       EXTINFOS = [
+               "type",
+       ]
+
+       ALLINFOS = FILEINFOS + EXTINFOS
+
+       def __init__(self, model, path):
+               QtCore.QAbstractListModel.__init__(self)
+               self._path = path
+
+               self._model = model
+               self._rootIndex = self._model.index(self._path)
+
+               self._child = None
+               self.setRoleNames(dict(enumerate(self.ALLINFOS)))
+               self._model.directoryLoaded.connect(self._on_directory_loaded)
+
+       childChanged = QtCore.Signal(QtCore.QObject)
+
+       def _child(self):
+               assert self._child is not None
+               return self._child
+
+       child = QtCore.Property(QtCore.QObject, _child, notify=childChanged)
+
+       backendChanged = QtCore.Signal()
+
+       def _parent(self):
+               finfo = self._model.fileInfo(self._rootIndex)
+               return finfo.fileName()
+
+       parent = QtCore.Property(str, _parent, notify=backendChanged)
+
+       @QtCore.Slot(str)
+       def browse_to(self, path):
+               if self._child is None:
+                       self._child = FileSystemModel(self._model, path)
+               else:
+                       self._child.switch_to(path)
+               self.childChanged.emit()
+               return self._child
+
+       @QtCore.Slot(str)
+       def switch_to(self, path):
+               with scoped_model_reset(self):
+                       self._path = path
+                       self._rootIndex = self._model.index(self._path)
+               self.backendChanged.emit()
+
+       def __len__(self):
+               return self._model.rowCount(self._rootIndex)
+
+       def __getitem__(self, key):
+               return self._model.index(key, 0, self._rootIndex)
+
+       def __iter__(self):
+               return (self[i] for i in xrange(len(self)))
+
+       def rowCount(self, parent=QtCore.QModelIndex()):
+               return len(self)
+
+       def data(self, index, role):
+               if index.isValid() and 0 <= role and role < len(self.ALLINFOS):
+                       internalIndex = self._translate_index(index)
+                       info = self._model.fileInfo(internalIndex)
+                       if role < len(self.FILEINFOS):
+                               field = self.FILEINFOS[role]
+                               value = getattr(info, field)()
+                       else:
+                               role -= len(self.FILEINFOS)
+                               field = self.EXTINFOS[role]
+                               if field == "type":
+                                       return self._model.type(internalIndex)
+                               else:
+                                       raise NotImplementedError("Out of range that was already checked")
+                       return value
+               return None
+
+       def _on_directory_loaded(self, path):
+               if self._path == path:
+                       self.backendChanged.emit()
+                       self.reset()
+
+       def _translate_index(self, externalIndex):
+               internalIndex = self._model.index(externalIndex.row(), 0, self._rootIndex)
+               return internalIndex
+
+
+@contextlib.contextmanager
+def scoped_model_reset(model):
+       model.beginResetModel()
+       try:
+               yield
+       finally:
+               model.endResetModel()
+
+
+def create_qobject(*classDef, **kwargs):
+       """
+       >>> Car = create_qobject(
+       ...     ('model', str),
+       ...     ('brand', str),
+       ...     ('year', int),
+       ...     ('inStock', bool),
+       ...     name='Car'
+       ... )
+       >>> print Car
+       <class '__main__.AutoQObject'>
+       >>>  
+       >>> c = Car(model='Fiesta', brand='Ford', year=1337)
+       >>> print c.model, c.brand, c.year, c.inStock
+       Fiesta Ford 1337 False
+       >>> print c
+       <Car (model='Fiesta', brand='Ford', year=1337, inStock=False)>
+       >>>  
+       >>> c.inStock = True
+       >>>  
+       >>> print c.model, c.brand, c.year, c.inStock
+       Fiesta Ford 1337 True
+       >>> print c
+       <Car (model='Fiesta', brand='Ford', year=1337, inStock=True)>
+       """
+
+       class AutoQObject(QtCore.QObject):
+
+               def __init__(self, **initKwargs):
+                       QtCore.QObject.__init__(self)
+                       for key, val in classDef:
+                               setattr(self, '_'+key, initKwargs.get(key, val()))
+
+               def __repr__(self):
+                       values = (
+                               '%s=%r' % (key, getattr(self, '_'+key))
+                               for key, value in classDef
+                       )
+                       return '<%s (%s)>' % (
+                               kwargs.get('name', self.__class__.__name__),
+                               ', '.join(values),
+                       )
+
+               for key, value in classDef:
+                       nfy = locals()['_nfy_'+key] = QtCore.Signal()
+
+                       def _get(key):
+                               def f(self):
+                                       return self.__dict__['_'+key]
+                               return f
+
+                       def _set(key):
+                               def f(self, value):
+                                       setattr(self, '_'+key, value)
+                                       getattr(self, '_nfy_'+key).emit()
+                               return f
+
+                       setter = locals()['_set_'+key] = _set(key)
+                       getter = locals()['_get_'+key] = _get(key)
+
+                       locals()[key] = QtCore.Property(value, getter, setter, notify=nfy)
+               del nfy, _get, _set, getter, setter
+
+       return AutoQObject
+
+
+class QObjectProxy(object):
+       """
+       Proxy for accessing properties and slots as attributes
+
+       This class acts as a proxy for the object for which it is
+       created, and makes property access more Pythonic while
+       still allowing access to slots (as member functions).
+
+       Attribute names starting with '_' are not proxied.
+       """
+
+       def __init__(self, rootObject):
+               self._rootObject = rootObject
+               m = self._rootObject.metaObject()
+               self._properties = [
+                       m.property(i).name()
+                       for i in xrange(m.propertyCount())
+               ]
+
+       def __getattr__(self, key):
+               value = self._rootObject.property(key)
+
+               # No such property, so assume we call a slot
+               if value is None and key not in self._properties:
+                       return getattr(self._rootObject, key)
+
+               return value
+
+       def __setattr__(self, key, value):
+               if key.startswith('_'):
+                       object.__setattr__(self, key, value)
+               else:
+                       self._rootObject.setProperty(key, value)
+
+
+if __name__ == "__main__":
+       import doctest
+       print doctest.testmod()
diff --git a/ejpi/util/qt_compat.py b/ejpi/util/qt_compat.py
new file mode 100644 (file)
index 0000000..3ab17c8
--- /dev/null
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+from __future__ import division
+
+try:
+       import PySide.QtCore as _QtCore
+       QtCore = _QtCore
+       USES_PYSIDE = True
+except ImportError:
+       import sip
+       sip.setapi('QString', 2)
+       sip.setapi('QVariant', 2)
+       import PyQt4.QtCore as _QtCore
+       QtCore = _QtCore
+       USES_PYSIDE = False
+
+
+def _pyside_import_module(moduleName):
+       pyside = __import__('PySide', globals(), locals(), [moduleName], -1)
+       return getattr(pyside, moduleName)
+
+
+def _pyqt4_import_module(moduleName):
+       pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1)
+       return getattr(pyside, moduleName)
+
+
+if USES_PYSIDE:
+       import_module = _pyside_import_module
+
+       Signal = QtCore.Signal
+       Slot = QtCore.Slot
+       Property = QtCore.Property
+else:
+       import_module = _pyqt4_import_module
+
+       Signal = QtCore.pyqtSignal
+       Slot = QtCore.pyqtSlot
+       Property = QtCore.pyqtProperty
+
+
+if __name__ == "__main__":
+       pass
+
diff --git a/ejpi/util/qtpie.py b/ejpi/util/qtpie.py
new file mode 100755 (executable)
index 0000000..6b77d5d
--- /dev/null
@@ -0,0 +1,1094 @@
+#!/usr/bin/env python
+
+import math
+import logging
+
+import qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+import misc as misc_utils
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+_TWOPI = 2 * math.pi
+
+
+def _radius_at(center, pos):
+       delta = pos - center
+       xDelta = delta.x()
+       yDelta = delta.y()
+
+       radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
+       return radius
+
+
+def _angle_at(center, pos):
+       delta = pos - center
+       xDelta = delta.x()
+       yDelta = delta.y()
+
+       radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
+       angle = math.acos(xDelta / radius)
+       if 0 <= yDelta:
+               angle = _TWOPI - angle
+
+       return angle
+
+
+class QActionPieItem(object):
+
+       def __init__(self, action, weight = 1):
+               self._action = action
+               self._weight = weight
+
+       def action(self):
+               return self._action
+
+       def setWeight(self, weight):
+               self._weight = weight
+
+       def weight(self):
+               return self._weight
+
+       def setEnabled(self, enabled = True):
+               self._action.setEnabled(enabled)
+
+       def isEnabled(self):
+               return self._action.isEnabled()
+
+
+class PieFiling(object):
+
+       INNER_RADIUS_DEFAULT = 64
+       OUTER_RADIUS_DEFAULT = 192
+
+       SELECTION_CENTER = -1
+       SELECTION_NONE = -2
+
+       NULL_CENTER = QActionPieItem(QtGui.QAction(None))
+
+       def __init__(self):
+               self._innerRadius = self.INNER_RADIUS_DEFAULT
+               self._outerRadius = self.OUTER_RADIUS_DEFAULT
+               self._children = []
+               self._center = self.NULL_CENTER
+
+               self._cacheIndexToAngle = {}
+               self._cacheTotalWeight = 0
+
+       def insertItem(self, item, index = -1):
+               self._children.insert(index, item)
+               self._invalidate_cache()
+
+       def removeItemAt(self, index):
+               item = self._children.pop(index)
+               self._invalidate_cache()
+
+       def set_center(self, item):
+               if item is None:
+                       item = self.NULL_CENTER
+               self._center = item
+
+       def center(self):
+               return self._center
+
+       def clear(self):
+               del self._children[:]
+               self._center = self.NULL_CENTER
+               self._invalidate_cache()
+
+       def itemAt(self, index):
+               return self._children[index]
+
+       def indexAt(self, center, point):
+               return self._angle_to_index(_angle_at(center, point))
+
+       def innerRadius(self):
+               return self._innerRadius
+
+       def setInnerRadius(self, radius):
+               self._innerRadius = radius
+
+       def outerRadius(self):
+               return self._outerRadius
+
+       def setOuterRadius(self, radius):
+               self._outerRadius = radius
+
+       def __iter__(self):
+               return iter(self._children)
+
+       def __len__(self):
+               return len(self._children)
+
+       def __getitem__(self, index):
+               return self._children[index]
+
+       def _invalidate_cache(self):
+               self._cacheIndexToAngle.clear()
+               self._cacheTotalWeight = sum(child.weight() for child in self._children)
+               if self._cacheTotalWeight == 0:
+                       self._cacheTotalWeight = 1
+
+       def _index_to_angle(self, index, isShifted):
+               key = index, isShifted
+               if key in self._cacheIndexToAngle:
+                       return self._cacheIndexToAngle[key]
+               index = index % len(self._children)
+
+               baseAngle = _TWOPI / self._cacheTotalWeight
+
+               angle = math.pi / 2
+               if isShifted:
+                       if self._children:
+                               angle -= (self._children[0].weight() * baseAngle) / 2
+                       else:
+                               angle -= baseAngle / 2
+               while angle < 0:
+                       angle += _TWOPI
+
+               for i, child in enumerate(self._children):
+                       if index < i:
+                               break
+                       angle += child.weight() * baseAngle
+               while _TWOPI < angle:
+                       angle -= _TWOPI
+
+               self._cacheIndexToAngle[key] = angle
+               return angle
+
+       def _angle_to_index(self, angle):
+               numChildren = len(self._children)
+               if numChildren == 0:
+                       return self.SELECTION_CENTER
+
+               baseAngle = _TWOPI / self._cacheTotalWeight
+
+               iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
+               while iterAngle < 0:
+                       iterAngle += _TWOPI
+
+               oldIterAngle = iterAngle
+               for index, child in enumerate(self._children):
+                       iterAngle += child.weight() * baseAngle
+                       if oldIterAngle < angle and angle <= iterAngle:
+                               return index - 1 if index != 0 else numChildren - 1
+                       elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
+                               return index - 1 if index != 0 else numChildren - 1
+                       oldIterAngle = iterAngle
+
+
+class PieArtist(object):
+
+       ICON_SIZE_DEFAULT = 48
+
+       SHAPE_CIRCLE = "circle"
+       SHAPE_SQUARE = "square"
+       DEFAULT_SHAPE = SHAPE_SQUARE
+
+       BACKGROUND_FILL = "fill"
+       BACKGROUND_NOFILL = "no fill"
+
+       def __init__(self, filing, background = BACKGROUND_FILL):
+               self._filing = filing
+
+               self._cachedOuterRadius = self._filing.outerRadius()
+               self._cachedInnerRadius = self._filing.innerRadius()
+               canvasSize = self._cachedOuterRadius * 2 + 1
+               self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
+               self._mask = None
+               self._backgroundState = background
+               self.palette = None
+
+       def pieSize(self):
+               diameter = self._filing.outerRadius() * 2 + 1
+               return QtCore.QSize(diameter, diameter)
+
+       def centerSize(self):
+               painter = QtGui.QPainter(self._canvas)
+               text = self._filing.center().action().text()
+               fontMetrics = painter.fontMetrics()
+               if text:
+                       textBoundingRect = fontMetrics.boundingRect(text)
+               else:
+                       textBoundingRect = QtCore.QRect()
+               textWidth = textBoundingRect.width()
+               textHeight = textBoundingRect.height()
+
+               return QtCore.QSize(
+                       textWidth + self.ICON_SIZE_DEFAULT,
+                       max(textHeight, self.ICON_SIZE_DEFAULT),
+               )
+
+       def show(self, palette):
+               self.palette = palette
+
+               if (
+                       self._cachedOuterRadius != self._filing.outerRadius() or
+                       self._cachedInnerRadius != self._filing.innerRadius()
+               ):
+                       self._cachedOuterRadius = self._filing.outerRadius()
+                       self._cachedInnerRadius = self._filing.innerRadius()
+                       self._canvas = self._canvas.scaled(self.pieSize())
+
+               if self._mask is None:
+                       self._mask = QtGui.QBitmap(self._canvas.size())
+                       self._mask.fill(QtCore.Qt.color0)
+                       self._generate_mask(self._mask)
+                       self._canvas.setMask(self._mask)
+               return self._mask
+
+       def hide(self):
+               self.palette = None
+
+       def paint(self, selectionIndex):
+               painter = QtGui.QPainter(self._canvas)
+               painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
+
+               self.paintPainter(selectionIndex, painter)
+
+               return self._canvas
+
+       def paintPainter(self, selectionIndex, painter):
+               adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
+
+               numChildren = len(self._filing)
+               if numChildren == 0:
+                       self._paint_center_background(painter, adjustmentRect, selectionIndex)
+                       self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
+                       return self._canvas
+               else:
+                       for i in xrange(len(self._filing)):
+                               self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
+
+               self._paint_center_background(painter, adjustmentRect, selectionIndex)
+               self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
+
+               for i in xrange(len(self._filing)):
+                       self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
+
+       def _generate_mask(self, mask):
+               """
+               Specifies on the mask the shape of the pie menu
+               """
+               painter = QtGui.QPainter(mask)
+               painter.setPen(QtCore.Qt.color1)
+               painter.setBrush(QtCore.Qt.color1)
+               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                       painter.drawRect(mask.rect())
+               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                       painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
+               else:
+                       raise NotImplementedError(self.DEFAULT_SHAPE)
+
+       def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
+               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                       currentWidth = adjustmentRect.width()
+                       newWidth = math.sqrt(2) * currentWidth
+                       dx = (newWidth - currentWidth) / 2
+                       adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
+               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                       pass
+               else:
+                       raise NotImplementedError(self.DEFAULT_SHAPE)
+
+               if self._backgroundState == self.BACKGROUND_NOFILL:
+                       painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
+                       painter.setPen(self.palette.highlight().color())
+               else:
+                       if i == selectionIndex and self._filing[i].isEnabled():
+                               painter.setBrush(self.palette.highlight())
+                               painter.setPen(self.palette.highlight().color())
+                       else:
+                               painter.setBrush(self.palette.window())
+                               painter.setPen(self.palette.window().color())
+
+               a = self._filing._index_to_angle(i, True)
+               b = self._filing._index_to_angle(i + 1, True)
+               if b < a:
+                       b += _TWOPI
+               size = b - a
+               if size < 0:
+                       size += _TWOPI
+
+               startAngleInDeg = (a * 360 * 16) / _TWOPI
+               sizeInDeg = (size * 360 * 16) / _TWOPI
+               painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
+
+       def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
+               child = self._filing[i]
+
+               a = self._filing._index_to_angle(i, True)
+               b = self._filing._index_to_angle(i + 1, True)
+               if b < a:
+                       b += _TWOPI
+               middleAngle = (a + b) / 2
+               averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
+
+               sliceX = averageRadius * math.cos(middleAngle)
+               sliceY = - averageRadius * math.sin(middleAngle)
+
+               piePos = adjustmentRect.center()
+               pieX = piePos.x()
+               pieY = piePos.y()
+               self._paint_label(
+                       painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
+               )
+
+       def _paint_label(self, painter, action, isSelected, x, y):
+               text = action.text()
+               fontMetrics = painter.fontMetrics()
+               if text:
+                       textBoundingRect = fontMetrics.boundingRect(text)
+               else:
+                       textBoundingRect = QtCore.QRect()
+               textWidth = textBoundingRect.width()
+               textHeight = textBoundingRect.height()
+
+               icon = action.icon().pixmap(
+                       QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
+                       QtGui.QIcon.Normal,
+                       QtGui.QIcon.On,
+               )
+               iconWidth = icon.width()
+               iconHeight = icon.width()
+               averageWidth = (iconWidth + textWidth)/2
+               if not icon.isNull():
+                       iconRect = QtCore.QRect(
+                               x - averageWidth,
+                               y - iconHeight/2,
+                               iconWidth,
+                               iconHeight,
+                       )
+
+                       painter.drawPixmap(iconRect, icon)
+
+               if text:
+                       if isSelected:
+                               if action.isEnabled():
+                                       pen = self.palette.highlightedText()
+                                       brush = self.palette.highlight()
+                               else:
+                                       pen = self.palette.mid()
+                                       brush = self.palette.window()
+                       else:
+                               if action.isEnabled():
+                                       pen = self.palette.windowText()
+                               else:
+                                       pen = self.palette.mid()
+                               brush = self.palette.window()
+
+                       leftX = x - averageWidth + iconWidth
+                       topY = y + textHeight/2
+                       painter.setPen(pen.color())
+                       painter.setBrush(brush)
+                       painter.drawText(leftX, topY, text)
+
+       def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
+               if self._backgroundState == self.BACKGROUND_NOFILL:
+                       return
+               if len(self._filing) == 0:
+                       if self._backgroundState == self.BACKGROUND_NOFILL:
+                               painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
+                       else:
+                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
+                                       painter.setBrush(self.palette.highlight())
+                               else:
+                                       painter.setBrush(self.palette.window())
+                       painter.setPen(self.palette.mid().color())
+
+                       painter.drawRect(adjustmentRect)
+               else:
+                       dark = self.palette.mid().color()
+                       light = self.palette.light().color()
+                       if self._backgroundState == self.BACKGROUND_NOFILL:
+                               background = QtGui.QBrush(QtCore.Qt.transparent)
+                       else:
+                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
+                                       background = self.palette.highlight().color()
+                               else:
+                                       background = self.palette.window().color()
+
+                       innerRadius = self._cachedInnerRadius
+                       adjustmentCenterPos = adjustmentRect.center()
+                       innerRect = QtCore.QRect(
+                               adjustmentCenterPos.x() - innerRadius,
+                               adjustmentCenterPos.y() - innerRadius,
+                               innerRadius * 2 + 1,
+                               innerRadius * 2 + 1,
+                       )
+
+                       painter.setPen(QtCore.Qt.NoPen)
+                       painter.setBrush(background)
+                       painter.drawPie(innerRect, 0, 360 * 16)
+
+                       if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
+                               pass
+                       elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
+                               painter.setPen(QtGui.QPen(dark, 1))
+                               painter.setBrush(QtCore.Qt.NoBrush)
+                               painter.drawEllipse(adjustmentRect)
+                       else:
+                               raise NotImplementedError(self.DEFAULT_SHAPE)
+
+       def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
+               centerPos = adjustmentRect.center()
+               pieX = centerPos.x()
+               pieY = centerPos.y()
+
+               x = pieX
+               y = pieY
+
+               self._paint_label(
+                       painter,
+                       self._filing.center().action(),
+                       selectionIndex == PieFiling.SELECTION_CENTER,
+                       x, y
+               )
+
+
+class QPieDisplay(QtGui.QWidget):
+
+       def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
+               QtGui.QWidget.__init__(self, parent, flags)
+               self._filing = filing
+               self._artist = PieArtist(self._filing)
+               self._selectionIndex = PieFiling.SELECTION_NONE
+
+       def popup(self, pos):
+               self._update_selection(pos)
+               self.show()
+
+       def sizeHint(self):
+               return self._artist.pieSize()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def showEvent(self, showEvent):
+               mask = self._artist.show(self.palette())
+               self.setMask(mask)
+
+               QtGui.QWidget.showEvent(self, showEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def hideEvent(self, hideEvent):
+               self._artist.hide()
+               self._selectionIndex = PieFiling.SELECTION_NONE
+               QtGui.QWidget.hideEvent(self, hideEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def paintEvent(self, paintEvent):
+               canvas = self._artist.paint(self._selectionIndex)
+               offset = (self.size() - canvas.size()) / 2
+
+               screen = QtGui.QPainter(self)
+               screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
+
+               QtGui.QWidget.paintEvent(self, paintEvent)
+
+       def selectAt(self, index):
+               oldIndex = self._selectionIndex
+               self._selectionIndex = index
+               if self.isVisible():
+                       self.update()
+
+
+class QPieButton(QtGui.QWidget):
+
+       activated = qt_compat.Signal(int)
+       highlighted = qt_compat.Signal(int)
+       canceled = qt_compat.Signal()
+       aboutToShow = qt_compat.Signal()
+       aboutToHide = qt_compat.Signal()
+
+       BUTTON_RADIUS = 24
+       DELAY = 250
+
+       def __init__(self, buttonSlice, parent = None, buttonSlices = None):
+               # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
+               # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
+               QtGui.QWidget.__init__(self, parent)
+               self._cachedCenterPosition = self.rect().center()
+
+               self._filing = PieFiling()
+               self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
+               self._selectionIndex = PieFiling.SELECTION_NONE
+
+               self._buttonFiling = PieFiling()
+               self._buttonFiling.set_center(buttonSlice)
+               if buttonSlices is not None:
+                       for slice in buttonSlices:
+                               self._buttonFiling.insertItem(slice)
+               self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
+               self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
+               self._poppedUp = False
+               self._pressed = False
+
+               self._delayPopupTimer = QtCore.QTimer()
+               self._delayPopupTimer.setInterval(self.DELAY)
+               self._delayPopupTimer.setSingleShot(True)
+               self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
+               self._popupLocation = None
+
+               self._mousePosition = None
+               self.setFocusPolicy(QtCore.Qt.StrongFocus)
+               self.setSizePolicy(
+                       QtGui.QSizePolicy(
+                               QtGui.QSizePolicy.MinimumExpanding,
+                               QtGui.QSizePolicy.MinimumExpanding,
+                       )
+               )
+
+       def insertItem(self, item, index = -1):
+               self._filing.insertItem(item, index)
+
+       def removeItemAt(self, index):
+               self._filing.removeItemAt(index)
+
+       def set_center(self, item):
+               self._filing.set_center(item)
+
+       def set_button(self, item):
+               self.update()
+
+       def clear(self):
+               self._filing.clear()
+
+       def itemAt(self, index):
+               return self._filing.itemAt(index)
+
+       def indexAt(self, point):
+               return self._filing.indexAt(self._cachedCenterPosition, point)
+
+       def innerRadius(self):
+               return self._filing.innerRadius()
+
+       def setInnerRadius(self, radius):
+               self._filing.setInnerRadius(radius)
+
+       def outerRadius(self):
+               return self._filing.outerRadius()
+
+       def setOuterRadius(self, radius):
+               self._filing.setOuterRadius(radius)
+
+       def buttonRadius(self):
+               return self._buttonFiling.outerRadius()
+
+       def setButtonRadius(self, radius):
+               self._buttonFiling.setOuterRadius(radius)
+               self._buttonFiling.setInnerRadius(radius / 2)
+               self._buttonArtist.show(self.palette())
+
+       def minimumSizeHint(self):
+               return self._buttonArtist.centerSize()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mousePressEvent(self, mouseEvent):
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               self._mousePosition = lastMousePos
+               self._update_selection(self._cachedCenterPosition)
+
+               self.highlighted.emit(self._selectionIndex)
+
+               self._display.selectAt(self._selectionIndex)
+               self._pressed = True
+               self.update()
+               self._popupLocation = mouseEvent.globalPos()
+               self._delayPopupTimer.start()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_delayed_popup(self):
+               assert self._popupLocation is not None, "Widget location abuse"
+               self._popup_child(self._popupLocation)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mouseMoveEvent(self, mouseEvent):
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               if self._mousePosition is None:
+                       # Absolute
+                       self._update_selection(lastMousePos)
+               else:
+                       # Relative
+                       self._update_selection(
+                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
+                               ignoreOuter = True,
+                       )
+
+               if lastSelection != self._selectionIndex:
+                       self.highlighted.emit(self._selectionIndex)
+                       self._display.selectAt(self._selectionIndex)
+
+               if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
+                       self._on_delayed_popup()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mouseReleaseEvent(self, mouseEvent):
+               self._delayPopupTimer.stop()
+               self._popupLocation = None
+
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               if self._mousePosition is None:
+                       # Absolute
+                       self._update_selection(lastMousePos)
+               else:
+                       # Relative
+                       self._update_selection(
+                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
+                               ignoreOuter = True,
+                       )
+               self._mousePosition = None
+
+               self._activate_at(self._selectionIndex)
+               self._pressed = False
+               self.update()
+               self._hide_child()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def keyPressEvent(self, keyEvent):
+               if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
+                       self._popup_child(QtGui.QCursor.pos())
+                       if self._selectionIndex != len(self._filing) - 1:
+                               nextSelection = self._selectionIndex + 1
+                       else:
+                               nextSelection = 0
+                       self._select_at(nextSelection)
+                       self._display.selectAt(self._selectionIndex)
+               elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
+                       self._popup_child(QtGui.QCursor.pos())
+                       if 0 < self._selectionIndex:
+                               nextSelection = self._selectionIndex - 1
+                       else:
+                               nextSelection = len(self._filing) - 1
+                       self._select_at(nextSelection)
+                       self._display.selectAt(self._selectionIndex)
+               elif keyEvent.key() in [QtCore.Qt.Key_Space]:
+                       self._popup_child(QtGui.QCursor.pos())
+                       self._select_at(PieFiling.SELECTION_CENTER)
+                       self._display.selectAt(self._selectionIndex)
+               elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
+                       self._delayPopupTimer.stop()
+                       self._popupLocation = None
+                       self._activate_at(self._selectionIndex)
+                       self._hide_child()
+               elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
+                       self._delayPopupTimer.stop()
+                       self._popupLocation = None
+                       self._activate_at(PieFiling.SELECTION_NONE)
+                       self._hide_child()
+               else:
+                       QtGui.QWidget.keyPressEvent(self, keyEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def resizeEvent(self, resizeEvent):
+               self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
+               QtGui.QWidget.resizeEvent(self, resizeEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def showEvent(self, showEvent):
+               self._buttonArtist.show(self.palette())
+               self._cachedCenterPosition = self.rect().center()
+
+               QtGui.QWidget.showEvent(self, showEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def hideEvent(self, hideEvent):
+               self._display.hide()
+               self._select_at(PieFiling.SELECTION_NONE)
+               QtGui.QWidget.hideEvent(self, hideEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def paintEvent(self, paintEvent):
+               self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
+               if self._poppedUp:
+                       selectionIndex = PieFiling.SELECTION_CENTER
+               else:
+                       selectionIndex = PieFiling.SELECTION_NONE
+
+               screen = QtGui.QStylePainter(self)
+               screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
+               option = QtGui.QStyleOptionButton()
+               option.initFrom(self)
+               option.state = QtGui.QStyle.State_Sunken if self._pressed else QtGui.QStyle.State_Raised
+
+               screen.drawControl(QtGui.QStyle.CE_PushButton, option)
+               self._buttonArtist.paintPainter(selectionIndex, screen)
+
+               QtGui.QWidget.paintEvent(self, paintEvent)
+
+       def __iter__(self):
+               return iter(self._filing)
+
+       def __len__(self):
+               return len(self._filing)
+
+       def _popup_child(self, position):
+               self._poppedUp = True
+               self.aboutToShow.emit()
+
+               self._delayPopupTimer.stop()
+               self._popupLocation = None
+
+               position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
+               self._display.move(position)
+               self._display.show()
+
+               self.update()
+
+       def _hide_child(self):
+               self._poppedUp = False
+               self.aboutToHide.emit()
+               self._display.hide()
+               self.update()
+
+       def _select_at(self, index):
+               self._selectionIndex = index
+
+       def _update_selection(self, lastMousePos, ignoreOuter = False):
+               radius = _radius_at(self._cachedCenterPosition, lastMousePos)
+               if radius < self._filing.innerRadius():
+                       self._select_at(PieFiling.SELECTION_CENTER)
+               elif radius <= self._filing.outerRadius() or ignoreOuter:
+                       self._select_at(self.indexAt(lastMousePos))
+               else:
+                       self._select_at(PieFiling.SELECTION_NONE)
+
+       def _activate_at(self, index):
+               if index == PieFiling.SELECTION_NONE:
+                       self.canceled.emit()
+                       return
+               elif index == PieFiling.SELECTION_CENTER:
+                       child = self._filing.center()
+               else:
+                       child = self.itemAt(index)
+
+               if child.action().isEnabled():
+                       child.action().trigger()
+                       self.activated.emit(index)
+               else:
+                       self.canceled.emit()
+
+
+class QPieMenu(QtGui.QWidget):
+
+       activated = qt_compat.Signal(int)
+       highlighted = qt_compat.Signal(int)
+       canceled = qt_compat.Signal()
+       aboutToShow = qt_compat.Signal()
+       aboutToHide = qt_compat.Signal()
+
+       def __init__(self, parent = None):
+               QtGui.QWidget.__init__(self, parent)
+               self._cachedCenterPosition = self.rect().center()
+
+               self._filing = PieFiling()
+               self._artist = PieArtist(self._filing)
+               self._selectionIndex = PieFiling.SELECTION_NONE
+
+               self._mousePosition = ()
+               self.setFocusPolicy(QtCore.Qt.StrongFocus)
+
+       def popup(self, pos):
+               self._update_selection(pos)
+               self.show()
+
+       def insertItem(self, item, index = -1):
+               self._filing.insertItem(item, index)
+               self.update()
+
+       def removeItemAt(self, index):
+               self._filing.removeItemAt(index)
+               self.update()
+
+       def set_center(self, item):
+               self._filing.set_center(item)
+               self.update()
+
+       def clear(self):
+               self._filing.clear()
+               self.update()
+
+       def itemAt(self, index):
+               return self._filing.itemAt(index)
+
+       def indexAt(self, point):
+               return self._filing.indexAt(self._cachedCenterPosition, point)
+
+       def innerRadius(self):
+               return self._filing.innerRadius()
+
+       def setInnerRadius(self, radius):
+               self._filing.setInnerRadius(radius)
+               self.update()
+
+       def outerRadius(self):
+               return self._filing.outerRadius()
+
+       def setOuterRadius(self, radius):
+               self._filing.setOuterRadius(radius)
+               self.update()
+
+       def sizeHint(self):
+               return self._artist.pieSize()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mousePressEvent(self, mouseEvent):
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               self._update_selection(lastMousePos)
+               self._mousePosition = lastMousePos
+
+               if lastSelection != self._selectionIndex:
+                       self.highlighted.emit(self._selectionIndex)
+                       self.update()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mouseMoveEvent(self, mouseEvent):
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               self._update_selection(lastMousePos)
+
+               if lastSelection != self._selectionIndex:
+                       self.highlighted.emit(self._selectionIndex)
+                       self.update()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def mouseReleaseEvent(self, mouseEvent):
+               lastSelection = self._selectionIndex
+
+               lastMousePos = mouseEvent.pos()
+               self._update_selection(lastMousePos)
+               self._mousePosition = ()
+
+               self._activate_at(self._selectionIndex)
+               self.update()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def keyPressEvent(self, keyEvent):
+               if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
+                       if self._selectionIndex != len(self._filing) - 1:
+                               nextSelection = self._selectionIndex + 1
+                       else:
+                               nextSelection = 0
+                       self._select_at(nextSelection)
+                       self.update()
+               elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
+                       if 0 < self._selectionIndex:
+                               nextSelection = self._selectionIndex - 1
+                       else:
+                               nextSelection = len(self._filing) - 1
+                       self._select_at(nextSelection)
+                       self.update()
+               elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
+                       self._activate_at(self._selectionIndex)
+               elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
+                       self._activate_at(PieFiling.SELECTION_NONE)
+               else:
+                       QtGui.QWidget.keyPressEvent(self, keyEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def showEvent(self, showEvent):
+               self.aboutToShow.emit()
+               self._cachedCenterPosition = self.rect().center()
+
+               mask = self._artist.show(self.palette())
+               self.setMask(mask)
+
+               lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
+               self._update_selection(lastMousePos)
+
+               QtGui.QWidget.showEvent(self, showEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def hideEvent(self, hideEvent):
+               self._artist.hide()
+               self._selectionIndex = PieFiling.SELECTION_NONE
+               QtGui.QWidget.hideEvent(self, hideEvent)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def paintEvent(self, paintEvent):
+               canvas = self._artist.paint(self._selectionIndex)
+
+               screen = QtGui.QPainter(self)
+               screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
+
+               QtGui.QWidget.paintEvent(self, paintEvent)
+
+       def __iter__(self):
+               return iter(self._filing)
+
+       def __len__(self):
+               return len(self._filing)
+
+       def _select_at(self, index):
+               self._selectionIndex = index
+
+       def _update_selection(self, lastMousePos):
+               radius = _radius_at(self._cachedCenterPosition, lastMousePos)
+               if radius < self._filing.innerRadius():
+                       self._selectionIndex = PieFiling.SELECTION_CENTER
+               elif radius <= self._filing.outerRadius():
+                       self._select_at(self.indexAt(lastMousePos))
+               else:
+                       self._selectionIndex = PieFiling.SELECTION_NONE
+
+       def _activate_at(self, index):
+               if index == PieFiling.SELECTION_NONE:
+                       self.canceled.emit()
+                       self.aboutToHide.emit()
+                       self.hide()
+                       return
+               elif index == PieFiling.SELECTION_CENTER:
+                       child = self._filing.center()
+               else:
+                       child = self.itemAt(index)
+
+               if child.isEnabled():
+                       child.action().trigger()
+                       self.activated.emit(index)
+               else:
+                       self.canceled.emit()
+               self.aboutToHide.emit()
+               self.hide()
+
+
+def init_pies():
+       PieFiling.NULL_CENTER.setEnabled(False)
+
+
+def _print(msg):
+       print msg
+
+
+def _on_about_to_hide(app):
+       app.exit()
+
+
+if __name__ == "__main__":
+       app = QtGui.QApplication([])
+       init_pies()
+
+       if False:
+               pie = QPieMenu()
+               pie.show()
+
+       if False:
+               singleAction = QtGui.QAction(None)
+               singleAction.setText("Boo")
+               singleItem = QActionPieItem(singleAction)
+               spie = QPieMenu()
+               spie.insertItem(singleItem)
+               spie.show()
+
+       if False:
+               oneAction = QtGui.QAction(None)
+               oneAction.setText("Chew")
+               oneItem = QActionPieItem(oneAction)
+               twoAction = QtGui.QAction(None)
+               twoAction.setText("Foo")
+               twoItem = QActionPieItem(twoAction)
+               iconTextAction = QtGui.QAction(None)
+               iconTextAction.setText("Icon")
+               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
+               iconTextItem = QActionPieItem(iconTextAction)
+               mpie = QPieMenu()
+               mpie.insertItem(oneItem)
+               mpie.insertItem(twoItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(iconTextItem)
+               mpie.show()
+
+       if True:
+               oneAction = QtGui.QAction(None)
+               oneAction.setText("Chew")
+               oneAction.triggered.connect(lambda: _print("Chew"))
+               oneItem = QActionPieItem(oneAction)
+               twoAction = QtGui.QAction(None)
+               twoAction.setText("Foo")
+               twoAction.triggered.connect(lambda: _print("Foo"))
+               twoItem = QActionPieItem(twoAction)
+               iconAction = QtGui.QAction(None)
+               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
+               iconAction.triggered.connect(lambda: _print("Icon"))
+               iconItem = QActionPieItem(iconAction)
+               iconTextAction = QtGui.QAction(None)
+               iconTextAction.setText("Icon")
+               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
+               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
+               iconTextItem = QActionPieItem(iconTextAction)
+               mpie = QPieMenu()
+               mpie.set_center(iconItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(twoItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(iconTextItem)
+               mpie.show()
+               mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
+               mpie.canceled.connect(lambda: _print("Canceled"))
+
+       if False:
+               oneAction = QtGui.QAction(None)
+               oneAction.setText("Chew")
+               oneAction.triggered.connect(lambda: _print("Chew"))
+               oneItem = QActionPieItem(oneAction)
+               twoAction = QtGui.QAction(None)
+               twoAction.setText("Foo")
+               twoAction.triggered.connect(lambda: _print("Foo"))
+               twoItem = QActionPieItem(twoAction)
+               iconAction = QtGui.QAction(None)
+               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
+               iconAction.triggered.connect(lambda: _print("Icon"))
+               iconItem = QActionPieItem(iconAction)
+               iconTextAction = QtGui.QAction(None)
+               iconTextAction.setText("Icon")
+               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
+               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
+               iconTextItem = QActionPieItem(iconTextAction)
+               pieFiling = PieFiling()
+               pieFiling.set_center(iconItem)
+               pieFiling.insertItem(oneItem)
+               pieFiling.insertItem(twoItem)
+               pieFiling.insertItem(oneItem)
+               pieFiling.insertItem(iconTextItem)
+               mpie = QPieDisplay(pieFiling)
+               mpie.show()
+
+       if False:
+               oneAction = QtGui.QAction(None)
+               oneAction.setText("Chew")
+               oneAction.triggered.connect(lambda: _print("Chew"))
+               oneItem = QActionPieItem(oneAction)
+               twoAction = QtGui.QAction(None)
+               twoAction.setText("Foo")
+               twoAction.triggered.connect(lambda: _print("Foo"))
+               twoItem = QActionPieItem(twoAction)
+               iconAction = QtGui.QAction(None)
+               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
+               iconAction.triggered.connect(lambda: _print("Icon"))
+               iconItem = QActionPieItem(iconAction)
+               iconTextAction = QtGui.QAction(None)
+               iconTextAction.setText("Icon")
+               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
+               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
+               iconTextItem = QActionPieItem(iconTextAction)
+               mpie = QPieButton(iconItem)
+               mpie.set_center(iconItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(twoItem)
+               mpie.insertItem(oneItem)
+               mpie.insertItem(iconTextItem)
+               mpie.show()
+               mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
+               mpie.canceled.connect(lambda: _print("Canceled"))
+
+       app.exec_()
diff --git a/ejpi/util/qtpieboard.py b/ejpi/util/qtpieboard.py
new file mode 100755 (executable)
index 0000000..50ae9ae
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/env python
+
+
+from __future__ import division
+
+import os
+import warnings
+
+import qt_compat
+QtGui = qt_compat.import_module("QtGui")
+
+import qtpie
+
+
+class PieKeyboard(object):
+
+       SLICE_CENTER = -1
+       SLICE_NORTH = 0
+       SLICE_NORTH_WEST = 1
+       SLICE_WEST = 2
+       SLICE_SOUTH_WEST = 3
+       SLICE_SOUTH = 4
+       SLICE_SOUTH_EAST = 5
+       SLICE_EAST = 6
+       SLICE_NORTH_EAST = 7
+
+       MAX_ANGULAR_SLICES = 8
+
+       SLICE_DIRECTIONS = [
+               SLICE_CENTER,
+               SLICE_NORTH,
+               SLICE_NORTH_WEST,
+               SLICE_WEST,
+               SLICE_SOUTH_WEST,
+               SLICE_SOUTH,
+               SLICE_SOUTH_EAST,
+               SLICE_EAST,
+               SLICE_NORTH_EAST,
+       ]
+
+       SLICE_DIRECTION_NAMES = [
+               "CENTER",
+               "NORTH",
+               "NORTH_WEST",
+               "WEST",
+               "SOUTH_WEST",
+               "SOUTH",
+               "SOUTH_EAST",
+               "EAST",
+               "NORTH_EAST",
+       ]
+
+       def __init__(self):
+               self._layout = QtGui.QGridLayout()
+               self._widget = QtGui.QWidget()
+               self._widget.setLayout(self._layout)
+
+               self.__cells = {}
+
+       @property
+       def toplevel(self):
+               return self._widget
+
+       def add_pie(self, row, column, pieButton):
+               assert len(pieButton) == 8
+               self._layout.addWidget(pieButton, row, column)
+               self.__cells[(row, column)] = pieButton
+
+       def get_pie(self, row, column):
+               return self.__cells[(row, column)]
+
+
+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
+
+
+def parse_keyboard_data(text):
+       return eval(text)
+
+
+def _enumerate_pie_slices(pieData, iconPaths):
+       for direction, directionName in zip(
+               PieKeyboard.SLICE_DIRECTIONS, PieKeyboard.SLICE_DIRECTION_NAMES
+       ):
+               if directionName in pieData:
+                       sliceData = pieData[directionName]
+
+                       action = QtGui.QAction(None)
+                       try:
+                               action.setText(sliceData["text"])
+                       except KeyError:
+                               pass
+                       try:
+                               relativeIconPath = sliceData["path"]
+                       except KeyError:
+                               pass
+                       else:
+                               for iconPath in iconPaths:
+                                       absIconPath = os.path.join(iconPath, relativeIconPath)
+                                       if os.path.exists(absIconPath):
+                                               action.setIcon(QtGui.QIcon(absIconPath))
+                                               break
+                       pieItem = qtpie.QActionPieItem(action)
+                       actionToken = sliceData["action"]
+               else:
+                       pieItem = qtpie.PieFiling.NULL_CENTER
+                       actionToken = ""
+               yield direction, pieItem, actionToken
+
+
+def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler, iconPaths):
+       for (row, column), pieData in dataTree.iteritems():
+               pieItems = list(_enumerate_pie_slices(pieData, iconPaths))
+               assert pieItems[0][0] == PieKeyboard.SLICE_CENTER, pieItems[0]
+               _, center, centerAction = pieItems.pop(0)
+
+               pieButton = qtpie.QPieButton(center)
+               pieButton.set_center(center)
+               keyboardHandler.map_slice_action(center, centerAction)
+               for direction, pieItem, action in pieItems:
+                       pieButton.insertItem(pieItem)
+                       keyboardHandler.map_slice_action(pieItem, action)
+               keyboard.add_pie(row, column, pieButton)
+
+
+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 Look into hooking these up directly to the pie actions
+               self.__commandHandlers["[%s]" % command] = handler
+
+       def unregister_command_handler(self, command):
+               # @todo Look into hooking these up directly to the pie actions
+               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):
+               callback = lambda direction: self(direction, action)
+               slice.action().triggered.connect(callback)
+               self.__sliceActions[slice] = (action, callback)
+
+       def __call__(self, direction, action):
+               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/ejpi/util/qui_utils.py b/ejpi/util/qui_utils.py
new file mode 100644 (file)
index 0000000..11b3453
--- /dev/null
@@ -0,0 +1,419 @@
+import sys
+import contextlib
+import datetime
+import logging
+
+import qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+@contextlib.contextmanager
+def notify_error(log):
+       try:
+               yield
+       except:
+               log.push_exception()
+
+
+@contextlib.contextmanager
+def notify_busy(log, message):
+       log.push_busy(message)
+       try:
+               yield
+       finally:
+               log.pop(message)
+
+
+class ErrorMessage(object):
+
+       LEVEL_ERROR = 0
+       LEVEL_BUSY = 1
+       LEVEL_INFO = 2
+
+       def __init__(self, message, level):
+               self._message = message
+               self._level = level
+               self._time = datetime.datetime.now()
+
+       @property
+       def level(self):
+               return self._level
+
+       @property
+       def message(self):
+               return self._message
+
+       def __repr__(self):
+               return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level)
+
+
+class QErrorLog(QtCore.QObject):
+
+       messagePushed = qt_compat.Signal()
+       messagePopped = qt_compat.Signal()
+
+       def __init__(self):
+               QtCore.QObject.__init__(self)
+               self._messages = []
+
+       def push_busy(self, message):
+               _moduleLogger.info("Entering state: %s" % message)
+               self._push_message(message, ErrorMessage.LEVEL_BUSY)
+
+       def push_message(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_INFO)
+
+       def push_error(self, message):
+               self._push_message(message, ErrorMessage.LEVEL_ERROR)
+
+       def push_exception(self):
+               userMessage = str(sys.exc_info()[1])
+               _moduleLogger.exception(userMessage)
+               self.push_error(userMessage)
+
+       def pop(self, message = None):
+               if message is None:
+                       del self._messages[0]
+               else:
+                       _moduleLogger.info("Exiting state: %s" % message)
+                       messageIndex = [
+                               i
+                               for (i, error) in enumerate(self._messages)
+                               if error.message == message
+                       ]
+                       # Might be removed out of order
+                       if messageIndex:
+                               del self._messages[messageIndex[0]]
+               self.messagePopped.emit()
+
+       def peek_message(self):
+               return self._messages[0]
+
+       def _push_message(self, message, level):
+               self._messages.append(ErrorMessage(message, level))
+               # Sort is defined as stable, so this should be fine
+               self._messages.sort(key=lambda x: x.level)
+               self.messagePushed.emit()
+
+       def __len__(self):
+               return len(self._messages)
+
+
+class ErrorDisplay(object):
+
+       _SENTINEL_ICON = QtGui.QIcon()
+
+       def __init__(self, errorLog):
+               self._errorLog = errorLog
+               self._errorLog.messagePushed.connect(self._on_message_pushed)
+               self._errorLog.messagePopped.connect(self._on_message_popped)
+
+               self._icons = None
+               self._severityLabel = QtGui.QLabel()
+               self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+
+               self._message = QtGui.QLabel()
+               self._message.setText("Boo")
+               self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
+               self._message.setWordWrap(True)
+
+               self._closeLabel = None
+
+               self._controlLayout = QtGui.QHBoxLayout()
+               self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter)
+               self._controlLayout.addWidget(self._message, 1000)
+
+               self._widget = QtGui.QWidget()
+               self._widget.setLayout(self._controlLayout)
+               self._widget.hide()
+
+       @property
+       def toplevel(self):
+               return self._widget
+
+       def _show_error(self):
+               if self._icons is None:
+                       self._icons = {
+                               ErrorMessage.LEVEL_BUSY:
+                                       get_theme_icon(
+                                               #("process-working", "view-refresh", "general_refresh", "gtk-refresh")
+                                               ("view-refresh", "general_refresh", "gtk-refresh", )
+                                       ).pixmap(32, 32),
+                               ErrorMessage.LEVEL_INFO:
+                                       get_theme_icon(
+                                               ("dialog-information", "general_notes", "gtk-info")
+                                       ).pixmap(32, 32),
+                               ErrorMessage.LEVEL_ERROR:
+                                       get_theme_icon(
+                                               ("dialog-error", "app_install_error", "gtk-dialog-error")
+                                       ).pixmap(32, 32),
+                       }
+               if self._closeLabel is None:
+                       closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON)
+                       if closeIcon is not self._SENTINEL_ICON:
+                               self._closeLabel = QtGui.QPushButton(closeIcon, "")
+                       else:
+                               self._closeLabel = QtGui.QPushButton("X")
+                       self._closeLabel.clicked.connect(self._on_close)
+                       self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter)
+               error = self._errorLog.peek_message()
+               self._message.setText(error.message)
+               self._severityLabel.setPixmap(self._icons[error.level])
+               self._widget.show()
+
+       @qt_compat.Slot()
+       @qt_compat.Slot(bool)
+       @misc.log_exception(_moduleLogger)
+       def _on_close(self, checked = False):
+               self._errorLog.pop()
+
+       @qt_compat.Slot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_pushed(self):
+               self._show_error()
+
+       @qt_compat.Slot()
+       @misc.log_exception(_moduleLogger)
+       def _on_message_popped(self):
+               if len(self._errorLog) == 0:
+                       self._message.setText("")
+                       self._widget.hide()
+               else:
+                       self._show_error()
+
+
+class QHtmlDelegate(QtGui.QStyledItemDelegate):
+
+       UNDEFINED_SIZE = -1
+
+       def __init__(self, *args, **kwd):
+               QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd)
+               self._width = self.UNDEFINED_SIZE
+
+       def paint(self, painter, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+               if newOption.widget is not None:
+                       style = newOption.widget.style()
+               else:
+                       style = QtGui.QApplication.style()
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               doc.setTextWidth(newOption.rect.width())
+
+               newOption.text = ""
+               style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
+
+               ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
+               if newOption.state & QtGui.QStyle.State_Selected:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.HighlightedText
+                               )
+                       )
+               else:
+                       ctx.palette.setColor(
+                               QtGui.QPalette.Text,
+                               newOption.palette.color(
+                                       QtGui.QPalette.Active,
+                                       QtGui.QPalette.Text
+                               )
+                       )
+
+               textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
+               painter.save()
+               painter.translate(textRect.topLeft())
+               painter.setClipRect(textRect.translated(-textRect.topLeft()))
+               doc.documentLayout().draw(painter, ctx)
+               painter.restore()
+
+       def setWidth(self, width, model):
+               if self._width == width:
+                       return
+               self._width = width
+               for c in xrange(model.rowCount()):
+                       cItem = model.item(c, 0)
+                       for r in xrange(model.rowCount()):
+                               rItem = cItem.child(r, 0)
+                               rIndex = model.indexFromItem(rItem)
+                               self.sizeHintChanged.emit(rIndex)
+                               return
+
+       def sizeHint(self, option, index):
+               newOption = QtGui.QStyleOptionViewItemV4(option)
+               self.initStyleOption(newOption, index)
+
+               doc = QtGui.QTextDocument()
+               doc.setHtml(newOption.text)
+               if self._width != self.UNDEFINED_SIZE:
+                       width = self._width
+               else:
+                       width = newOption.rect.width()
+               doc.setTextWidth(width)
+               size = QtCore.QSize(doc.idealWidth(), doc.size().height())
+               return size
+
+
+class QSignalingMainWindow(QtGui.QMainWindow):
+
+       closed = qt_compat.Signal()
+       hidden = qt_compat.Signal()
+       shown = qt_compat.Signal()
+       resized = qt_compat.Signal()
+
+       def __init__(self, *args, **kwd):
+               QtGui.QMainWindow.__init__(*((self, )+args), **kwd)
+
+       def closeEvent(self, event):
+               val = QtGui.QMainWindow.closeEvent(self, event)
+               self.closed.emit()
+               return val
+
+       def hideEvent(self, event):
+               val = QtGui.QMainWindow.hideEvent(self, event)
+               self.hidden.emit()
+               return val
+
+       def showEvent(self, event):
+               val = QtGui.QMainWindow.showEvent(self, event)
+               self.shown.emit()
+               return val
+
+       def resizeEvent(self, event):
+               val = QtGui.QMainWindow.resizeEvent(self, event)
+               self.resized.emit()
+               return val
+
+def set_current_index(selector, itemText, default = 0):
+       for i in xrange(selector.count()):
+               if selector.itemText(i) == itemText:
+                       selector.setCurrentIndex(i)
+                       break
+       else:
+               itemText.setCurrentIndex(default)
+
+
+def _null_set_stackable(window, isStackable):
+       pass
+
+
+def _maemo_set_stackable(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5StackedWindow
+       set_stackable = _maemo_set_stackable
+except AttributeError:
+       set_stackable = _null_set_stackable
+
+
+def _null_set_autorient(window, doAutoOrient):
+       pass
+
+
+def _maemo_set_autorient(window, doAutoOrient):
+       window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, doAutoOrient)
+
+
+try:
+       QtCore.Qt.WA_Maemo5AutoOrientation
+       set_autorient = _maemo_set_autorient
+except AttributeError:
+       set_autorient = _null_set_autorient
+
+
+def screen_orientation():
+       geom = QtGui.QApplication.desktop().screenGeometry()
+       if geom.width() <= geom.height():
+               return QtCore.Qt.Vertical
+       else:
+               return QtCore.Qt.Horizontal
+
+
+def _null_set_window_orientation(window, orientation):
+       pass
+
+
+def _maemo_set_window_orientation(window, orientation):
+       if orientation == QtCore.Qt.Vertical:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, True)
+       elif orientation == QtCore.Qt.Horizontal:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, True)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
+       elif orientation is None:
+               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
+               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
+       else:
+               raise RuntimeError("Unknown orientation: %r" % orientation)
+
+
+try:
+       QtCore.Qt.WA_Maemo5LandscapeOrientation
+       QtCore.Qt.WA_Maemo5PortraitOrientation
+       set_window_orientation = _maemo_set_window_orientation
+except AttributeError:
+       set_window_orientation = _null_set_window_orientation
+
+
+def _null_show_progress_indicator(window, isStackable):
+       pass
+
+
+def _maemo_show_progress_indicator(window, isStackable):
+       window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable)
+
+
+try:
+       QtCore.Qt.WA_Maemo5ShowProgressIndicator
+       show_progress_indicator = _maemo_show_progress_indicator
+except AttributeError:
+       show_progress_indicator = _null_show_progress_indicator
+
+
+def _null_mark_numbers_preferred(widget):
+       pass
+
+
+def _newqt_mark_numbers_preferred(widget):
+       widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
+
+
+try:
+       QtCore.Qt.ImhPreferNumbers
+       mark_numbers_preferred = _newqt_mark_numbers_preferred
+except AttributeError:
+       mark_numbers_preferred = _null_mark_numbers_preferred
+
+
+def _null_get_theme_icon(iconNames, fallback = None):
+       icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+def _newqt_get_theme_icon(iconNames, fallback = None):
+       for iconName in iconNames:
+               if QtGui.QIcon.hasThemeIcon(iconName):
+                       icon = QtGui.QIcon.fromTheme(iconName)
+                       break
+       else:
+               icon = fallback if fallback is not None else QtGui.QIcon()
+       return icon
+
+
+try:
+       QtGui.QIcon.fromTheme
+       get_theme_icon = _newqt_get_theme_icon
+except AttributeError:
+       get_theme_icon = _null_get_theme_icon
+
diff --git a/ejpi/util/qwrappers.py b/ejpi/util/qwrappers.py
new file mode 100644 (file)
index 0000000..09270cd
--- /dev/null
@@ -0,0 +1,328 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+from __future__ import division
+
+import logging
+
+import qt_compat
+QtCore = qt_compat.QtCore
+QtGui = qt_compat.import_module("QtGui")
+
+import qui_utils
+import misc as misc_utils
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class ApplicationWrapper(object):
+
+       DEFAULT_ORIENTATION = "Default"
+       AUTO_ORIENTATION = "Auto"
+       LANDSCAPE_ORIENTATION = "Landscape"
+       PORTRAIT_ORIENTATION = "Portrait"
+
+       def __init__(self, qapp, constants):
+               self._constants = constants
+               self._qapp = qapp
+               self._clipboard = QtGui.QApplication.clipboard()
+
+               self._errorLog = qui_utils.QErrorLog()
+               self._mainWindow = None
+
+               self._fullscreenAction = QtGui.QAction(None)
+               self._fullscreenAction.setText("Fullscreen")
+               self._fullscreenAction.setCheckable(True)
+               self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
+               self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
+
+               self._orientation = self.DEFAULT_ORIENTATION
+               self._orientationAction = QtGui.QAction(None)
+               self._orientationAction.setText("Next Orientation")
+               self._orientationAction.setCheckable(True)
+               self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o"))
+               self._orientationAction.triggered.connect(self._on_next_orientation)
+
+               self._logAction = QtGui.QAction(None)
+               self._logAction.setText("Log")
+               self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
+               self._logAction.triggered.connect(self._on_log)
+
+               self._quitAction = QtGui.QAction(None)
+               self._quitAction.setText("Quit")
+               self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
+               self._quitAction.triggered.connect(self._on_quit)
+
+               self._aboutAction = QtGui.QAction(None)
+               self._aboutAction.setText("About")
+               self._aboutAction.triggered.connect(self._on_about)
+
+               self._qapp.lastWindowClosed.connect(self._on_app_quit)
+               self._mainWindow = self._new_main_window()
+               self._mainWindow.window.destroyed.connect(self._on_child_close)
+
+               self.load_settings()
+
+               self._mainWindow.show()
+               self._idleDelay = QtCore.QTimer()
+               self._idleDelay.setSingleShot(True)
+               self._idleDelay.setInterval(0)
+               self._idleDelay.timeout.connect(self._on_delayed_start)
+               self._idleDelay.start()
+
+       def load_settings(self):
+               raise NotImplementedError("Booh")
+
+       def save_settings(self):
+               raise NotImplementedError("Booh")
+
+       def _new_main_window(self):
+               raise NotImplementedError("Booh")
+
+       @property
+       def qapp(self):
+               return self._qapp
+
+       @property
+       def constants(self):
+               return self._constants
+
+       @property
+       def errorLog(self):
+               return self._errorLog
+
+       @property
+       def fullscreenAction(self):
+               return self._fullscreenAction
+
+       @property
+       def orientationAction(self):
+               return self._orientationAction
+
+       @property
+       def orientation(self):
+               return self._orientation
+
+       @property
+       def logAction(self):
+               return self._logAction
+
+       @property
+       def aboutAction(self):
+               return self._aboutAction
+
+       @property
+       def quitAction(self):
+               return self._quitAction
+
+       def set_orientation(self, orientation):
+               self._orientation = orientation
+               self._mainWindow.update_orientation(self._orientation)
+
+       @classmethod
+       def _next_orientation(cls, current):
+               return {
+                       cls.DEFAULT_ORIENTATION: cls.AUTO_ORIENTATION,
+                       cls.AUTO_ORIENTATION: cls.LANDSCAPE_ORIENTATION,
+                       cls.LANDSCAPE_ORIENTATION: cls.PORTRAIT_ORIENTATION,
+                       cls.PORTRAIT_ORIENTATION: cls.DEFAULT_ORIENTATION,
+               }[current]
+
+       def _close_windows(self):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow.window.destroyed.disconnect(self._on_child_close)
+                       self._mainWindow.close()
+                       self._mainWindow = None
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_delayed_start(self):
+               self._mainWindow.start()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_app_quit(self, checked = False):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow.destroy()
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_child_close(self, obj = None):
+               if self._mainWindow is not None:
+                       self.save_settings()
+                       self._mainWindow = None
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_toggle_fullscreen(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self._mainWindow.set_fullscreen(checked)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_next_orientation(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self.set_orientation(self._next_orientation(self._orientation))
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_about(self, checked = True):
+               raise NotImplementedError("Booh")
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_log(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       with open(self._constants._user_logpath_, "r") as f:
+                               logLines = f.xreadlines()
+                               log = "".join(logLines)
+                               self._clipboard.setText(log)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_quit(self, checked = False):
+               with qui_utils.notify_error(self._errorLog):
+                       self._close_windows()
+
+
+class WindowWrapper(object):
+
+       def __init__(self, parent, app):
+               self._app = app
+
+               self._errorDisplay = qui_utils.ErrorDisplay(self._app.errorLog)
+
+               self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight)
+               self._layout.setContentsMargins(0, 0, 0, 0)
+
+               self._superLayout = QtGui.QVBoxLayout()
+               self._superLayout.addWidget(self._errorDisplay.toplevel)
+               self._superLayout.setContentsMargins(0, 0, 0, 0)
+               self._superLayout.addLayout(self._layout)
+
+               centralWidget = QtGui.QWidget()
+               centralWidget.setLayout(self._superLayout)
+               centralWidget.setContentsMargins(0, 0, 0, 0)
+
+               self._window = qui_utils.QSignalingMainWindow(parent)
+               self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
+               qui_utils.set_stackable(self._window, True)
+               self._window.setCentralWidget(centralWidget)
+
+               self._closeWindowAction = QtGui.QAction(None)
+               self._closeWindowAction.setText("Close")
+               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
+               self._closeWindowAction.triggered.connect(self._on_close_window)
+
+               self._window.addAction(self._closeWindowAction)
+               self._window.addAction(self._app.quitAction)
+               self._window.addAction(self._app.fullscreenAction)
+               self._window.addAction(self._app.orientationAction)
+               self._window.addAction(self._app.logAction)
+
+       @property
+       def window(self):
+               return self._window
+
+       @property
+       def windowOrientation(self):
+               geom = self._window.size()
+               if geom.width() <= geom.height():
+                       return QtCore.Qt.Vertical
+               else:
+                       return QtCore.Qt.Horizontal
+
+       @property
+       def idealWindowOrientation(self):
+               if self._app.orientation ==  self._app.AUTO_ORIENTATION:
+                       windowOrientation = self.windowOrientation
+               elif self._app.orientation ==  self._app.DEFAULT_ORIENTATION:
+                       windowOrientation = qui_utils.screen_orientation()
+               elif self._app.orientation ==  self._app.LANDSCAPE_ORIENTATION:
+                       windowOrientation = QtCore.Qt.Horizontal
+               elif self._app.orientation ==  self._app.PORTRAIT_ORIENTATION:
+                       windowOrientation = QtCore.Qt.Vertical
+               else:
+                       raise RuntimeError("Bad! No %r for you" % self._app.orientation)
+               return windowOrientation
+
+       def walk_children(self):
+               return ()
+
+       def start(self):
+               pass
+
+       def close(self):
+               for child in self.walk_children():
+                       child.window.destroyed.disconnect(self._on_child_close)
+                       child.close()
+               self._window.close()
+
+       def destroy(self):
+               pass
+
+       def show(self):
+               self._window.show()
+               for child in self.walk_children():
+                       child.show()
+               self.set_fullscreen(self._app.fullscreenAction.isChecked())
+
+       def hide(self):
+               for child in self.walk_children():
+                       child.hide()
+               self._window.hide()
+
+       def set_fullscreen(self, isFullscreen):
+               if self._window.isVisible():
+                       if isFullscreen:
+                               self._window.showFullScreen()
+                       else:
+                               self._window.showNormal()
+               for child in self.walk_children():
+                       child.set_fullscreen(isFullscreen)
+
+       def update_orientation(self, orientation):
+               if orientation == self._app.DEFAULT_ORIENTATION:
+                       qui_utils.set_autorient(self.window, False)
+                       qui_utils.set_window_orientation(self.window, None)
+               elif orientation == self._app.AUTO_ORIENTATION:
+                       qui_utils.set_autorient(self.window, True)
+                       qui_utils.set_window_orientation(self.window, None)
+               elif orientation == self._app.LANDSCAPE_ORIENTATION:
+                       qui_utils.set_autorient(self.window, False)
+                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal)
+               elif orientation == self._app.PORTRAIT_ORIENTATION:
+                       qui_utils.set_autorient(self.window, False)
+                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical)
+               else:
+                       raise RuntimeError("Unknown orientation: %r" % orientation)
+               for child in self.walk_children():
+                       child.update_orientation(orientation)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_child_close(self, obj = None):
+               raise NotImplementedError("Booh")
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_close_window(self, checked = True):
+               with qui_utils.notify_error(self._errorLog):
+                       self.close()
+
+
+class AutoFreezeWindowFeature(object):
+
+       def __init__(self, app, window):
+               self._app = app
+               self._window = window
+               self._app.qapp.focusChanged.connect(self._on_focus_changed)
+               if self._app.qapp.focusWidget() is not None:
+                       self._window.setUpdatesEnabled(True)
+               else:
+                       self._window.setUpdatesEnabled(False)
+
+       def close(self):
+               self._app.qapp.focusChanged.disconnect(self._on_focus_changed)
+               self._window.setUpdatesEnabled(True)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_focus_changed(self, oldWindow, newWindow):
+               with qui_utils.notify_error(self._app.errorLog):
+                       if oldWindow is None and newWindow is not None:
+                               self._window.setUpdatesEnabled(True)
+                       elif oldWindow is not None and newWindow is None:
+                               self._window.setUpdatesEnabled(False)
diff --git a/ejpi/util/time_utils.py b/ejpi/util/time_utils.py
new file mode 100644 (file)
index 0000000..90ec84d
--- /dev/null
@@ -0,0 +1,94 @@
+from datetime import tzinfo, timedelta, datetime
+
+ZERO = timedelta(0)
+HOUR = timedelta(hours=1)
+
+
+def first_sunday_on_or_after(dt):
+       days_to_go = 6 - dt.weekday()
+       if days_to_go:
+               dt += timedelta(days_to_go)
+       return dt
+
+
+# US DST Rules
+#
+# This is a simplified (i.e., wrong for a few cases) set of rules for US
+# DST start and end times. For a complete and up-to-date set of DST rules
+# and timezone definitions, visit the Olson Database (or try pytz):
+# http://www.twinsun.com/tz/tz-link.htm
+# http://sourceforge.net/projects/pytz/ (might not be up-to-date)
+#
+# In the US, since 2007, DST starts at 2am (standard time) on the second
+# Sunday in March, which is the first Sunday on or after Mar 8.
+DSTSTART_2007 = datetime(1, 3, 8, 2)
+# and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov.
+DSTEND_2007 = datetime(1, 11, 1, 1)
+# From 1987 to 2006, DST used to start at 2am (standard time) on the first
+# Sunday in April and to end at 2am (DST time; 1am standard time) on the last
+# Sunday of October, which is the first Sunday on or after Oct 25.
+DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
+DSTEND_1987_2006 = datetime(1, 10, 25, 1)
+# From 1967 to 1986, DST used to start at 2am (standard time) on the last
+# Sunday in April (the one on or after April 24) and to end at 2am (DST time;
+# 1am standard time) on the last Sunday of October, which is the first Sunday
+# on or after Oct 25.
+DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
+DSTEND_1967_1986 = DSTEND_1987_2006
+
+
+class USTimeZone(tzinfo):
+
+       def __init__(self, hours, reprname, stdname, dstname):
+               self.stdoffset = timedelta(hours=hours)
+               self.reprname = reprname
+               self.stdname = stdname
+               self.dstname = dstname
+
+       def __repr__(self):
+               return self.reprname
+
+       def tzname(self, dt):
+               if self.dst(dt):
+                       return self.dstname
+               else:
+                       return self.stdname
+
+       def utcoffset(self, dt):
+               return self.stdoffset + self.dst(dt)
+
+       def dst(self, dt):
+               if dt is None or dt.tzinfo is None:
+                       # An exception may be sensible here, in one or both cases.
+                       # It depends on how you want to treat them.  The default
+                       # fromutc() implementation (called by the default astimezone()
+                       # implementation) passes a datetime with dt.tzinfo is self.
+                       return ZERO
+               assert dt.tzinfo is self
+
+               # Find start and end times for US DST. For years before 1967, return
+               # ZERO for no DST.
+               if 2006 < dt.year:
+                       dststart, dstend = DSTSTART_2007, DSTEND_2007
+               elif 1986 < dt.year < 2007:
+                       dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
+               elif 1966 < dt.year < 1987:
+                       dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
+               else:
+                       return ZERO
+
+               start = first_sunday_on_or_after(dststart.replace(year=dt.year))
+               end = first_sunday_on_or_after(dstend.replace(year=dt.year))
+
+               # Can't compare naive to aware objects, so strip the timezone from
+               # dt first.
+               if start <= dt.replace(tzinfo=None) < end:
+                       return HOUR
+               else:
+                       return ZERO
+
+
+Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
+Central  = USTimeZone(-6, "Central",  "CST", "CDT")
+Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
+Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
diff --git a/ejpi/util/tp_utils.py b/ejpi/util/tp_utils.py
new file mode 100644 (file)
index 0000000..7c55c42
--- /dev/null
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+
+import logging
+
+import dbus
+import telepathy
+
+import util.go_utils as gobject_utils
+import misc
+
+
+_moduleLogger = logging.getLogger(__name__)
+DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
+
+
+class WasMissedCall(object):
+
+       def __init__(self, bus, conn, chan, on_success, on_error):
+               self.__on_success = on_success
+               self.__on_error = on_error
+
+               self._requested = None
+               self._didMembersChange = False
+               self._didClose = False
+               self._didReport = False
+
+               self._onTimeout = gobject_utils.Timeout(self._on_timeout)
+               self._onTimeout.start(seconds=60)
+
+               chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].connect_to_signal(
+                       "MembersChanged",
+                       self._on_members_changed,
+               )
+
+               chan[telepathy.interfaces.CHANNEL].connect_to_signal(
+                       "Closed",
+                       self._on_closed,
+               )
+
+               chan[DBUS_PROPERTIES].GetAll(
+                       telepathy.interfaces.CHANNEL_INTERFACE,
+                       reply_handler = self._on_got_all,
+                       error_handler = self._on_error,
+               )
+
+       def cancel(self):
+               self._report_error("by request")
+
+       def _report_missed_if_ready(self):
+               if self._didReport:
+                       pass
+               elif self._requested is not None and (self._didMembersChange or self._didClose):
+                       if self._requested:
+                               self._report_error("wrong direction")
+                       elif self._didClose:
+                               self._report_success()
+                       else:
+                               self._report_error("members added")
+               else:
+                       if self._didClose:
+                               self._report_error("closed too early")
+
+       def _report_success(self):
+               assert not self._didReport, "Double reporting a missed call"
+               self._didReport = True
+               self._onTimeout.cancel()
+               self.__on_success(self)
+
+       def _report_error(self, reason):
+               assert not self._didReport, "Double reporting a missed call"
+               self._didReport = True
+               self._onTimeout.cancel()
+               self.__on_error(self, reason)
+
+       @misc.log_exception(_moduleLogger)
+       def _on_got_all(self, properties):
+               self._requested = properties["Requested"]
+               self._report_missed_if_ready()
+
+       @misc.log_exception(_moduleLogger)
+       def _on_members_changed(self, message, added, removed, lp, rp, actor, reason):
+               if added:
+                       self._didMembersChange = True
+                       self._report_missed_if_ready()
+
+       @misc.log_exception(_moduleLogger)
+       def _on_closed(self):
+               self._didClose = True
+               self._report_missed_if_ready()
+
+       @misc.log_exception(_moduleLogger)
+       def _on_error(self, *args):
+               self._report_error(args)
+
+       @misc.log_exception(_moduleLogger)
+       def _on_timeout(self):
+               self._report_error("timeout")
+               return False
+
+
+class NewChannelSignaller(object):
+
+       def __init__(self, on_new_channel):
+               self._sessionBus = dbus.SessionBus()
+               self._on_user_new_channel = on_new_channel
+
+       def start(self):
+               self._sessionBus.add_signal_receiver(
+                       self._on_new_channel,
+                       "NewChannel",
+                       "org.freedesktop.Telepathy.Connection",
+                       None,
+                       None
+               )
+
+       def stop(self):
+               self._sessionBus.remove_signal_receiver(
+                       self._on_new_channel,
+                       "NewChannel",
+                       "org.freedesktop.Telepathy.Connection",
+                       None,
+                       None
+               )
+
+       @misc.log_exception(_moduleLogger)
+       def _on_new_channel(
+               self, channelObjectPath, channelType, handleType, handle, supressHandler
+       ):
+               connObjectPath = channel_path_to_conn_path(channelObjectPath)
+               serviceName = path_to_service_name(channelObjectPath)
+               try:
+                       self._on_user_new_channel(
+                               self._sessionBus, serviceName, connObjectPath, channelObjectPath, channelType
+                       )
+               except Exception:
+                       _moduleLogger.exception("Blocking exception from being passed up")
+
+
+class EnableSystemContactIntegration(object):
+
+       ACCOUNT_MGR_NAME = "org.freedesktop.Telepathy.AccountManager"
+       ACCOUNT_MGR_PATH = "/org/freedesktop/Telepathy/AccountManager"
+       ACCOUNT_MGR_IFACE_QUERY = "com.nokia.AccountManager.Interface.Query"
+       ACCOUNT_IFACE_COMPAT = "com.nokia.Account.Interface.Compat"
+       ACCOUNT_IFACE_COMPAT_PROFILE = "com.nokia.Account.Interface.Compat.Profile"
+       DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
+
+       def __init__(self, profileName):
+               self._bus = dbus.SessionBus()
+               self._profileName = profileName
+
+       def start(self):
+               self._accountManager = self._bus.get_object(
+                       self.ACCOUNT_MGR_NAME,
+                       self.ACCOUNT_MGR_PATH,
+               )
+               self._accountManagerQuery = dbus.Interface(
+                       self._accountManager,
+                       dbus_interface=self.ACCOUNT_MGR_IFACE_QUERY,
+               )
+
+               self._accountManagerQuery.FindAccounts(
+                       {
+                               self.ACCOUNT_IFACE_COMPAT_PROFILE: self._profileName,
+                       },
+                       reply_handler = self._on_found_accounts_reply,
+                       error_handler = self._on_error,
+               )
+
+       @misc.log_exception(_moduleLogger)
+       def _on_found_accounts_reply(self, accountObjectPaths):
+               for accountObjectPath in accountObjectPaths:
+                       print accountObjectPath
+                       account = self._bus.get_object(
+                               self.ACCOUNT_MGR_NAME,
+                               accountObjectPath,
+                       )
+                       accountProperties = dbus.Interface(
+                               account,
+                               self.DBUS_PROPERTIES,
+                       )
+                       accountProperties.Set(
+                               self.ACCOUNT_IFACE_COMPAT,
+                               "SecondaryVCardFields",
+                               ["TEL"],
+                               reply_handler = self._on_field_set,
+                               error_handler = self._on_error,
+                       )
+
+       @misc.log_exception(_moduleLogger)
+       def _on_field_set(self):
+               _moduleLogger.info("SecondaryVCardFields Set")
+
+       @misc.log_exception(_moduleLogger)
+       def _on_error(self, error):
+               _moduleLogger.error("%r" % (error, ))
+
+
+def channel_path_to_conn_path(channelObjectPath):
+       """
+       >>> channel_path_to_conn_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       '/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME'
+       """
+       return channelObjectPath.rsplit("/", 1)[0]
+
+
+def path_to_service_name(path):
+       """
+       >>> path_to_service_name("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       'org.freedesktop.Telepathy.ConnectionManager.theonering.gv.USERNAME'
+       """
+       return ".".join(path[1:].split("/")[0:7])
+
+
+def cm_from_path(path):
+       """
+       >>> cm_from_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       'theonering'
+       """
+       return path[1:].split("/")[4]
diff --git a/setup.py b/setup.py
new file mode 100755 (executable)
index 0000000..1e25ab3
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import sys
+reload(sys).setdefaultencoding("UTF-8")
+import os
+
+try:
+       from sdist_maemo import sdist_maemo as _sdist_maemo
+       sdist_maemo = _sdist_maemo
+except ImportError:
+       sdist_maemo = None
+       print 'sdist_maemo command not available'
+
+from distutils.core import setup
+from ejpi import constants
+
+
+def is_package(path):
+       return (
+               os.path.isdir(path) and
+               os.path.isfile(os.path.join(path, '__init__.py'))
+       )
+
+
+def find_packages(path, base="", includeRoot=False):
+       """ Find all packages in path """
+       if includeRoot:
+               assert not base, "Base not supported with includeRoot: %r" % base
+               rootPath, module_name = os.path.split(path)
+               yield module_name
+               base = module_name
+       for item in os.listdir(path):
+               dir = os.path.join(path, item)
+               if is_package( dir ):
+                       if base:
+                               module_name = "%(base)s.%(item)s" % vars()
+                       else:
+                               module_name = item
+                       yield module_name
+                       for mname in find_packages(dir, module_name):
+                               yield mname
+
+
+changes = ""
+icon = "data/%s.png" % constants.__app_name__
+
+
+setup(
+       name=constants.__app_name__,
+       version=constants.__version__,
+       description="RPN calculator designed for touchscreens",
+       long_description="RPN calculator designed for touchscreens",
+       author="Ed Page",
+       author_email="eopage@byu.net",
+       maintainer="Ed Page",
+       maintainer_email="eopage@byu.net",
+       url="http://ejpi.garage.maemo.org/",
+       license="GNU LGPLv2.1",
+       scripts=[
+               "ejpi-calc",
+       ],
+       packages=list(find_packages(constants.__app_name__, includeRoot=True)),
+       data_files=[
+               #[[[cog
+               #       import cog
+               #       cog.outl('              ("%s", ["data/%%s.desktop" %% constants.__app_name__]),' % desktopFilePath)
+               #]]]
+               ("/usr/share/applications", ["data/%s.desktop" % constants.__app_name__]),
+               #[[[end]]]
+               ("/usr/share/icons/hicolor/22x22/apps", ["data/icons/22/%s.png" % constants.__app_name__]),
+               ("/usr/share/icons/hicolor/28x28/apps", ["data/icons/28/%s.png" % constants.__app_name__]),
+               ("/usr/share/icons/hicolor/32x32/apps", ["data/icons/32/%s.png" % constants.__app_name__]),
+               ("/usr/share/icons/hicolor/48x48/apps", ["data/icons/48/%s.png" % constants.__app_name__]),
+               ("/usr/share/icons/hicolor/scalable/apps", ["data/%s.svg" % constants.__app_name__]),
+       ],
+       requires=[
+               "PySide",
+       ],
+       cmdclass={
+               'sdist_diablo': sdist_maemo,
+               'sdist_fremantle': sdist_maemo,
+               'sdist_harmattan': sdist_maemo,
+       },
+       options={
+               "sdist_diablo": {
+                       "debian_package": constants.__app_name__,
+                       "Maemo_Display_Name": constants.__pretty_app_name__,
+                       #"Maemo_Upgrade_Description": changes,
+                       "Maemo_Bugtracker": "https://bugs.maemo.org/enter_bug.cgi?product=ejpi",
+                       "Maemo_Icon_26": "data/icons/48/%s.png" % constants.__app_name__,
+                       "MeeGo_Desktop_Entry_Filename": constants.__app_name__,
+                       #"MeeGo_Desktop_Entry": "",
+                       "section": "user/science",
+                       "copyright": "lgpl",
+                       "changelog": changes,
+                       "buildversion": str(constants.__build__),
+                       "depends": "python, python-qt4-core, python-qt4-gui",
+                       "architecture": "any",
+               },
+               "sdist_fremantle": {
+                       "debian_package": constants.__app_name__,
+                       "Maemo_Display_Name": constants.__pretty_app_name__,
+                       #"Maemo_Upgrade_Description": changes,
+                       "Maemo_Bugtracker": "https://bugs.maemo.org/enter_bug.cgi?product=ejpi",
+                       "Maemo_Icon_26": "data/icons/48/%s.png" % constants.__app_name__,
+                       "MeeGo_Desktop_Entry_Filename": constants.__app_name__,
+                       #"MeeGo_Desktop_Entry": "",
+                       "section": "user/science",
+                       "copyright": "lgpl",
+                       "changelog": changes,
+                       "buildversion": str(constants.__build__),
+                       "depends": "python, python-pyside.qtcore, python-pyside.qtgui, python-pyside.maemo5",
+                       "architecture": "any",
+               },
+               "sdist_harmattan": {
+                       "debian_package": constants.__app_name__,
+                       "Maemo_Display_Name": constants.__pretty_app_name__,
+                       #"Maemo_Upgrade_Description": changes,
+                       "Maemo_Bugtracker": "https://bugs.maemo.org/enter_bug.cgi?product=ejpi",
+                       "Maemo_Icon_26": "data/icons/26/%s.png" % constants.__app_name__,
+                       "MeeGo_Desktop_Entry_Filename": constants.__app_name__,
+                       #"MeeGo_Desktop_Entry": "",
+                       "section": "user/science",
+                       "copyright": "lgpl",
+                       "changelog": changes,
+                       "buildversion": str(constants.__build__),
+                       "depends": "python, python-pyside.qtcore, python-pyside.qtgui",
+                       "architecture": "any",
+               },
+               "bdist_rpm": {
+                       "requires": "REPLACEME",
+                       "icon": icon,
+                       "group": "REPLACEME",
+               },
+       },
+)
diff --git a/src b/src
new file mode 120000 (symlink)
index 0000000..bd62b2d
--- /dev/null
+++ b/src
@@ -0,0 +1 @@
+ejpi/
\ No newline at end of file
diff --git a/src/__init__.py b/src/__init__.py
deleted file mode 100644 (file)
index 4265cc3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#!/usr/bin/env python
diff --git a/src/constants.py b/src/constants.py
deleted file mode 100644 (file)
index 1c8d555..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-import os
-
-__pretty_app_name__ = "e**(j pi) + 1 = 0"
-__app_name__ = "ejpi"
-__version__ = "1.0.6"
-__build__ = 0
-__app_magic__ = 0xdeadbeef
-_data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
-_user_settings_ = "%s/settings.ini" % _data_path_
-_user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__)
-IS_MAEMO = True
diff --git a/src/ejpi.py b/src/ejpi.py
deleted file mode 100755 (executable)
index 5d0be57..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import logging
-
-
-_moduleLogger = logging.getLogger(__name__)
-sys.path.append("/opt/ejpi/lib")
-
-
-import ejpi_qt
-
-
-if __name__ == "__main__":
-       ejpi_qt.run()
diff --git a/src/ejpi_qt.py b/src/ejpi_qt.py
deleted file mode 100755 (executable)
index b5d58d8..0000000
+++ /dev/null
@@ -1,380 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: UTF8 -*-
-
-from __future__ import with_statement
-
-import os
-import simplejson
-import string
-import logging
-import logging.handlers
-
-import util.qt_compat as qt_compat
-QtCore = qt_compat.QtCore
-QtGui = qt_compat.import_module("QtGui")
-
-import constants
-from util import misc as misc_utils
-
-from util import qui_utils
-from util import qwrappers
-from util import qtpie
-from util import qtpieboard
-import plugin_utils
-import history
-import qhistory
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class Calculator(qwrappers.ApplicationWrapper):
-
-       def __init__(self, app):
-               self._recent = []
-               self._hiddenCategories = set()
-               self._hiddenUnits = {}
-               qwrappers.ApplicationWrapper.__init__(self, app, constants)
-
-       def load_settings(self):
-               try:
-                       with open(constants._user_settings_, "r") as settingsFile:
-                               settings = simplejson.load(settingsFile)
-               except IOError, e:
-                       _moduleLogger.info("No settings")
-                       settings = {}
-               except ValueError:
-                       _moduleLogger.info("Settings were corrupt")
-                       settings = {}
-
-               isPortraitDefault = qui_utils.screen_orientation() == QtCore.Qt.Vertical
-               self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
-               self._orientationAction.setChecked(settings.get("isPortrait", isPortraitDefault))
-
-       def save_settings(self):
-               settings = {
-                       "isFullScreen": self._fullscreenAction.isChecked(),
-                       "isPortrait": self._orientationAction.isChecked(),
-               }
-               with open(constants._user_settings_, "w") as settingsFile:
-                       simplejson.dump(settings, settingsFile)
-
-       @property
-       def dataPath(self):
-               return self._dataPath
-
-       def _new_main_window(self):
-               return MainWindow(None, self)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_about(self, checked = True):
-               raise NotImplementedError("Booh")
-
-
-class QValueEntry(object):
-
-       def __init__(self):
-               self._widget = QtGui.QLineEdit("")
-               qui_utils.mark_numbers_preferred(self._widget)
-
-       @property
-       def toplevel(self):
-               return self._widget
-
-       @property
-       def entry(self):
-               return self._widget
-
-       def get_value(self):
-               value = str(self._widget.text()).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._widget.setText(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 MainWindow(qwrappers.WindowWrapper):
-
-       _plugin_search_paths = [
-               os.path.join(os.path.dirname(__file__), "plugins/"),
-       ]
-
-       _user_history = "%s/history.stack" % constants._data_path_
-
-       def __init__(self, parent, app):
-               qwrappers.WindowWrapper.__init__(self, parent, app)
-               self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
-               #self._freezer = qwrappers.AutoFreezeWindowFeature(self._app, self._window)
-
-               self._historyView = qhistory.QCalcHistory(self._app.errorLog)
-               self._userEntry = QValueEntry()
-               self._userEntry.entry.returnPressed.connect(self._on_push)
-               self._userEntryLayout = QtGui.QHBoxLayout()
-               self._userEntryLayout.setContentsMargins(0, 0, 0, 0)
-               self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
-
-               self._controlLayout = QtGui.QVBoxLayout()
-               self._controlLayout.setContentsMargins(0, 0, 0, 0)
-               self._controlLayout.addWidget(self._historyView.toplevel, 1000)
-               self._controlLayout.addLayout(self._userEntryLayout, 0)
-
-               self._keyboardTabs = QtGui.QTabWidget()
-
-               self._layout.addLayout(self._controlLayout)
-               self._layout.addWidget(self._keyboardTabs)
-
-               self._copyItemAction = QtGui.QAction(None)
-               self._copyItemAction.setText("Copy")
-               self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
-               self._copyItemAction.triggered.connect(self._on_copy)
-
-               self._pasteItemAction = QtGui.QAction(None)
-               self._pasteItemAction.setText("Paste")
-               self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
-               self._pasteItemAction.triggered.connect(self._on_paste)
-
-               self._closeWindowAction = QtGui.QAction(None)
-               self._closeWindowAction.setText("Close")
-               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
-               self._closeWindowAction.triggered.connect(self._on_close_window)
-
-               self._window.addAction(self._copyItemAction)
-               self._window.addAction(self._pasteItemAction)
-
-               self._constantPlugins = plugin_utils.ConstantPluginManager()
-               self._constantPlugins.add_path(*self._plugin_search_paths)
-               for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
-                       try:
-                               pluginId = self._constantPlugins.lookup_plugin(pluginName)
-                               self._constantPlugins.enable_plugin(pluginId)
-                       except:
-                               _moduleLogger.info("Failed to load plugin %s" % pluginName)
-
-               self._operatorPlugins = plugin_utils.OperatorPluginManager()
-               self._operatorPlugins.add_path(*self._plugin_search_paths)
-               for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
-                       try:
-                               pluginId = self._operatorPlugins.lookup_plugin(pluginName)
-                               self._operatorPlugins.enable_plugin(pluginId)
-                       except:
-                               _moduleLogger.info("Failed to load plugin %s" % pluginName)
-
-               self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
-               self._keyboardPlugins.add_path(*self._plugin_search_paths)
-               self._activeKeyboards = []
-
-               self._history = history.RpnCalcHistory(
-                       self._historyView,
-                       self._userEntry, self._app.errorLog,
-                       self._constantPlugins.constants, self._operatorPlugins.operators
-               )
-               self._load_history()
-
-               # Basic keyboard stuff
-               self._handler = qtpieboard.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
-               entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
-               self._keyboardPlugins.enable_plugin(entryKeyboardId)
-               entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
-               entryKeyboard = entryPlugin.setup(self._history, self._handler)
-               self._userEntryLayout.addWidget(entryKeyboard.toplevel)
-
-               # Plugins
-               self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
-               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._scrollTimer = QtCore.QTimer()
-               self._scrollTimer.setInterval(0)
-               self._scrollTimer.setSingleShot(True)
-               self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
-               self._scrollTimer.start()
-
-               self.set_fullscreen(self._app.fullscreenAction.isChecked())
-               self.update_orientation(self._app.orientation)
-
-       def walk_children(self):
-               return ()
-
-       def update_orientation(self, orientation):
-               qwrappers.WindowWrapper.update_orientation(self, orientation)
-               windowOrientation = self.idealWindowOrientation
-               if windowOrientation == QtCore.Qt.Horizontal:
-                       defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
-                       tabPosition = QtGui.QTabWidget.North
-               else:
-                       defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
-                       #tabPosition = QtGui.QTabWidget.South
-                       tabPosition = QtGui.QTabWidget.West
-               self._layout.setDirection(defaultLayoutOrientation)
-               self._keyboardTabs.setTabPosition(tabPosition)
-
-       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()
-               relIcon = self._keyboardPlugins.keyboards[pluginName].icon
-               for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
-                       absIconPath = os.path.join(iconPath, relIcon)
-                       if os.path.exists(absIconPath):
-                               icon = QtGui.QIcon(absIconPath)
-                               break
-               else:
-                       icon = None
-               pluginKeyboard = plugin.setup(self._history, self._handler)
-
-               self._activeKeyboards.append({
-                       "pluginName": pluginName,
-                       "plugin": plugin,
-                       "pluginKeyboard": pluginKeyboard,
-               })
-               if icon is None:
-                       self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
-               else:
-                       self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
-
-       def close(self):
-               qwrappers.WindowWrapper.close(self)
-               self._save_history()
-
-       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)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_delayed_scroll_to_bottom(self):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._historyView.scroll_to_bottom()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_child_close(self, something = None):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._child = None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_copy(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       eqNode = self._historyView.peek()
-                       resultNode = eqNode.simplify()
-                       self._app._clipboard.setText(str(resultNode))
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_paste(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       result = str(self._app._clipboard.text())
-                       self._userEntry.append(result)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_entry_direct(self, keys, modifiers):
-               with qui_utils.notify_error(self._app.errorLog):
-                       if "shift" in modifiers:
-                               keys = keys.upper()
-                       self._userEntry.append(keys)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_push(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._history.push_entry()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_unpush(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._historyView.unpush()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_entry_backspace(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._userEntry.pop()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_entry_clear(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._userEntry.clear()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_clear_all(self, *args):
-               with qui_utils.notify_error(self._app.errorLog):
-                       self._history.clear()
-
-
-def run():
-       try:
-               os.makedirs(constants._data_path_)
-       except OSError, e:
-               if e.errno != 17:
-                       raise
-
-       logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
-       logging.basicConfig(level=logging.DEBUG, format=logFormat)
-       rotating = logging.handlers.RotatingFileHandler(constants._user_logpath_, maxBytes=512*1024, backupCount=1)
-       rotating.setFormatter(logging.Formatter(logFormat))
-       root = logging.getLogger()
-       root.addHandler(rotating)
-       _moduleLogger.info("%s %s-%s" % (constants.__app_name__, 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])
-
-       app = QtGui.QApplication([])
-       handle = Calculator(app)
-       qtpie.init_pies()
-       return app.exec_()
-
-
-if __name__ == "__main__":
-       import sys
-       val = run()
-       sys.exit(val)
diff --git a/src/history.py b/src/history.py
deleted file mode 100644 (file)
index c525027..0000000
+++ /dev/null
@@ -1,209 +0,0 @@
-#!/usr/bin/env python
-
-
-import re
-import weakref
-
-from util import algorithms
-import operation
-
-
-__BASE_MAPPINGS = {
-       "0x": 16,
-       "0o": 8,
-       "0b": 2,
-}
-
-
-_VARIABLE_VALIDATION_RE = re.compile("^[a-zA-Z0-9]+$")
-
-
-def validate_variable_name(variableName):
-       match = _VARIABLE_VALIDATION_RE.match(variableName)
-       if match is None:
-               raise RuntimeError("Invalid characters in '%s'" % variableName)
-
-
-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.history._parse_value = self._parse_value
-               self.__entry = weakref.ref(entry)
-
-               self.__errorReporter = errorReporting
-               self.__constants = constants
-               self.__operations = operations
-
-               self.__serialRenderer = operation.render_number()
-
-       @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
-
-               validate_variable_name(userInput)
-               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/operation.py b/src/operation.py
deleted file mode 100644 (file)
index 9a0cd5a..0000000
+++ /dev/null
@@ -1,420 +0,0 @@
-#!/usr/bin/env python
-
-
-import itertools
-import functools
-import decimal
-
-from util import overloading
-from util 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
deleted file mode 100644 (file)
index 087f8db..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-#!/usr/bin/env python
-
-
-from __future__ import with_statement
-
-
-import sys
-import os
-import inspect
-import ConfigParser
-
-from util import qtpieboard
-from util 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, boardHandler):
-               self.__handler = boardHandler
-
-               boardTree = self.factory.map
-
-               keyboardName = boardTree["name"]
-               keyTree = boardTree["keys"]
-
-               keyboard = qtpieboard.PieKeyboard()
-               qtpieboard.load_keyboard(keyboardName, keyTree, keyboard, self.__handler, self.factory.iconPaths)
-
-               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, icon, keyboardMap, iconPaths):
-               self.name = pluginName
-               self.map = keyboardMap
-               self.commands = {}
-               self.icon = icon
-               self.iconPaths = iconPaths
-
-       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
deleted file mode 100644 (file)
index 4265cc3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#!/usr/bin/env python
diff --git a/src/plugins/alphabet.ini b/src/plugins/alphabet.ini
deleted file mode 100644 (file)
index cb845d1..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[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.py b/src/plugins/alphabet.py
deleted file mode 100644 (file)
index 554aa08..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
-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 sys
-sys.path.append("../")
-import plugin_utils
-
-
-_NAME = "Alphabet"
-_ICON = "alphabet.png"
-_MAP = {
-       "name": _NAME,
-       "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,
-               },
-       },
-}
-_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
-PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_PATH)
diff --git a/src/plugins/builtins.ini b/src/plugins/builtins.ini
deleted file mode 100644 (file)
index 3e74fd2..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[Operator]
-name=Builtins
-version=0.1
-description=
-
-[Constants]
-name=Builtins
-version=0.1
-description=
-
-[Keyboard]
-name=Builtins
-version=0.1
-description=
diff --git a/src/plugins/builtins.py b/src/plugins/builtins.py
deleted file mode 100644 (file)
index ba0826e..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-from __future__ import division
-
-import os
-import operator
-import math
-
-import operation
-
-import sys
-sys.path.append("../")
-import plugin_utils
-
-_NAME = "Builtins"
-_ICON = "builtins.png"
-_MAP = {
-       "name": _NAME,
-       "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": "x ** y", },
-                       "EAST": {"action": "[sq]", "type": "text", "text": "x ** 2", },
-                       "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", },
-                       "NORTH": {"action": ".", "type": "text", "text": ".", },
-                       "EAST": {"action": "0", "type": "text", "text": "0", },
-                       "showAllSlices": True,
-               },
-               (2, 1): {
-                       "CENTER": {"action": "2", "type": "text", "text": "2", },
-                       "EAST": {"action": "[abs]", "type": "text", "text": "abs", },
-                       "WEST": {"action": "[+-]", "type": "text", "text": "+/-", },
-                       "showAllSlices": True,
-               },
-               (2, 2): {
-                       "CENTER": {"action": "3", "type": "text", "text": "3", },
-                       "NORTH": {"action": "[!]", "type": "text", "text": "x !", },
-                       "WEST": {"action": "j", "type": "text", "text": "j", },
-                       "showAllSlices": True,
-               },
-       },
-}
-_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
-PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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
deleted file mode 100644 (file)
index e0651cb..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[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.py b/src/plugins/computer.py
deleted file mode 100644 (file)
index 925ace6..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-from __future__ import division
-
-import os
-import operator
-import math
-
-import operation
-
-import sys
-sys.path.append("../")
-import plugin_utils
-
-
-_NAME = "Computer"
-_ICON = "computer.png"
-_MAP = {
-       "name": _NAME,
-       "keys": {
-               (0, 0): {
-                       "CENTER": {"action": "7", "type": "text", "text": "7", },
-                       "SOUTH": {"action": "d", "type": "text", "text": "D", },
-                       "showAllSlices": False,
-               },
-               (0, 1): {
-                       "CENTER": {"action": "8", "type": "text", "text": "8", },
-                       "SOUTH": {"action": "e", "type": "text", "text": "E", },
-                       "showAllSlices": False,
-               },
-               (0, 2): {
-                       "CENTER": {"action": "9", "type": "text", "text": "9", },
-                       "SOUTH": {"action": "f", "type": "text", "text": "F", },
-                       "showAllSlices": False,
-               },
-               (1, 0): {
-                       "CENTER": {"action": "4", "type": "text", "text": "4", },
-                       "NORTH_EAST": {"action": "0o", "type": "text", "text": "0o", },
-                       "EAST": {"action": "0x", "type": "text", "text": "0x", },
-                       "SOUTH_EAST": {"action": "0b", "type": "text", "text": "0b", },
-                       "showAllSlices": True,
-               },
-               (1, 1): {
-                       "CENTER": {"action": "5", "type": "text", "text": "5", },
-                       "NORTH": {"action": "[&]", "type": "text", "text": "and", },
-                       "WEST": {"action": "[|]", "type": "text", "text": "or", },
-                       "SOUTH": {"action": "[~]", "type": "text", "text": "not", },
-                       "EAST": {"action": "[^]", "type": "text", "text": "xor", },
-                       "showAllSlices": True,
-               },
-               (1, 2): {
-                       "CENTER": {"action": "6", "type": "text", "text": "6", },
-                       "NORTH_WEST": {"action": "[oct]", "type": "text", "text": "-> oct", },
-                       "WEST": {"action": "[dec]", "type": "text", "text": "-> dec", },
-                       "SOUTH_WEST": {"action": "[hex]", "type": "text", "text": "-> hex", },
-                       "showAllSlices": True,
-               },
-               (2, 0): {
-                       "CENTER": {"action": "1", "type": "text", "text": "1", },
-                       "NORTH": {"action": "a", "type": "text", "text": "A", },
-                       "EAST": {"action": "0", "type": "text", "text": "0", },
-                       "showAllSlices": False,
-               },
-               (2, 1): {
-                       "CENTER": {"action": "2", "type": "text", "text": "2", },
-                       "NORTH": {"action": "b", "type": "text", "text": "B", },
-                       "EAST": {"action": "[//]", "type": "text", "text": "x // y", },
-                       "WEST": {"action": "[%]", "type": "text", "text": "x % y", },
-                       "showAllSlices": False,
-               },
-               (2, 2): {
-                       "CENTER": {"action": "3", "type": "text", "text": "3", },
-                       "NORTH": {"action": "c", "type": "text", "text": "C", },
-                       "showAllSlices": False,
-               },
-       },
-}
-_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
-PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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/entry.ini b/src/plugins/entry.ini
deleted file mode 100644 (file)
index cbbaa4d..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[Operator]
-name=Entry
-version=0.1
-description=
-
-[Constants]
-name=Entry
-version=0.1
-description=
-
-[Keyboard]
-name=Entry
-version=0.1
-description=
diff --git a/src/plugins/entry.py b/src/plugins/entry.py
deleted file mode 100644 (file)
index 1383c63..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import division
-
-import os
-
-import sys
-sys.path.append("../")
-import plugin_utils
-
-
-_NAME = "Entry"
-_ICON = "newline.png"
-_MAP = {
-       "name": _NAME,
-       "keys": {
-               (0, 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,
-               },
-       },
-}
-_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
-PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_PATH)
diff --git a/src/plugins/images/alphabet.png b/src/plugins/images/alphabet.png
deleted file mode 100644 (file)
index 696449e..0000000
Binary files a/src/plugins/images/alphabet.png and /dev/null differ
diff --git a/src/plugins/images/alt.png b/src/plugins/images/alt.png
deleted file mode 100644 (file)
index 2e5c5c1..0000000
Binary files a/src/plugins/images/alt.png and /dev/null differ
diff --git a/src/plugins/images/arrows.png b/src/plugins/images/arrows.png
deleted file mode 100644 (file)
index 16bb714..0000000
Binary files a/src/plugins/images/arrows.png and /dev/null differ
diff --git a/src/plugins/images/backspace.png b/src/plugins/images/backspace.png
deleted file mode 100644 (file)
index 20dc09a..0000000
Binary files a/src/plugins/images/backspace.png and /dev/null differ
diff --git a/src/plugins/images/builtins.png b/src/plugins/images/builtins.png
deleted file mode 100644 (file)
index 4cce92a..0000000
Binary files a/src/plugins/images/builtins.png and /dev/null differ
diff --git a/src/plugins/images/clear.png b/src/plugins/images/clear.png
deleted file mode 100644 (file)
index fb07cd2..0000000
Binary files a/src/plugins/images/clear.png and /dev/null differ
diff --git a/src/plugins/images/computer.png b/src/plugins/images/computer.png
deleted file mode 100644 (file)
index 306e4bc..0000000
Binary files a/src/plugins/images/computer.png and /dev/null differ
diff --git a/src/plugins/images/control.png b/src/plugins/images/control.png
deleted file mode 100644 (file)
index b938b0f..0000000
Binary files a/src/plugins/images/control.png and /dev/null differ
diff --git a/src/plugins/images/newline.png b/src/plugins/images/newline.png
deleted file mode 100644 (file)
index 0ae2c53..0000000
Binary files a/src/plugins/images/newline.png and /dev/null differ
diff --git a/src/plugins/images/shift.png b/src/plugins/images/shift.png
deleted file mode 100644 (file)
index e1687c8..0000000
Binary files a/src/plugins/images/shift.png and /dev/null differ
diff --git a/src/plugins/images/space.png b/src/plugins/images/space.png
deleted file mode 100644 (file)
index 4efb4c2..0000000
Binary files a/src/plugins/images/space.png and /dev/null differ
diff --git a/src/plugins/images/super.png b/src/plugins/images/super.png
deleted file mode 100644 (file)
index 2f57f62..0000000
Binary files a/src/plugins/images/super.png and /dev/null differ
diff --git a/src/plugins/images/symbols.dia b/src/plugins/images/symbols.dia
deleted file mode 100644 (file)
index a04fe5a..0000000
Binary files a/src/plugins/images/symbols.dia and /dev/null differ
diff --git a/src/plugins/images/tab.png b/src/plugins/images/tab.png
deleted file mode 100644 (file)
index 1450d81..0000000
Binary files a/src/plugins/images/tab.png and /dev/null differ
diff --git a/src/plugins/images/trig.png b/src/plugins/images/trig.png
deleted file mode 100644 (file)
index c03a585..0000000
Binary files a/src/plugins/images/trig.png and /dev/null differ
diff --git a/src/plugins/trig.ini b/src/plugins/trig.ini
deleted file mode 100644 (file)
index fb9111f..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-[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.py b/src/plugins/trig.py
deleted file mode 100644 (file)
index 5c33a52..0000000
+++ /dev/null
@@ -1,143 +0,0 @@
-from __future__ import division
-
-import os
-import math
-import cmath
-
-import operation
-
-import sys
-sys.path.append("../")
-import plugin_utils
-
-
-_NAME = "Trigonometry"
-_ICON = "trig.png"
-_MAP = {
-       "name": _NAME,
-       "keys": {
-               (0, 0): {
-                       "CENTER": {"action": "7", "type": "text", "text": "7", },
-                       "SOUTH": {"action": "[sinh]", "type": "text", "text": "sinh", },
-                       "SOUTH_EAST": {"action": "[cosh]", "type": "text", "text": "cosh", },
-                       "EAST": {"action": "[tanh]", "type": "text", "text": "tanh", },
-                       "showAllSlices": False,
-               },
-               (0, 1): {
-                       "CENTER": {"action": "8", "type": "text", "text": "8", },
-                       "showAllSlices": False,
-               },
-               (0, 2): {
-                       "CENTER": {"action": "9", "type": "text", "text": "9", },
-                       "SOUTH": {"action": "[asinh]", "type": "text", "text": "asinh", },
-                       "SOUTH_WEST": {"action": "[acosh]", "type": "text", "text": "acosh", },
-                       "WEST": {"action": "[atanh]", "type": "text", "text": "atanh", },
-                       "showAllSlices": True,
-               },
-               (1, 0): {
-                       "CENTER": {"action": "4", "type": "text", "text": "4", },
-                       "showAllSlices": True,
-               },
-               (1, 1): {
-                       "CENTER": {"action": "5", "type": "text", "text": "5", },
-                       "NORTH": {"action": "[exp]", "type": "text", "text": "e ** x", },
-                       "SOUTH": {"action": "[log]", "type": "text", "text": "ln", },
-                       "WEST": {"action": "e", "type": "text", "text": "e", },
-                       "EAST": {"action": "j", "type": "text", "text": "j", },
-                       "showAllSlices": True,
-               },
-               (1, 2): {
-                       "CENTER": {"action": "6", "type": "text", "text": "6", },
-                       "WEST": {"action": "pi", "type": "text", "text": "pi", },
-                       "NORTH": {"action": "[rad]", "type": "text", "text": "-> rad", },
-                       "SOUTH": {"action": "[deg]", "type": "text", "text": "-> deg", },
-                       "showAllSlices": True,
-               },
-               (2, 0): {
-                       "CENTER": {"action": "1", "type": "text", "text": "1", },
-                       "NORTH": {"action": ".", "type": "text", "text": ".", },
-                       "EAST": {"action": "0", "type": "text", "text": "0", },
-                       "showAllSlices": True,
-               },
-               (2, 1): {
-                       "CENTER": {"action": "2", "type": "text", "text": "2", },
-                       "WEST": {"action": "[sin]", "type": "text", "text": "sin", },
-                       "NORTH": {"action": "[cos]", "type": "text", "text": "cos", },
-                       "EAST": {"action": "[tan]", "type": "text", "text": "tan", },
-                       "showAllSlices": True,
-               },
-               (2, 2): {
-                       "CENTER": {"action": "3", "type": "text", "text": "3", },
-                       "NORTH": {"action": "[asin]", "type": "text", "text": "asin", },
-                       "NORTH_WEST": {"action": "[acos]", "type": "text", "text": "acos", },
-                       "WEST": {"action": "[atan]", "type": "text", "text": "atan", },
-                       "showAllSlices": False,
-               },
-       },
-}
-_ICON_PATH = [os.path.join(os.path.dirname(__file__), "images")]
-PLUGIN = plugin_utils.PieKeyboardPluginFactory(_NAME, _ICON, _MAP, _ICON_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)
-
diff --git a/src/qhistory.py b/src/qhistory.py
deleted file mode 100644 (file)
index 1dea45d..0000000
+++ /dev/null
@@ -1,183 +0,0 @@
-#!/usr/bin/env python
-
-"""
-http://www.grigoriev.ru/svgmath/ (MathML->SVG in Python)
-http://helm.cs.unibo.it/mml-widget/ (MathML widget in C++)
-"""
-
-from __future__ import with_statement
-
-import logging
-
-import util.qt_compat as qt_compat
-QtCore = qt_compat.QtCore
-QtGui = qt_compat.import_module("QtGui")
-
-from util import qui_utils
-import util.misc as misc_utils
-import history
-import operation
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class QCalcHistory(history.AbstractHistory):
-
-       _CLOSE_COLUMN = 0
-       _EQ_COLUMN = 1
-       _RESULT_COLUMN = 2
-
-       def __init__(self, errorReporter):
-               super(QCalcHistory, self).__init__()
-               self._prettyRenderer = operation.render_number()
-               self._errorLog = errorReporter
-
-               self._historyStore = QtGui.QStandardItemModel()
-               self._historyStore.setHorizontalHeaderLabels(["", "Equation", "Result"])
-               self._historyStore.itemChanged.connect(self._on_item_changed)
-
-               self._historyView = QtGui.QTreeView()
-               self._historyView.setModel(self._historyStore)
-               self._historyView.setUniformRowHeights(True)
-               self._historyView.setRootIsDecorated(False)
-               self._historyView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
-               self._historyView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
-               self._historyView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
-               self._historyView.setHeaderHidden(True)
-               self._historyView.activated.connect(self._on_row_activated)
-
-               viewHeader = self._historyView.header()
-               viewHeader.setSortIndicatorShown(True)
-               viewHeader.setClickable(True)
-
-               viewHeader.setResizeMode(self._CLOSE_COLUMN, QtGui.QHeaderView.ResizeToContents)
-               viewHeader.setResizeMode(self._EQ_COLUMN, QtGui.QHeaderView.Stretch)
-               viewHeader.setResizeMode(self._RESULT_COLUMN, QtGui.QHeaderView.ResizeToContents)
-               viewHeader.setStretchLastSection(False)
-
-               self._rowCount = 0
-               self._programmaticUpdate = False
-               self._closeIcon = qui_utils.get_theme_icon(("window-close", "general_close", "gtk-close"))
-
-       @property
-       def toplevel(self):
-               return self._historyView
-
-       def push(self, node):
-               simpleNode = node.simplify()
-
-               closeIcon = self._closeIcon
-               icon = QtGui.QStandardItem(closeIcon, "")
-               icon.setEditable(False)
-               icon.setCheckable(False)
-               equation = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, node))
-               equation.setData(node)
-               equation.setCheckable(False)
-               eqFont = equation.font()
-               eqFont.setPointSize(max(eqFont.pointSize() - 3, 5))
-               equation.setFont(eqFont)
-
-               result = QtGui.QStandardItem(operation.render_operation(self._prettyRenderer, simpleNode))
-               result.setData(simpleNode)
-               result.setEditable(False)
-               result.setCheckable(False)
-
-               row = (icon, equation, result)
-               self._historyStore.appendRow(row)
-
-               index = result.index()
-               self._historyView.scrollToBottom()
-               self._rowCount += 1
-
-       def pop(self):
-               if len(self) == 0:
-                       raise IndexError("Not enough items in the history for the operation")
-
-               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
-               self._rowCount -= 1
-               return equation.data()
-
-       def peek(self):
-               if len(self) == 0:
-                       raise IndexError("Not enough items in the history for the operation")
-
-               icon, equation, result = self._historyStore.takeRow(self._rowCount - 1)
-               row = (icon, equation, result)
-               self._historyStore.appendRow(row)
-
-               return equation.data()
-
-       def clear(self):
-               self._historyStore.clear()
-               self._rowCount = 0
-
-       def scroll_to_bottom(self):
-               self._historyView.scrollToBottom()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_row_activated(self, index):
-               with qui_utils.notify_error(self._errorLog):
-                       if index.column() == self._CLOSE_COLUMN:
-                               self._historyStore.removeRow(index.row(), index.parent())
-                               self._rowCount -= 1
-                       elif index.column() == self._EQ_COLUMN:
-                               self._duplicate_row(index)
-                       elif index.column() == self._RESULT_COLUMN:
-                               self._duplicate_row(index)
-                       else:
-                               raise NotImplementedError("Unsupported column to activate %s" % index.column())
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_item_changed(self, item):
-               with qui_utils.notify_error(self._errorLog):
-                       if self._programmaticUpdate:
-                               _moduleLogger.info("Blocking updating %r recursively" % item)
-                               return
-                       self._programmaticUpdate = True
-                       try:
-                               if item.column() in [self._EQ_COLUMN, self._RESULT_COLUMN]:
-                                       self._update_input(item)
-                               else:
-                                       raise NotImplementedError("Unsupported column to edit %s" % item.column())
-                       except StandardError, e:
-                               self._errorReporter.push_exception()
-                       finally:
-                               self._programmaticUpdate = False
-
-       def _duplicate_row(self, index):
-               item = self._historyStore.item(index.row(), self._EQ_COLUMN)
-               self.push(item.data())
-
-       def _parse_value(self, value):
-               raise NotImplementedError("What?")
-
-       def _update_input(self, item):
-               node = item.data()
-               try:
-                       eqNode = self._parse_value(str(item.text()))
-                       newText = operation.render_operation(self._prettyRenderer, eqNode)
-
-                       eqItem = self._historyStore.item(item.row(), self._EQ_COLUMN)
-                       eqItem.setData(eqNode)
-                       eqItem.setText(newText)
-
-                       resultNode = eqNode.simplify()
-                       resultText = operation.render_operation(self._prettyRenderer, resultNode)
-                       resultItem = self._historyStore.item(item.row(), self._RESULT_COLUMN)
-                       resultItem.setData(resultNode)
-                       resultItem.setText(resultText)
-               except:
-                       oldText = operation.render_operation(self._prettyRenderer, node)
-                       item.setText(oldText)
-                       raise
-
-       def __len__(self):
-               return self._rowCount
-
-       def __iter__(self):
-               for i in xrange(self._rowCount):
-                       item = self._historyStore.item(i, self._EQ_COLUMN)
-                       if item is None:
-                               continue
-                       yield item.data()
diff --git a/src/util/__init__.py b/src/util/__init__.py
deleted file mode 100644 (file)
index 4265cc3..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#!/usr/bin/env python
diff --git a/src/util/algorithms.py b/src/util/algorithms.py
deleted file mode 100644 (file)
index e94fb61..0000000
+++ /dev/null
@@ -1,664 +0,0 @@
-#!/usr/bin/env python
-
-"""
-@note Source http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66448
-"""
-
-import itertools
-import functools
-import datetime
-import types
-import array
-import random
-
-
-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 product(*args, **kwds):
-       # product('ABCD', 'xy') --> Ax Ay Bx By Cx Cy Dx Dy
-       # product(range(2), repeat=3) --> 000 001 010 011 100 101 110 111
-       pools = map(tuple, args) * kwds.get('repeat', 1)
-       result = [[]]
-       for pool in pools:
-               result = [x+[y] for x in result for y in pool]
-       for prod in result:
-               yield tuple(prod)
-
-
-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
-
-
-def itr_available(queue, initiallyBlock = False):
-       if initiallyBlock:
-               yield queue.get()
-       while not queue.empty():
-               yield queue.get_nowait()
-
-
-class BloomFilter(object):
-       """
-       http://en.wikipedia.org/wiki/Bloom_filter
-       Sources:
-       http://code.activestate.com/recipes/577684-bloom-filter/
-       http://code.activestate.com/recipes/577686-bloom-filter/
-
-       >>> from random import sample
-       >>> from string import ascii_letters
-       >>> states = '''Alabama Alaska Arizona Arkansas California Colorado Connecticut
-       ... Delaware Florida Georgia Hawaii Idaho Illinois Indiana Iowa Kansas
-       ... Kentucky Louisiana Maine Maryland Massachusetts Michigan Minnesota
-       ... Mississippi Missouri Montana Nebraska Nevada NewHampshire NewJersey
-       ... NewMexico NewYork NorthCarolina NorthDakota Ohio Oklahoma Oregon
-       ... Pennsylvania RhodeIsland SouthCarolina SouthDakota Tennessee Texas Utah
-       ... Vermont Virginia Washington WestVirginia Wisconsin Wyoming'''.split()
-       >>> bf = BloomFilter(num_bits=1000, num_probes=14)
-       >>> for state in states:
-       ...     bf.add(state)
-       >>> numStatesFound = sum(state in bf for state in states)
-       >>> numStatesFound, len(states)
-       (50, 50)
-       >>> trials = 100
-       >>> numGarbageFound = sum(''.join(sample(ascii_letters, 5)) in bf for i in range(trials))
-       >>> numGarbageFound, trials
-       (0, 100)
-       """
-
-       def __init__(self, num_bits, num_probes):
-               num_words = (num_bits + 31) // 32
-               self._arr = array.array('B', [0]) * num_words
-               self._num_probes = num_probes
-
-       def add(self, key):
-               for i, mask in self._get_probes(key):
-                       self._arr[i] |= mask
-
-       def union(self, bfilter):
-               if self._match_template(bfilter):
-                       for i, b in enumerate(bfilter._arr):
-                               self._arr[i] |= b
-               else:
-                       # Union b/w two unrelated bloom filter raises this
-                       raise ValueError("Mismatched bloom filters")
-
-       def intersection(self, bfilter):
-               if self._match_template(bfilter):
-                       for i, b in enumerate(bfilter._arr):
-                               self._arr[i] &= b
-               else:
-                       # Intersection b/w two unrelated bloom filter raises this
-                       raise ValueError("Mismatched bloom filters")
-
-       def __contains__(self, key):
-               return all(self._arr[i] & mask for i, mask in self._get_probes(key))
-
-       def _match_template(self, bfilter):
-               return self.num_bits == bfilter.num_bits and self.num_probes == bfilter.num_probes
-
-       def _get_probes(self, key):
-               hasher = random.Random(key).randrange
-               for _ in range(self._num_probes):
-                       array_index = hasher(len(self._arr))
-                       bit_index = hasher(32)
-                       yield array_index, 1 << bit_index
-
-
-if __name__ == "__main__":
-       import doctest
-       print doctest.testmod()
diff --git a/src/util/concurrent.py b/src/util/concurrent.py
deleted file mode 100644 (file)
index f5f6e1d..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-
-import os
-import errno
-import time
-import functools
-import contextlib
-import logging
-
-import misc
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class AsyncTaskQueue(object):
-
-       def __init__(self, taskPool):
-               self._asyncs = []
-               self._taskPool = taskPool
-
-       def add_async(self, func):
-               self.flush()
-               a = AsyncGeneratorTask(self._taskPool, func)
-               self._asyncs.append(a)
-               return a
-
-       def flush(self):
-               self._asyncs = [a for a in self._asyncs if not a.isDone]
-
-
-class AsyncGeneratorTask(object):
-
-       def __init__(self, pool, func):
-               self._pool = pool
-               self._func = func
-               self._run = None
-               self._isDone = False
-
-       @property
-       def isDone(self):
-               return self._isDone
-
-       def start(self, *args, **kwds):
-               assert self._run is None, "Task already started"
-               self._run = self._func(*args, **kwds)
-               trampoline, args, kwds = self._run.send(None) # priming the function
-               self._pool.add_task(
-                       trampoline,
-                       args,
-                       kwds,
-                       self.on_success,
-                       self.on_error,
-               )
-
-       @misc.log_exception(_moduleLogger)
-       def on_success(self, result):
-               _moduleLogger.debug("Processing success for: %r", self._func)
-               try:
-                       trampoline, args, kwds = self._run.send(result)
-               except StopIteration, e:
-                       self._isDone = True
-               else:
-                       self._pool.add_task(
-                               trampoline,
-                               args,
-                               kwds,
-                               self.on_success,
-                               self.on_error,
-                       )
-
-       @misc.log_exception(_moduleLogger)
-       def on_error(self, error):
-               _moduleLogger.debug("Processing error for: %r", self._func)
-               try:
-                       trampoline, args, kwds = self._run.throw(error)
-               except StopIteration, e:
-                       self._isDone = True
-               else:
-                       self._pool.add_task(
-                               trampoline,
-                               args,
-                               kwds,
-                               self.on_success,
-                               self.on_error,
-                       )
-
-       def __repr__(self):
-               return "<async %s at 0x%x>" % (self._func.__name__, id(self))
-
-       def __hash__(self):
-               return hash(self._func)
-
-       def __eq__(self, other):
-               return self._func == other._func
-
-       def __ne__(self, other):
-               return self._func != other._func
-
-
-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)
diff --git a/src/util/coroutines.py b/src/util/coroutines.py
deleted file mode 100755 (executable)
index b1e539e..0000000
+++ /dev/null
@@ -1,623 +0,0 @@
-#!/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/util/go_utils.py b/src/util/go_utils.py
deleted file mode 100644 (file)
index 61e731d..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-
-import time
-import functools
-import threading
-import Queue
-import logging
-
-import gobject
-
-import algorithms
-import misc
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-def make_idler(func):
-       """
-       Decorator that makes a generator-function into a function that will continue execution on next call
-       """
-       a = []
-
-       @functools.wraps(func)
-       def decorated_func(*args, **kwds):
-               if not a:
-                       a.append(func(*args, **kwds))
-               try:
-                       a[0].next()
-                       return True
-               except StopIteration:
-                       del a[:]
-                       return False
-
-       return decorated_func
-
-
-def async(func):
-       """
-       Make a function mainloop friendly. the function will be called at the
-       next mainloop idle state.
-
-       >>> import misc
-       >>> misc.validate_decorator(async)
-       """
-
-       @functools.wraps(func)
-       def new_function(*args, **kwargs):
-
-               def async_function():
-                       func(*args, **kwargs)
-                       return False
-
-               gobject.idle_add(async_function)
-
-       return new_function
-
-
-class Async(object):
-
-       def __init__(self, func, once = True):
-               self.__func = func
-               self.__idleId = None
-               self.__once = once
-
-       def start(self):
-               assert self.__idleId is None
-               if self.__once:
-                       self.__idleId = gobject.idle_add(self._on_once)
-               else:
-                       self.__idleId = gobject.idle_add(self.__func)
-
-       def is_running(self):
-               return self.__idleId is not None
-
-       def cancel(self):
-               if self.__idleId is not None:
-                       gobject.source_remove(self.__idleId)
-                       self.__idleId = None
-
-       def __call__(self):
-               return self.start()
-
-       @misc.log_exception(_moduleLogger)
-       def _on_once(self):
-               self.cancel()
-               try:
-                       self.__func()
-               except Exception:
-                       pass
-               return False
-
-
-class Timeout(object):
-
-       def __init__(self, func, once = True):
-               self.__func = func
-               self.__timeoutId = None
-               self.__once = once
-
-       def start(self, **kwds):
-               assert self.__timeoutId is None
-
-               callback = self._on_once if self.__once else self.__func
-
-               assert len(kwds) == 1
-               timeoutInSeconds = kwds["seconds"]
-               assert 0 <= timeoutInSeconds
-
-               if timeoutInSeconds == 0:
-                       self.__timeoutId = gobject.idle_add(callback)
-               else:
-                       self.__timeoutId = timeout_add_seconds(timeoutInSeconds, callback)
-
-       def is_running(self):
-               return self.__timeoutId is not None
-
-       def cancel(self):
-               if self.__timeoutId is not None:
-                       gobject.source_remove(self.__timeoutId)
-                       self.__timeoutId = None
-
-       def __call__(self, **kwds):
-               return self.start(**kwds)
-
-       @misc.log_exception(_moduleLogger)
-       def _on_once(self):
-               self.cancel()
-               try:
-                       self.__func()
-               except Exception:
-                       pass
-               return False
-
-
-_QUEUE_EMPTY = object()
-
-
-class FutureThread(object):
-
-       def __init__(self):
-               self.__workQueue = Queue.Queue()
-               self.__thread = threading.Thread(
-                       name = type(self).__name__,
-                       target = self.__consume_queue,
-               )
-               self.__isRunning = True
-
-       def start(self):
-               self.__thread.start()
-
-       def stop(self):
-               self.__isRunning = False
-               for _ in algorithms.itr_available(self.__workQueue):
-                       pass # eat up queue to cut down dumb work
-               self.__workQueue.put(_QUEUE_EMPTY)
-
-       def clear_tasks(self):
-               for _ in algorithms.itr_available(self.__workQueue):
-                       pass # eat up queue to cut down dumb work
-
-       def add_task(self, func, args, kwds, on_success, on_error):
-               task = func, args, kwds, on_success, on_error
-               self.__workQueue.put(task)
-
-       @misc.log_exception(_moduleLogger)
-       def __trampoline_callback(self, on_success, on_error, isError, result):
-               if not self.__isRunning:
-                       if isError:
-                               _moduleLogger.error("Masking: %s" % (result, ))
-                       isError = True
-                       result = StopIteration("Cancelling all callbacks")
-               callback = on_success if not isError else on_error
-               try:
-                       callback(result)
-               except Exception:
-                       _moduleLogger.exception("Callback errored")
-               return False
-
-       @misc.log_exception(_moduleLogger)
-       def __consume_queue(self):
-               while True:
-                       task = self.__workQueue.get()
-                       if task is _QUEUE_EMPTY:
-                               break
-                       func, args, kwds, on_success, on_error = task
-
-                       try:
-                               result = func(*args, **kwds)
-                               isError = False
-                       except Exception, e:
-                               _moduleLogger.error("Error, passing it back to the main thread")
-                               result = e
-                               isError = True
-                       self.__workQueue.task_done()
-
-                       gobject.idle_add(self.__trampoline_callback, on_success, on_error, isError, result)
-               _moduleLogger.debug("Shutting down worker thread")
-
-
-class AutoSignal(object):
-
-       def __init__(self, toplevel):
-               self.__disconnectPool = []
-               toplevel.connect("destroy", self.__on_destroy)
-
-       def connect_auto(self, widget, *args):
-               id = widget.connect(*args)
-               self.__disconnectPool.append((widget, id))
-
-       @misc.log_exception(_moduleLogger)
-       def __on_destroy(self, widget):
-               _moduleLogger.info("Destroy: %r (%s to clean up)" % (self, len(self.__disconnectPool)))
-               for widget, id in self.__disconnectPool:
-                       widget.disconnect(id)
-               del self.__disconnectPool[:]
-
-
-def throttled(minDelay, queue):
-       """
-       Throttle the calls to a function by queueing all the calls that happen
-       before the minimum delay
-
-       >>> import misc
-       >>> import Queue
-       >>> misc.validate_decorator(throttled(0, Queue.Queue()))
-       """
-
-       def actual_decorator(func):
-
-               lastCallTime = [None]
-
-               def process_queue():
-                       if 0 < len(queue):
-                               func, args, kwargs = queue.pop(0)
-                               lastCallTime[0] = time.time() * 1000
-                               func(*args, **kwargs)
-                       return False
-
-               @functools.wraps(func)
-               def new_function(*args, **kwargs):
-                       now = time.time() * 1000
-                       if (
-                               lastCallTime[0] is None or
-                               (now - lastCallTime >= minDelay)
-                       ):
-                               lastCallTime[0] = now
-                               func(*args, **kwargs)
-                       else:
-                               queue.append((func, args, kwargs))
-                               lastCallDelta = now - lastCallTime[0]
-                               processQueueTimeout = int(minDelay * len(queue) - lastCallDelta)
-                               gobject.timeout_add(processQueueTimeout, process_queue)
-
-               return new_function
-
-       return actual_decorator
-
-
-def _old_timeout_add_seconds(timeout, callback):
-       return gobject.timeout_add(timeout * 1000, callback)
-
-
-def _timeout_add_seconds(timeout, callback):
-       return gobject.timeout_add_seconds(timeout, callback)
-
-
-try:
-       gobject.timeout_add_seconds
-       timeout_add_seconds = _timeout_add_seconds
-except AttributeError:
-       timeout_add_seconds = _old_timeout_add_seconds
diff --git a/src/util/gtk_utils.py b/src/util/gtk_utils.py
deleted file mode 100644 (file)
index 342feae..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-from __future__ import division
-
-import contextlib
-import logging
-
-import gtk
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-@contextlib.contextmanager
-def gtk_lock():
-       gtk.gdk.threads_enter()
-       try:
-               yield
-       finally:
-               gtk.gdk.threads_leave()
-
-
-def find_parent_window(widget):
-       while True:
-               parent = widget.get_parent()
-               if isinstance(parent, gtk.Window):
-                       return parent
-               widget = parent
-
-
-if __name__ == "__main__":
-       pass
-
diff --git a/src/util/hildonize.py b/src/util/hildonize.py
deleted file mode 100644 (file)
index 339eb2a..0000000
+++ /dev/null
@@ -1,766 +0,0 @@
-#!/usr/bin/env python
-
-"""
-Open Issues
-       @bug not all of a message is shown
-       @bug Buttons are too small
-"""
-
-
-import gobject
-import gtk
-import dbus
-
-
-class _NullHildonModule(object):
-       pass
-
-
-try:
-       import hildon as _hildon
-       hildon  = _hildon # Dumb but gets around pyflakiness
-except (ImportError, OSError):
-       hildon = _NullHildonModule
-
-
-IS_HILDON_SUPPORTED = hildon is not _NullHildonModule
-
-
-class _NullHildonProgram(object):
-
-       def add_window(self, window):
-               pass
-
-
-def _hildon_get_app_class():
-       return hildon.Program
-
-
-def _null_get_app_class():
-       return _NullHildonProgram
-
-
-try:
-       hildon.Program
-       get_app_class = _hildon_get_app_class
-except AttributeError:
-       get_app_class = _null_get_app_class
-
-
-def _hildon_set_application_name(name):
-       gtk.set_application_name(name)
-
-
-def _null_set_application_name(name):
-       pass
-
-
-try:
-       gtk.set_application_name
-       set_application_name = _hildon_set_application_name
-except AttributeError:
-       set_application_name = _null_set_application_name
-
-
-def _fremantle_hildonize_window(app, window):
-       oldWindow = window
-       newWindow = hildon.StackableWindow()
-       if oldWindow.get_child() is not None:
-               oldWindow.get_child().reparent(newWindow)
-       app.add_window(newWindow)
-       return newWindow
-
-
-def _hildon_hildonize_window(app, window):
-       oldWindow = window
-       newWindow = hildon.Window()
-       if oldWindow.get_child() is not None:
-               oldWindow.get_child().reparent(newWindow)
-       app.add_window(newWindow)
-       return newWindow
-
-
-def _null_hildonize_window(app, window):
-       return window
-
-
-try:
-       hildon.StackableWindow
-       hildonize_window = _fremantle_hildonize_window
-except AttributeError:
-       try:
-               hildon.Window
-               hildonize_window = _hildon_hildonize_window
-       except AttributeError:
-               hildonize_window = _null_hildonize_window
-
-
-def _fremantle_hildonize_menu(window, gtkMenu):
-       appMenu = hildon.AppMenu()
-       window.set_app_menu(appMenu)
-       gtkMenu.get_parent().remove(gtkMenu)
-       return appMenu
-
-
-def _hildon_hildonize_menu(window, gtkMenu):
-       hildonMenu = gtk.Menu()
-       for child in gtkMenu.get_children():
-               child.reparent(hildonMenu)
-       window.set_menu(hildonMenu)
-       gtkMenu.destroy()
-       return hildonMenu
-
-
-def _null_hildonize_menu(window, gtkMenu):
-       return gtkMenu
-
-
-try:
-       hildon.AppMenu
-       GTK_MENU_USED = False
-       IS_FREMANTLE_SUPPORTED = True
-       hildonize_menu = _fremantle_hildonize_menu
-except AttributeError:
-       GTK_MENU_USED = True
-       IS_FREMANTLE_SUPPORTED = False
-       if IS_HILDON_SUPPORTED:
-               hildonize_menu = _hildon_hildonize_menu
-       else:
-               hildonize_menu = _null_hildonize_menu
-
-
-def _hildon_set_button_auto_selectable(button):
-       button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT)
-
-
-def _null_set_button_auto_selectable(button):
-       pass
-
-
-try:
-       hildon.HILDON_SIZE_AUTO_HEIGHT
-       gtk.Button.set_theme_size
-       set_button_auto_selectable = _hildon_set_button_auto_selectable
-except AttributeError:
-       set_button_auto_selectable = _null_set_button_auto_selectable
-
-
-def _hildon_set_button_finger_selectable(button):
-       button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT)
-
-
-def _null_set_button_finger_selectable(button):
-       pass
-
-
-try:
-       hildon.HILDON_SIZE_FINGER_HEIGHT
-       gtk.Button.set_theme_size
-       set_button_finger_selectable = _hildon_set_button_finger_selectable
-except AttributeError:
-       set_button_finger_selectable = _null_set_button_finger_selectable
-
-
-def _hildon_set_button_thumb_selectable(button):
-       button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT)
-
-
-def _null_set_button_thumb_selectable(button):
-       pass
-
-
-try:
-       hildon.HILDON_SIZE_THUMB_HEIGHT
-       gtk.Button.set_theme_size
-       set_button_thumb_selectable = _hildon_set_button_thumb_selectable
-except AttributeError:
-       set_button_thumb_selectable = _null_set_button_thumb_selectable
-
-
-def _hildon_set_cell_thumb_selectable(renderer):
-       renderer.set_property("scale", 1.5)
-
-
-def _null_set_cell_thumb_selectable(renderer):
-       pass
-
-
-if IS_HILDON_SUPPORTED:
-       set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable
-else:
-       set_cell_thumb_selectable = _null_set_cell_thumb_selectable
-
-
-def _hildon_set_pix_cell_thumb_selectable(renderer):
-       renderer.set_property("stock-size", 48)
-
-
-def _null_set_pix_cell_thumb_selectable(renderer):
-       pass
-
-
-if IS_HILDON_SUPPORTED:
-       set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable
-else:
-       set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable
-
-
-def _fremantle_show_information_banner(parent, message):
-       hildon.hildon_banner_show_information(parent, "", message)
-
-
-def _hildon_show_information_banner(parent, message):
-       hildon.hildon_banner_show_information(parent, None, message)
-
-
-def _null_show_information_banner(parent, message):
-       pass
-
-
-if IS_FREMANTLE_SUPPORTED:
-       show_information_banner = _fremantle_show_information_banner
-else:
-       try:
-               hildon.hildon_banner_show_information
-               show_information_banner = _hildon_show_information_banner
-       except AttributeError:
-               show_information_banner = _null_show_information_banner
-
-
-def _fremantle_show_busy_banner_start(parent, message):
-       hildon.hildon_gtk_window_set_progress_indicator(parent, True)
-       return parent
-
-
-def _fremantle_show_busy_banner_end(banner):
-       hildon.hildon_gtk_window_set_progress_indicator(banner, False)
-
-
-def _hildon_show_busy_banner_start(parent, message):
-       return hildon.hildon_banner_show_animation(parent, None, message)
-
-
-def _hildon_show_busy_banner_end(banner):
-       banner.destroy()
-
-
-def _null_show_busy_banner_start(parent, message):
-       return None
-
-
-def _null_show_busy_banner_end(banner):
-       assert banner is None
-
-
-try:
-       hildon.hildon_gtk_window_set_progress_indicator
-       show_busy_banner_start = _fremantle_show_busy_banner_start
-       show_busy_banner_end = _fremantle_show_busy_banner_end
-except AttributeError:
-       try:
-               hildon.hildon_banner_show_animation
-               show_busy_banner_start = _hildon_show_busy_banner_start
-               show_busy_banner_end = _hildon_show_busy_banner_end
-       except AttributeError:
-               show_busy_banner_start = _null_show_busy_banner_start
-               show_busy_banner_end = _null_show_busy_banner_end
-
-
-def _hildon_hildonize_text_entry(textEntry):
-       textEntry.set_property('hildon-input-mode', 7)
-
-
-def _null_hildonize_text_entry(textEntry):
-       pass
-
-
-if IS_HILDON_SUPPORTED:
-       hildonize_text_entry = _hildon_hildonize_text_entry
-else:
-       hildonize_text_entry = _null_hildonize_text_entry
-
-
-def _hildon_window_to_portrait(window):
-       # gtk documentation is unclear whether this does a "=" or a "|="
-       flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST
-       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
-
-
-def _hildon_window_to_landscape(window):
-       # gtk documentation is unclear whether this does a "=" or a "&= ~"
-       flags = hildon.PORTRAIT_MODE_SUPPORT
-       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
-
-
-def _null_window_to_portrait(window):
-       pass
-
-
-def _null_window_to_landscape(window):
-       pass
-
-
-try:
-       hildon.PORTRAIT_MODE_SUPPORT
-       hildon.PORTRAIT_MODE_REQUEST
-       hildon.hildon_gtk_window_set_portrait_flags
-
-       window_to_portrait = _hildon_window_to_portrait
-       window_to_landscape = _hildon_window_to_landscape
-except AttributeError:
-       window_to_portrait = _null_window_to_portrait
-       window_to_landscape = _null_window_to_landscape
-
-
-def get_device_orientation():
-       bus = dbus.SystemBus()
-       try:
-               rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
-               mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request")
-               orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation()
-       except dbus.exception.DBusException:
-               # catching for documentation purposes that when a system doesn't
-               # support this, this is what to expect
-               raise
-
-       if orientation == "":
-               return gtk.ORIENTATION_HORIZONTAL
-       elif orientation == "":
-               return gtk.ORIENTATION_VERTICAL
-       else:
-               raise RuntimeError("Unknown orientation: %s" % orientation)
-
-
-def _hildon_hildonize_password_entry(textEntry):
-       textEntry.set_property('hildon-input-mode', 7 | (1 << 29))
-
-
-def _null_hildonize_password_entry(textEntry):
-       pass
-
-
-if IS_HILDON_SUPPORTED:
-       hildonize_password_entry = _hildon_hildonize_password_entry
-else:
-       hildonize_password_entry = _null_hildonize_password_entry
-
-
-def _hildon_hildonize_combo_entry(comboEntry):
-       comboEntry.set_property('hildon-input-mode', 1 << 4)
-
-
-def _null_hildonize_combo_entry(textEntry):
-       pass
-
-
-if IS_HILDON_SUPPORTED:
-       hildonize_combo_entry = _hildon_hildonize_combo_entry
-else:
-       hildonize_combo_entry = _null_hildonize_combo_entry
-
-
-def _null_create_seekbar():
-       adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
-       seek = gtk.HScale(adjustment)
-       seek.set_draw_value(False)
-       return seek
-
-
-def _fremantle_create_seekbar():
-       seek = hildon.Seekbar()
-       seek.set_range(0.0, 100)
-       seek.set_draw_value(False)
-       seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
-       return seek
-
-
-try:
-       hildon.Seekbar
-       create_seekbar = _fremantle_create_seekbar
-except AttributeError:
-       create_seekbar = _null_create_seekbar
-
-
-def _fremantle_hildonize_scrollwindow(scrolledWindow):
-       pannableWindow = hildon.PannableArea()
-
-       child = scrolledWindow.get_child()
-       scrolledWindow.remove(child)
-       pannableWindow.add(child)
-
-       parent = scrolledWindow.get_parent()
-       if parent is not None:
-               parent.remove(scrolledWindow)
-               parent.add(pannableWindow)
-
-       return pannableWindow
-
-
-def _hildon_hildonize_scrollwindow(scrolledWindow):
-       hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
-       return scrolledWindow
-
-
-def _null_hildonize_scrollwindow(scrolledWindow):
-       return scrolledWindow
-
-
-try:
-       hildon.PannableArea
-       hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
-       hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
-except AttributeError:
-       try:
-               hildon.hildon_helper_set_thumb_scrollbar
-               hildonize_scrollwindow = _hildon_hildonize_scrollwindow
-               hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
-       except AttributeError:
-               hildonize_scrollwindow = _null_hildonize_scrollwindow
-               hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
-
-
-def _hildon_request_number(parent, title, range, default):
-       spinner = hildon.NumberEditor(*range)
-       spinner.set_value(default)
-
-       dialog = gtk.Dialog(
-               title,
-               parent,
-               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
-               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
-       )
-       dialog.set_default_response(gtk.RESPONSE_CANCEL)
-       dialog.get_child().add(spinner)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-
-               if response == gtk.RESPONSE_OK:
-                       return spinner.get_value()
-               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-                       raise RuntimeError("User cancelled request")
-               else:
-                       raise RuntimeError("Unrecognized response %r", response)
-       finally:
-               dialog.hide()
-               dialog.destroy()
-
-
-def _null_request_number(parent, title, range, default):
-       adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
-       spinner = gtk.SpinButton(adjustment, 0, 0)
-       spinner.set_wrap(False)
-
-       dialog = gtk.Dialog(
-               title,
-               parent,
-               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
-               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
-       )
-       dialog.set_default_response(gtk.RESPONSE_CANCEL)
-       dialog.get_child().add(spinner)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-
-               if response == gtk.RESPONSE_OK:
-                       return spinner.get_value_as_int()
-               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-                       raise RuntimeError("User cancelled request")
-               else:
-                       raise RuntimeError("Unrecognized response %r", response)
-       finally:
-               dialog.hide()
-               dialog.destroy()
-
-
-try:
-       hildon.NumberEditor # TODO deprecated in fremantle
-       request_number = _hildon_request_number
-except AttributeError:
-       request_number = _null_request_number
-
-
-def _hildon_touch_selector(parent, title, items, defaultIndex):
-       model = gtk.ListStore(gobject.TYPE_STRING)
-       for item in items:
-               model.append((item, ))
-
-       selector = hildon.TouchSelector()
-       selector.append_text_column(model, True)
-       selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
-       selector.set_active(0, defaultIndex)
-
-       dialog = hildon.PickerDialog(parent)
-       dialog.set_selector(selector)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-
-               if response == gtk.RESPONSE_OK:
-                       return selector.get_active(0)
-               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-                       raise RuntimeError("User cancelled request")
-               else:
-                       raise RuntimeError("Unrecognized response %r", response)
-       finally:
-               dialog.hide()
-               dialog.destroy()
-
-
-def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
-       dialog.response(gtk.RESPONSE_OK)
-       pathData[0] = path
-
-
-def _null_touch_selector(parent, title, items, defaultIndex = -1):
-       parentSize = parent.get_size()
-
-       model = gtk.ListStore(gobject.TYPE_STRING)
-       for item in items:
-               model.append((item, ))
-
-       cell = gtk.CellRendererText()
-       set_cell_thumb_selectable(cell)
-       column = gtk.TreeViewColumn(title)
-       column.pack_start(cell, expand=True)
-       column.add_attribute(cell, "text", 0)
-
-       treeView = gtk.TreeView()
-       treeView.set_model(model)
-       treeView.append_column(column)
-       selection = treeView.get_selection()
-       selection.set_mode(gtk.SELECTION_SINGLE)
-       if 0 < defaultIndex:
-               selection.select_path((defaultIndex, ))
-
-       scrolledWin = gtk.ScrolledWindow()
-       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-       scrolledWin.add(treeView)
-
-       dialog = gtk.Dialog(
-               title,
-               parent,
-               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
-               (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
-       )
-       dialog.set_default_response(gtk.RESPONSE_CANCEL)
-       dialog.get_child().add(scrolledWin)
-       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
-
-       scrolledWin = hildonize_scrollwindow(scrolledWin)
-       pathData = [None]
-       treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-
-               if response == gtk.RESPONSE_OK:
-                       if pathData[0] is None:
-                               raise RuntimeError("No selection made")
-                       return pathData[0][0]
-               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-                       raise RuntimeError("User cancelled request")
-               else:
-                       raise RuntimeError("Unrecognized response %r", response)
-       finally:
-               dialog.hide()
-               dialog.destroy()
-
-
-try:
-       hildon.PickerDialog
-       hildon.TouchSelector
-       touch_selector = _hildon_touch_selector
-except AttributeError:
-       touch_selector = _null_touch_selector
-
-
-def _hildon_touch_selector_entry(parent, title, items, defaultItem):
-       # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
-       try:
-               selector = hildon.TouchSelectorEntry(text=True)
-       except TypeError:
-               selector = hildon.hildon_touch_selector_entry_new_text()
-       defaultIndex = -1
-       for i, item in enumerate(items):
-               selector.append_text(item)
-               if item == defaultItem:
-                       defaultIndex = i
-
-       dialog = hildon.PickerDialog(parent)
-       dialog.set_selector(selector)
-
-       if 0 < defaultIndex:
-               selector.set_active(0, defaultIndex)
-       else:
-               selector.get_entry().set_text(defaultItem)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-       finally:
-               dialog.hide()
-
-       if response == gtk.RESPONSE_OK:
-               return selector.get_entry().get_text()
-       elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-               raise RuntimeError("User cancelled request")
-       else:
-               raise RuntimeError("Unrecognized response %r", response)
-
-
-def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
-       custom = entry.get_text().strip()
-       if custom:
-               result[0] = custom
-               selection.unselect_all()
-       else:
-               result[0] = None
-               selection.select_path((defaultIndex, ))
-
-
-def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
-       dialog.response(gtk.RESPONSE_OK)
-       result[0] = customEntry.get_text()
-
-
-def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
-       dialog.response(gtk.RESPONSE_OK)
-       model = treeView.get_model()
-       itr = model.get_iter(path)
-       if itr is not None:
-               result[0] = model.get_value(itr, 0)
-
-
-def _null_touch_selector_entry(parent, title, items, defaultItem):
-       parentSize = parent.get_size()
-
-       model = gtk.ListStore(gobject.TYPE_STRING)
-       defaultIndex = -1
-       for i, item in enumerate(items):
-               model.append((item, ))
-               if item == defaultItem:
-                       defaultIndex = i
-
-       cell = gtk.CellRendererText()
-       set_cell_thumb_selectable(cell)
-       column = gtk.TreeViewColumn(title)
-       column.pack_start(cell, expand=True)
-       column.add_attribute(cell, "text", 0)
-
-       treeView = gtk.TreeView()
-       treeView.set_model(model)
-       treeView.append_column(column)
-       selection = treeView.get_selection()
-       selection.set_mode(gtk.SELECTION_SINGLE)
-
-       scrolledWin = gtk.ScrolledWindow()
-       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
-       scrolledWin.add(treeView)
-
-       customEntry = gtk.Entry()
-
-       layout = gtk.VBox()
-       layout.pack_start(customEntry, expand=False)
-       layout.pack_start(scrolledWin)
-
-       dialog = gtk.Dialog(
-               title,
-               parent,
-               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
-               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
-       )
-       dialog.set_default_response(gtk.RESPONSE_CANCEL)
-       dialog.get_child().add(layout)
-       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
-
-       scrolledWin = hildonize_scrollwindow(scrolledWin)
-
-       result = [None]
-       if 0 < defaultIndex:
-               selection.select_path((defaultIndex, ))
-               result[0] = defaultItem
-       else:
-               customEntry.set_text(defaultItem)
-
-       customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
-       customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
-       treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
-
-       try:
-               dialog.show_all()
-               response = dialog.run()
-
-               if response == gtk.RESPONSE_OK:
-                       _, itr = selection.get_selected()
-                       if itr is not None:
-                               return model.get_value(itr, 0)
-                       else:
-                               enteredText = customEntry.get_text().strip()
-                               if enteredText:
-                                       return enteredText
-                               elif result[0] is not None:
-                                       return result[0]
-                               else:
-                                       raise RuntimeError("No selection made")
-               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
-                       raise RuntimeError("User cancelled request")
-               else:
-                       raise RuntimeError("Unrecognized response %r", response)
-       finally:
-               dialog.hide()
-               dialog.destroy()
-
-
-try:
-       hildon.PickerDialog
-       hildon.TouchSelectorEntry
-       touch_selector_entry = _hildon_touch_selector_entry
-except AttributeError:
-       touch_selector_entry = _null_touch_selector_entry
-
-
-if __name__ == "__main__":
-       app = get_app_class()()
-
-       label = gtk.Label("Hello World from a Label!")
-
-       win = gtk.Window()
-       win.add(label)
-       win = hildonize_window(app, win)
-       if False and IS_FREMANTLE_SUPPORTED:
-               appMenu = hildon.AppMenu()
-               for i in xrange(5):
-                       b = gtk.Button(str(i))
-                       appMenu.append(b)
-               win.set_app_menu(appMenu)
-               win.show_all()
-               appMenu.show_all()
-               gtk.main()
-       elif False:
-               print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
-       elif False:
-               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
-               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
-       elif False:
-               import pprint
-               name, value = "", ""
-               goodLocals = [
-                       (name, value) for (name, value) in locals().iteritems()
-                       if not name.startswith("_")
-               ]
-               pprint.pprint(goodLocals)
-       elif False:
-               import time
-               show_information_banner(win, "Hello World")
-               time.sleep(5)
-       elif False:
-               import time
-               banner = show_busy_banner_start(win, "Hello World")
-               time.sleep(5)
-               show_busy_banner_end(banner)
diff --git a/src/util/io.py b/src/util/io.py
deleted file mode 100644 (file)
index 4198f4b..0000000
+++ /dev/null
@@ -1,231 +0,0 @@
-#!/usr/bin/env python
-
-
-from __future__ import with_statement
-
-import os
-import pickle
-import contextlib
-import itertools
-import codecs
-from xml.sax import saxutils
-import csv
-try:
-       import cStringIO as StringIO
-except ImportError:
-       import StringIO
-
-
-@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
-
-
-class UTF8Recoder(object):
-       """
-       Iterator that reads an encoded stream and reencodes the input to UTF-8
-       """
-       def __init__(self, f, encoding):
-               self.reader = codecs.getreader(encoding)(f)
-
-       def __iter__(self):
-               return self
-
-       def next(self):
-               return self.reader.next().encode("utf-8")
-
-
-class UnicodeReader(object):
-       """
-       A CSV reader which will iterate over lines in the CSV file "f",
-       which is encoded in the given encoding.
-       """
-
-       def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
-               f = UTF8Recoder(f, encoding)
-               self.reader = csv.reader(f, dialect=dialect, **kwds)
-
-       def next(self):
-               row = self.reader.next()
-               return [unicode(s, "utf-8") for s in row]
-
-       def __iter__(self):
-               return self
-
-class UnicodeWriter(object):
-       """
-       A CSV writer which will write rows to CSV file "f",
-       which is encoded in the given encoding.
-       """
-
-       def __init__(self, f, dialect=csv.excel, encoding="utf-8", **kwds):
-               # Redirect output to a queue
-               self.queue = StringIO.StringIO()
-               self.writer = csv.writer(self.queue, dialect=dialect, **kwds)
-               self.stream = f
-               self.encoder = codecs.getincrementalencoder(encoding)()
-
-       def writerow(self, row):
-               self.writer.writerow([s.encode("utf-8") for s in row])
-               # Fetch UTF-8 output from the queue ...
-               data = self.queue.getvalue()
-               data = data.decode("utf-8")
-               # ... and reencode it into the target encoding
-               data = self.encoder.encode(data)
-               # write to the target stream
-               self.stream.write(data)
-               # empty queue
-               self.queue.truncate(0)
-
-       def writerows(self, rows):
-               for row in rows:
-                       self.writerow(row)
-
-
-def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs):
-       # csv.py doesn't do Unicode; encode temporarily as UTF-8:
-       csv_reader = csv.reader(utf_8_encoder(unicode_csv_data),
-                                                       dialect=dialect, **kwargs)
-       for row in csv_reader:
-               # decode UTF-8 back to Unicode, cell by cell:
-               yield [unicode(cell, 'utf-8') for cell in row]
-
-
-def utf_8_encoder(unicode_csv_data):
-       for line in unicode_csv_data:
-               yield line.encode('utf-8')
-
-
-_UNESCAPE_ENTITIES = {
- "&quot;": '"',
- "&nbsp;": " ",
- "&#39;": "'",
-}
-
-
-_ESCAPE_ENTITIES = dict((v, k) for (v, k) in zip(_UNESCAPE_ENTITIES.itervalues(), _UNESCAPE_ENTITIES.iterkeys()))
-del _ESCAPE_ENTITIES[" "]
-
-
-def unescape(text):
-       plain = saxutils.unescape(text, _UNESCAPE_ENTITIES)
-       return plain
-
-
-def escape(text):
-       fancy = saxutils.escape(text, _ESCAPE_ENTITIES)
-       return fancy
diff --git a/src/util/linux.py b/src/util/linux.py
deleted file mode 100644 (file)
index 4e77445..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-#!/usr/bin/env python
-
-
-import os
-import logging
-
-try:
-       from xdg import BaseDirectory as _BaseDirectory
-       BaseDirectory = _BaseDirectory
-except ImportError:
-       BaseDirectory = None
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-_libc = None
-
-
-def set_process_name(name):
-       try: # change process name for killall
-               global _libc
-               if _libc is None:
-                       import ctypes
-                       _libc = ctypes.CDLL('libc.so.6')
-               _libc.prctl(15, name, 0, 0, 0)
-       except Exception, e:
-               _moduleLogger.warning('Unable to set processName: %s" % e')
-
-
-def get_new_resource(resourceType, resource, name):
-       if BaseDirectory is not None:
-               if resourceType == "data":
-                       base = BaseDirectory.xdg_data_home
-                       if base == "/usr/share/mime":
-                               # Ugly hack because somehow Maemo 4.1 seems to be set to this
-                               base = os.path.join(os.path.expanduser("~"), ".%s" % resource)
-               elif resourceType == "config":
-                       base = BaseDirectory.xdg_config_home
-               elif resourceType == "cache":
-                       base = BaseDirectory.xdg_cache_home
-               else:
-                       raise RuntimeError("Unknown type: "+resourceType)
-       else:
-               base = os.path.join(os.path.expanduser("~"), ".%s" % resource)
-
-       filePath = os.path.join(base, resource, name)
-       dirPath = os.path.dirname(filePath)
-       if not os.path.exists(dirPath):
-               # Looking before I leap to not mask errors
-               os.makedirs(dirPath)
-
-       return filePath
-
-
-def get_existing_resource(resourceType, resource, name):
-       if BaseDirectory is not None:
-               if resourceType == "data":
-                       base = BaseDirectory.xdg_data_home
-               elif resourceType == "config":
-                       base = BaseDirectory.xdg_config_home
-               elif resourceType == "cache":
-                       base = BaseDirectory.xdg_cache_home
-               else:
-                       raise RuntimeError("Unknown type: "+resourceType)
-       else:
-               base = None
-
-       if base is not None:
-               finalPath = os.path.join(base, name)
-               if os.path.exists(finalPath):
-                       return finalPath
-
-       altBase = os.path.join(os.path.expanduser("~"), ".%s" % resource)
-       finalPath = os.path.join(altBase, name)
-       if os.path.exists(finalPath):
-               return finalPath
-       else:
-               raise RuntimeError("Resource not found: %r" % ((resourceType, resource, name), ))
diff --git a/src/util/misc.py b/src/util/misc.py
deleted file mode 100644 (file)
index 9b8d88c..0000000
+++ /dev/null
@@ -1,900 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-
-import sys
-import re
-import cPickle
-
-import functools
-import contextlib
-import inspect
-
-import optparse
-import traceback
-import warnings
-import string
-
-
-class AnyData(object):
-
-       pass
-
-
-_indentationLevel = [0]
-
-
-def log_call(logger):
-
-       def log_call_decorator(func):
-
-               @functools.wraps(func)
-               def wrapper(*args, **kwds):
-                       logger.debug("%s> %s" % (" " * _indentationLevel[0], func.__name__, ))
-                       _indentationLevel[0] += 1
-                       try:
-                               return func(*args, **kwds)
-                       finally:
-                               _indentationLevel[0] -= 1
-                               logger.debug("%s< %s" % (" " * _indentationLevel[0], func.__name__, ))
-
-               return wrapper
-
-       return log_call_decorator
-
-
-def log_exception(logger):
-
-       def log_exception_decorator(func):
-
-               @functools.wraps(func)
-               def wrapper(*args, **kwds):
-                       try:
-                               return func(*args, **kwds)
-                       except Exception:
-                               logger.exception(func.__name__)
-                               raise
-
-               return wrapper
-
-       return log_exception_decorator
-
-
-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
-
-
-def into_debugger(func):
-       """
-       >>> validate_decorator(into_debugger)
-       """
-
-       @functools.wraps(func)
-       def newFunc(*args, **kwargs):
-               try:
-                       return func(*args, **kwargs)
-               except:
-                       import pdb
-                       pdb.post_mortem()
-
-       return newFunc
-
-
-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 nested_break():
-       """
-       >>> with nested_break() as mylabel:
-       ...     for i in xrange(3):
-       ...             print "Outer", i
-       ...             for j in xrange(3):
-       ...                     if i == 2: raise mylabel
-       ...                     if j == 2: break
-       ...                     print "Inner", j
-       ...             print "more processing"
-       Outer 0
-       Inner 0
-       Inner 1
-       Outer 1
-       Inner 0
-       Inner 1
-       Outer 2
-       """
-
-       class NestedBreakException(Exception):
-               pass
-
-       try:
-               yield NestedBreakException
-       except NestedBreakException:
-               pass
-
-
-@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
-
-
-def normalize_number(prettynumber):
-       """
-       function to take a phone number and strip out all non-numeric
-       characters
-
-       >>> normalize_number("+012-(345)-678-90")
-       '+01234567890'
-       >>> normalize_number("1-(345)-678-9000")
-       '+13456789000'
-       >>> normalize_number("+1-(345)-678-9000")
-       '+13456789000'
-       """
-       uglynumber = re.sub('[^0-9+]', '', prettynumber)
-       if uglynumber.startswith("+"):
-               pass
-       elif uglynumber.startswith("1"):
-               uglynumber = "+"+uglynumber
-       elif 10 <= len(uglynumber):
-               assert uglynumber[0] not in ("+", "1"), "Number format confusing"
-               uglynumber = "+1"+uglynumber
-       else:
-               pass
-
-       return uglynumber
-
-
-_VALIDATE_RE = re.compile("^\+?[0-9]{10,}$")
-
-
-def is_valid_number(number):
-       """
-       @returns If This number be called ( syntax validation only )
-       """
-       return _VALIDATE_RE.match(number) is not None
-
-
-def make_ugly(prettynumber):
-       """
-       function to take a phone number and strip out all non-numeric
-       characters
-
-       >>> make_ugly("+012-(345)-678-90")
-       '+01234567890'
-       """
-       return normalize_number(prettynumber)
-
-
-def _make_pretty_with_areacode(phonenumber):
-       prettynumber = "(%s)" % (phonenumber[0:3], )
-       if 3 < len(phonenumber):
-               prettynumber += " %s" % (phonenumber[3:6], )
-               if 6 < len(phonenumber):
-                       prettynumber += "-%s" % (phonenumber[6:], )
-       return prettynumber
-
-
-def _make_pretty_local(phonenumber):
-       prettynumber = "%s" % (phonenumber[0:3], )
-       if 3 < len(phonenumber):
-               prettynumber += "-%s" % (phonenumber[3:], )
-       return prettynumber
-
-
-def _make_pretty_international(phonenumber):
-       prettynumber = phonenumber
-       if phonenumber.startswith("1"):
-               prettynumber = "1 "
-               prettynumber += _make_pretty_with_areacode(phonenumber[1:])
-       return prettynumber
-
-
-def make_pretty(phonenumber):
-       """
-       Function to take a phone number and return the pretty version
-       pretty numbers:
-               if phonenumber begins with 0:
-                       ...-(...)-...-....
-               if phonenumber begins with 1: ( for gizmo callback numbers )
-                       1 (...)-...-....
-               if phonenumber is 13 digits:
-                       (...)-...-....
-               if phonenumber is 10 digits:
-                       ...-....
-       >>> make_pretty("12")
-       '12'
-       >>> make_pretty("1234567")
-       '123-4567'
-       >>> make_pretty("2345678901")
-       '+1 (234) 567-8901'
-       >>> make_pretty("12345678901")
-       '+1 (234) 567-8901'
-       >>> make_pretty("01234567890")
-       '+012 (345) 678-90'
-       >>> make_pretty("+01234567890")
-       '+012 (345) 678-90'
-       >>> make_pretty("+12")
-       '+1 (2)'
-       >>> make_pretty("+123")
-       '+1 (23)'
-       >>> make_pretty("+1234")
-       '+1 (234)'
-       """
-       if phonenumber is None or phonenumber == "":
-               return ""
-
-       phonenumber = normalize_number(phonenumber)
-
-       if phonenumber == "":
-               return ""
-       elif phonenumber[0] == "+":
-               prettynumber = _make_pretty_international(phonenumber[1:])
-               if not prettynumber.startswith("+"):
-                       prettynumber = "+"+prettynumber
-       elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
-               prettynumber = _make_pretty_international(phonenumber)
-       elif 7 < len(phonenumber):
-               prettynumber = _make_pretty_with_areacode(phonenumber)
-       elif 3 < len(phonenumber):
-               prettynumber = _make_pretty_local(phonenumber)
-       else:
-               prettynumber = phonenumber
-       return prettynumber.strip()
-
-
-def similar_ugly_numbers(lhs, rhs):
-       return (
-               lhs == rhs or
-               lhs[1:] == rhs and lhs.startswith("1") or
-               lhs[2:] == rhs and lhs.startswith("+1") or
-               lhs == rhs[1:] and rhs.startswith("1") or
-               lhs == rhs[2:] and rhs.startswith("+1")
-       )
-
-
-def abbrev_relative_date(date):
-       """
-       >>> abbrev_relative_date("42 hours ago")
-       '42 h'
-       >>> abbrev_relative_date("2 days ago")
-       '2 d'
-       >>> abbrev_relative_date("4 weeks ago")
-       '4 w'
-       """
-       parts = date.split(" ")
-       return "%s %s" % (parts[0], parts[1][0])
-
-
-def parse_version(versionText):
-       """
-       >>> parse_version("0.5.2")
-       [0, 5, 2]
-       """
-       return [
-               int(number)
-               for number in versionText.split(".")
-       ]
-
-
-def compare_versions(leftParsedVersion, rightParsedVersion):
-       """
-       >>> compare_versions([0, 1, 2], [0, 1, 2])
-       0
-       >>> compare_versions([0, 1, 2], [0, 1, 3])
-       -1
-       >>> compare_versions([0, 1, 2], [0, 2, 2])
-       -1
-       >>> compare_versions([0, 1, 2], [1, 1, 2])
-       -1
-       >>> compare_versions([0, 1, 3], [0, 1, 2])
-       1
-       >>> compare_versions([0, 2, 2], [0, 1, 2])
-       1
-       >>> compare_versions([1, 1, 2], [0, 1, 2])
-       1
-       """
-       for left, right in zip(leftParsedVersion, rightParsedVersion):
-               if left < right:
-                       return -1
-               elif right < left:
-                       return 1
-       else:
-               return 0
diff --git a/src/util/overloading.py b/src/util/overloading.py
deleted file mode 100644 (file)
index 89cb738..0000000
+++ /dev/null
@@ -1,256 +0,0 @@
-#!/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/util/qore_utils.py b/src/util/qore_utils.py
deleted file mode 100644 (file)
index 153558d..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-import logging
-
-import qt_compat
-QtCore = qt_compat.QtCore
-
-import misc
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class QThread44(QtCore.QThread):
-       """
-       This is to imitate QThread in Qt 4.4+ for when running on older version
-       See http://labs.trolltech.com/blogs/2010/06/17/youre-doing-it-wrong
-       (On Lucid I have Qt 4.7 and this is still an issue)
-       """
-
-       def __init__(self, parent = None):
-               QtCore.QThread.__init__(self, parent)
-
-       def run(self):
-               self.exec_()
-
-
-class _WorkerThread(QtCore.QObject):
-
-       _taskComplete  = qt_compat.Signal(object)
-
-       def __init__(self, futureThread):
-               QtCore.QObject.__init__(self)
-               self._futureThread = futureThread
-               self._futureThread._addTask.connect(self._on_task_added)
-               self._taskComplete.connect(self._futureThread._on_task_complete)
-
-       @qt_compat.Slot(object)
-       def _on_task_added(self, task):
-               self.__on_task_added(task)
-
-       @misc.log_exception(_moduleLogger)
-       def __on_task_added(self, task):
-               if not self._futureThread._isRunning:
-                       _moduleLogger.error("Dropping task")
-
-               func, args, kwds, on_success, on_error = task
-
-               try:
-                       result = func(*args, **kwds)
-                       isError = False
-               except Exception, e:
-                       _moduleLogger.error("Error, passing it back to the main thread")
-                       result = e
-                       isError = True
-
-               taskResult = on_success, on_error, isError, result
-               self._taskComplete.emit(taskResult)
-
-
-class FutureThread(QtCore.QObject):
-
-       _addTask = qt_compat.Signal(object)
-
-       def __init__(self):
-               QtCore.QObject.__init__(self)
-               self._thread = QThread44()
-               self._isRunning = False
-               self._worker = _WorkerThread(self)
-               self._worker.moveToThread(self._thread)
-
-       def start(self):
-               self._thread.start()
-               self._isRunning = True
-
-       def stop(self):
-               self._isRunning = False
-               self._thread.quit()
-
-       def add_task(self, func, args, kwds, on_success, on_error):
-               assert self._isRunning, "Task queue not started"
-               task = func, args, kwds, on_success, on_error
-               self._addTask.emit(task)
-
-       @qt_compat.Slot(object)
-       def _on_task_complete(self, taskResult):
-               self.__on_task_complete(taskResult)
-
-       @misc.log_exception(_moduleLogger)
-       def __on_task_complete(self, taskResult):
-               on_success, on_error, isError, result = taskResult
-               if not self._isRunning:
-                       if isError:
-                               _moduleLogger.error("Masking: %s" % (result, ))
-                       isError = True
-                       result = StopIteration("Cancelling all callbacks")
-               callback = on_success if not isError else on_error
-               try:
-                       callback(result)
-               except Exception:
-                       _moduleLogger.exception("Callback errored")
diff --git a/src/util/qt_compat.py b/src/util/qt_compat.py
deleted file mode 100644 (file)
index 2ab7fa4..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-from __future__ import division
-
-#try:
-#      import PySide.QtCore as _QtCore
-#      QtCore = _QtCore
-#      USES_PYSIDE = True
-#except ImportError:
-if True:
-       import sip
-       sip.setapi('QString', 2)
-       sip.setapi('QVariant', 2)
-       import PyQt4.QtCore as _QtCore
-       QtCore = _QtCore
-       USES_PYSIDE = False
-
-
-def _pyside_import_module(moduleName):
-       pyside = __import__('PySide', globals(), locals(), [moduleName], -1)
-       return getattr(pyside, moduleName)
-
-
-def _pyqt4_import_module(moduleName):
-       pyside = __import__('PyQt4', globals(), locals(), [moduleName], -1)
-       return getattr(pyside, moduleName)
-
-
-if USES_PYSIDE:
-       import_module = _pyside_import_module
-
-       Signal = QtCore.Signal
-       Slot = QtCore.Slot
-       Property = QtCore.Property
-else:
-       import_module = _pyqt4_import_module
-
-       Signal = QtCore.pyqtSignal
-       Slot = QtCore.pyqtSlot
-       Property = QtCore.pyqtProperty
-
-
-if __name__ == "__main__":
-       pass
-
diff --git a/src/util/qtpie.py b/src/util/qtpie.py
deleted file mode 100755 (executable)
index 6b77d5d..0000000
+++ /dev/null
@@ -1,1094 +0,0 @@
-#!/usr/bin/env python
-
-import math
-import logging
-
-import qt_compat
-QtCore = qt_compat.QtCore
-QtGui = qt_compat.import_module("QtGui")
-
-import misc as misc_utils
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-_TWOPI = 2 * math.pi
-
-
-def _radius_at(center, pos):
-       delta = pos - center
-       xDelta = delta.x()
-       yDelta = delta.y()
-
-       radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
-       return radius
-
-
-def _angle_at(center, pos):
-       delta = pos - center
-       xDelta = delta.x()
-       yDelta = delta.y()
-
-       radius = math.sqrt(xDelta ** 2 + yDelta ** 2)
-       angle = math.acos(xDelta / radius)
-       if 0 <= yDelta:
-               angle = _TWOPI - angle
-
-       return angle
-
-
-class QActionPieItem(object):
-
-       def __init__(self, action, weight = 1):
-               self._action = action
-               self._weight = weight
-
-       def action(self):
-               return self._action
-
-       def setWeight(self, weight):
-               self._weight = weight
-
-       def weight(self):
-               return self._weight
-
-       def setEnabled(self, enabled = True):
-               self._action.setEnabled(enabled)
-
-       def isEnabled(self):
-               return self._action.isEnabled()
-
-
-class PieFiling(object):
-
-       INNER_RADIUS_DEFAULT = 64
-       OUTER_RADIUS_DEFAULT = 192
-
-       SELECTION_CENTER = -1
-       SELECTION_NONE = -2
-
-       NULL_CENTER = QActionPieItem(QtGui.QAction(None))
-
-       def __init__(self):
-               self._innerRadius = self.INNER_RADIUS_DEFAULT
-               self._outerRadius = self.OUTER_RADIUS_DEFAULT
-               self._children = []
-               self._center = self.NULL_CENTER
-
-               self._cacheIndexToAngle = {}
-               self._cacheTotalWeight = 0
-
-       def insertItem(self, item, index = -1):
-               self._children.insert(index, item)
-               self._invalidate_cache()
-
-       def removeItemAt(self, index):
-               item = self._children.pop(index)
-               self._invalidate_cache()
-
-       def set_center(self, item):
-               if item is None:
-                       item = self.NULL_CENTER
-               self._center = item
-
-       def center(self):
-               return self._center
-
-       def clear(self):
-               del self._children[:]
-               self._center = self.NULL_CENTER
-               self._invalidate_cache()
-
-       def itemAt(self, index):
-               return self._children[index]
-
-       def indexAt(self, center, point):
-               return self._angle_to_index(_angle_at(center, point))
-
-       def innerRadius(self):
-               return self._innerRadius
-
-       def setInnerRadius(self, radius):
-               self._innerRadius = radius
-
-       def outerRadius(self):
-               return self._outerRadius
-
-       def setOuterRadius(self, radius):
-               self._outerRadius = radius
-
-       def __iter__(self):
-               return iter(self._children)
-
-       def __len__(self):
-               return len(self._children)
-
-       def __getitem__(self, index):
-               return self._children[index]
-
-       def _invalidate_cache(self):
-               self._cacheIndexToAngle.clear()
-               self._cacheTotalWeight = sum(child.weight() for child in self._children)
-               if self._cacheTotalWeight == 0:
-                       self._cacheTotalWeight = 1
-
-       def _index_to_angle(self, index, isShifted):
-               key = index, isShifted
-               if key in self._cacheIndexToAngle:
-                       return self._cacheIndexToAngle[key]
-               index = index % len(self._children)
-
-               baseAngle = _TWOPI / self._cacheTotalWeight
-
-               angle = math.pi / 2
-               if isShifted:
-                       if self._children:
-                               angle -= (self._children[0].weight() * baseAngle) / 2
-                       else:
-                               angle -= baseAngle / 2
-               while angle < 0:
-                       angle += _TWOPI
-
-               for i, child in enumerate(self._children):
-                       if index < i:
-                               break
-                       angle += child.weight() * baseAngle
-               while _TWOPI < angle:
-                       angle -= _TWOPI
-
-               self._cacheIndexToAngle[key] = angle
-               return angle
-
-       def _angle_to_index(self, angle):
-               numChildren = len(self._children)
-               if numChildren == 0:
-                       return self.SELECTION_CENTER
-
-               baseAngle = _TWOPI / self._cacheTotalWeight
-
-               iterAngle = math.pi / 2 - (self.itemAt(0).weight() * baseAngle) / 2
-               while iterAngle < 0:
-                       iterAngle += _TWOPI
-
-               oldIterAngle = iterAngle
-               for index, child in enumerate(self._children):
-                       iterAngle += child.weight() * baseAngle
-                       if oldIterAngle < angle and angle <= iterAngle:
-                               return index - 1 if index != 0 else numChildren - 1
-                       elif oldIterAngle < (angle + _TWOPI) and (angle + _TWOPI <= iterAngle):
-                               return index - 1 if index != 0 else numChildren - 1
-                       oldIterAngle = iterAngle
-
-
-class PieArtist(object):
-
-       ICON_SIZE_DEFAULT = 48
-
-       SHAPE_CIRCLE = "circle"
-       SHAPE_SQUARE = "square"
-       DEFAULT_SHAPE = SHAPE_SQUARE
-
-       BACKGROUND_FILL = "fill"
-       BACKGROUND_NOFILL = "no fill"
-
-       def __init__(self, filing, background = BACKGROUND_FILL):
-               self._filing = filing
-
-               self._cachedOuterRadius = self._filing.outerRadius()
-               self._cachedInnerRadius = self._filing.innerRadius()
-               canvasSize = self._cachedOuterRadius * 2 + 1
-               self._canvas = QtGui.QPixmap(canvasSize, canvasSize)
-               self._mask = None
-               self._backgroundState = background
-               self.palette = None
-
-       def pieSize(self):
-               diameter = self._filing.outerRadius() * 2 + 1
-               return QtCore.QSize(diameter, diameter)
-
-       def centerSize(self):
-               painter = QtGui.QPainter(self._canvas)
-               text = self._filing.center().action().text()
-               fontMetrics = painter.fontMetrics()
-               if text:
-                       textBoundingRect = fontMetrics.boundingRect(text)
-               else:
-                       textBoundingRect = QtCore.QRect()
-               textWidth = textBoundingRect.width()
-               textHeight = textBoundingRect.height()
-
-               return QtCore.QSize(
-                       textWidth + self.ICON_SIZE_DEFAULT,
-                       max(textHeight, self.ICON_SIZE_DEFAULT),
-               )
-
-       def show(self, palette):
-               self.palette = palette
-
-               if (
-                       self._cachedOuterRadius != self._filing.outerRadius() or
-                       self._cachedInnerRadius != self._filing.innerRadius()
-               ):
-                       self._cachedOuterRadius = self._filing.outerRadius()
-                       self._cachedInnerRadius = self._filing.innerRadius()
-                       self._canvas = self._canvas.scaled(self.pieSize())
-
-               if self._mask is None:
-                       self._mask = QtGui.QBitmap(self._canvas.size())
-                       self._mask.fill(QtCore.Qt.color0)
-                       self._generate_mask(self._mask)
-                       self._canvas.setMask(self._mask)
-               return self._mask
-
-       def hide(self):
-               self.palette = None
-
-       def paint(self, selectionIndex):
-               painter = QtGui.QPainter(self._canvas)
-               painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
-
-               self.paintPainter(selectionIndex, painter)
-
-               return self._canvas
-
-       def paintPainter(self, selectionIndex, painter):
-               adjustmentRect = painter.viewport().adjusted(0, 0, -1, -1)
-
-               numChildren = len(self._filing)
-               if numChildren == 0:
-                       self._paint_center_background(painter, adjustmentRect, selectionIndex)
-                       self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
-                       return self._canvas
-               else:
-                       for i in xrange(len(self._filing)):
-                               self._paint_slice_background(painter, adjustmentRect, i, selectionIndex)
-
-               self._paint_center_background(painter, adjustmentRect, selectionIndex)
-               self._paint_center_foreground(painter, adjustmentRect, selectionIndex)
-
-               for i in xrange(len(self._filing)):
-                       self._paint_slice_foreground(painter, adjustmentRect, i, selectionIndex)
-
-       def _generate_mask(self, mask):
-               """
-               Specifies on the mask the shape of the pie menu
-               """
-               painter = QtGui.QPainter(mask)
-               painter.setPen(QtCore.Qt.color1)
-               painter.setBrush(QtCore.Qt.color1)
-               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
-                       painter.drawRect(mask.rect())
-               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
-                       painter.drawEllipse(mask.rect().adjusted(0, 0, -1, -1))
-               else:
-                       raise NotImplementedError(self.DEFAULT_SHAPE)
-
-       def _paint_slice_background(self, painter, adjustmentRect, i, selectionIndex):
-               if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
-                       currentWidth = adjustmentRect.width()
-                       newWidth = math.sqrt(2) * currentWidth
-                       dx = (newWidth - currentWidth) / 2
-                       adjustmentRect = adjustmentRect.adjusted(-dx, -dx, dx, dx)
-               elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
-                       pass
-               else:
-                       raise NotImplementedError(self.DEFAULT_SHAPE)
-
-               if self._backgroundState == self.BACKGROUND_NOFILL:
-                       painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
-                       painter.setPen(self.palette.highlight().color())
-               else:
-                       if i == selectionIndex and self._filing[i].isEnabled():
-                               painter.setBrush(self.palette.highlight())
-                               painter.setPen(self.palette.highlight().color())
-                       else:
-                               painter.setBrush(self.palette.window())
-                               painter.setPen(self.palette.window().color())
-
-               a = self._filing._index_to_angle(i, True)
-               b = self._filing._index_to_angle(i + 1, True)
-               if b < a:
-                       b += _TWOPI
-               size = b - a
-               if size < 0:
-                       size += _TWOPI
-
-               startAngleInDeg = (a * 360 * 16) / _TWOPI
-               sizeInDeg = (size * 360 * 16) / _TWOPI
-               painter.drawPie(adjustmentRect, int(startAngleInDeg), int(sizeInDeg))
-
-       def _paint_slice_foreground(self, painter, adjustmentRect, i, selectionIndex):
-               child = self._filing[i]
-
-               a = self._filing._index_to_angle(i, True)
-               b = self._filing._index_to_angle(i + 1, True)
-               if b < a:
-                       b += _TWOPI
-               middleAngle = (a + b) / 2
-               averageRadius = (self._cachedInnerRadius + self._cachedOuterRadius) / 2
-
-               sliceX = averageRadius * math.cos(middleAngle)
-               sliceY = - averageRadius * math.sin(middleAngle)
-
-               piePos = adjustmentRect.center()
-               pieX = piePos.x()
-               pieY = piePos.y()
-               self._paint_label(
-                       painter, child.action(), i == selectionIndex, pieX+sliceX, pieY+sliceY
-               )
-
-       def _paint_label(self, painter, action, isSelected, x, y):
-               text = action.text()
-               fontMetrics = painter.fontMetrics()
-               if text:
-                       textBoundingRect = fontMetrics.boundingRect(text)
-               else:
-                       textBoundingRect = QtCore.QRect()
-               textWidth = textBoundingRect.width()
-               textHeight = textBoundingRect.height()
-
-               icon = action.icon().pixmap(
-                       QtCore.QSize(self.ICON_SIZE_DEFAULT, self.ICON_SIZE_DEFAULT),
-                       QtGui.QIcon.Normal,
-                       QtGui.QIcon.On,
-               )
-               iconWidth = icon.width()
-               iconHeight = icon.width()
-               averageWidth = (iconWidth + textWidth)/2
-               if not icon.isNull():
-                       iconRect = QtCore.QRect(
-                               x - averageWidth,
-                               y - iconHeight/2,
-                               iconWidth,
-                               iconHeight,
-                       )
-
-                       painter.drawPixmap(iconRect, icon)
-
-               if text:
-                       if isSelected:
-                               if action.isEnabled():
-                                       pen = self.palette.highlightedText()
-                                       brush = self.palette.highlight()
-                               else:
-                                       pen = self.palette.mid()
-                                       brush = self.palette.window()
-                       else:
-                               if action.isEnabled():
-                                       pen = self.palette.windowText()
-                               else:
-                                       pen = self.palette.mid()
-                               brush = self.palette.window()
-
-                       leftX = x - averageWidth + iconWidth
-                       topY = y + textHeight/2
-                       painter.setPen(pen.color())
-                       painter.setBrush(brush)
-                       painter.drawText(leftX, topY, text)
-
-       def _paint_center_background(self, painter, adjustmentRect, selectionIndex):
-               if self._backgroundState == self.BACKGROUND_NOFILL:
-                       return
-               if len(self._filing) == 0:
-                       if self._backgroundState == self.BACKGROUND_NOFILL:
-                               painter.setBrush(QtGui.QBrush(QtCore.Qt.transparent))
-                       else:
-                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
-                                       painter.setBrush(self.palette.highlight())
-                               else:
-                                       painter.setBrush(self.palette.window())
-                       painter.setPen(self.palette.mid().color())
-
-                       painter.drawRect(adjustmentRect)
-               else:
-                       dark = self.palette.mid().color()
-                       light = self.palette.light().color()
-                       if self._backgroundState == self.BACKGROUND_NOFILL:
-                               background = QtGui.QBrush(QtCore.Qt.transparent)
-                       else:
-                               if selectionIndex == PieFiling.SELECTION_CENTER and self._filing.center().isEnabled():
-                                       background = self.palette.highlight().color()
-                               else:
-                                       background = self.palette.window().color()
-
-                       innerRadius = self._cachedInnerRadius
-                       adjustmentCenterPos = adjustmentRect.center()
-                       innerRect = QtCore.QRect(
-                               adjustmentCenterPos.x() - innerRadius,
-                               adjustmentCenterPos.y() - innerRadius,
-                               innerRadius * 2 + 1,
-                               innerRadius * 2 + 1,
-                       )
-
-                       painter.setPen(QtCore.Qt.NoPen)
-                       painter.setBrush(background)
-                       painter.drawPie(innerRect, 0, 360 * 16)
-
-                       if self.DEFAULT_SHAPE == self.SHAPE_SQUARE:
-                               pass
-                       elif self.DEFAULT_SHAPE == self.SHAPE_CIRCLE:
-                               painter.setPen(QtGui.QPen(dark, 1))
-                               painter.setBrush(QtCore.Qt.NoBrush)
-                               painter.drawEllipse(adjustmentRect)
-                       else:
-                               raise NotImplementedError(self.DEFAULT_SHAPE)
-
-       def _paint_center_foreground(self, painter, adjustmentRect, selectionIndex):
-               centerPos = adjustmentRect.center()
-               pieX = centerPos.x()
-               pieY = centerPos.y()
-
-               x = pieX
-               y = pieY
-
-               self._paint_label(
-                       painter,
-                       self._filing.center().action(),
-                       selectionIndex == PieFiling.SELECTION_CENTER,
-                       x, y
-               )
-
-
-class QPieDisplay(QtGui.QWidget):
-
-       def __init__(self, filing, parent = None, flags = QtCore.Qt.Window):
-               QtGui.QWidget.__init__(self, parent, flags)
-               self._filing = filing
-               self._artist = PieArtist(self._filing)
-               self._selectionIndex = PieFiling.SELECTION_NONE
-
-       def popup(self, pos):
-               self._update_selection(pos)
-               self.show()
-
-       def sizeHint(self):
-               return self._artist.pieSize()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def showEvent(self, showEvent):
-               mask = self._artist.show(self.palette())
-               self.setMask(mask)
-
-               QtGui.QWidget.showEvent(self, showEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def hideEvent(self, hideEvent):
-               self._artist.hide()
-               self._selectionIndex = PieFiling.SELECTION_NONE
-               QtGui.QWidget.hideEvent(self, hideEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def paintEvent(self, paintEvent):
-               canvas = self._artist.paint(self._selectionIndex)
-               offset = (self.size() - canvas.size()) / 2
-
-               screen = QtGui.QPainter(self)
-               screen.drawPixmap(QtCore.QPoint(offset.width(), offset.height()), canvas)
-
-               QtGui.QWidget.paintEvent(self, paintEvent)
-
-       def selectAt(self, index):
-               oldIndex = self._selectionIndex
-               self._selectionIndex = index
-               if self.isVisible():
-                       self.update()
-
-
-class QPieButton(QtGui.QWidget):
-
-       activated = qt_compat.Signal(int)
-       highlighted = qt_compat.Signal(int)
-       canceled = qt_compat.Signal()
-       aboutToShow = qt_compat.Signal()
-       aboutToHide = qt_compat.Signal()
-
-       BUTTON_RADIUS = 24
-       DELAY = 250
-
-       def __init__(self, buttonSlice, parent = None, buttonSlices = None):
-               # @bug Artifacts on Maemo 5 due to window 3D effects, find way to disable them for just these?
-               # @bug The pie's are being pushed back on screen on Maemo, leading to coordinate issues
-               QtGui.QWidget.__init__(self, parent)
-               self._cachedCenterPosition = self.rect().center()
-
-               self._filing = PieFiling()
-               self._display = QPieDisplay(self._filing, None, QtCore.Qt.SplashScreen)
-               self._selectionIndex = PieFiling.SELECTION_NONE
-
-               self._buttonFiling = PieFiling()
-               self._buttonFiling.set_center(buttonSlice)
-               if buttonSlices is not None:
-                       for slice in buttonSlices:
-                               self._buttonFiling.insertItem(slice)
-               self._buttonFiling.setOuterRadius(self.BUTTON_RADIUS)
-               self._buttonArtist = PieArtist(self._buttonFiling, PieArtist.BACKGROUND_NOFILL)
-               self._poppedUp = False
-               self._pressed = False
-
-               self._delayPopupTimer = QtCore.QTimer()
-               self._delayPopupTimer.setInterval(self.DELAY)
-               self._delayPopupTimer.setSingleShot(True)
-               self._delayPopupTimer.timeout.connect(self._on_delayed_popup)
-               self._popupLocation = None
-
-               self._mousePosition = None
-               self.setFocusPolicy(QtCore.Qt.StrongFocus)
-               self.setSizePolicy(
-                       QtGui.QSizePolicy(
-                               QtGui.QSizePolicy.MinimumExpanding,
-                               QtGui.QSizePolicy.MinimumExpanding,
-                       )
-               )
-
-       def insertItem(self, item, index = -1):
-               self._filing.insertItem(item, index)
-
-       def removeItemAt(self, index):
-               self._filing.removeItemAt(index)
-
-       def set_center(self, item):
-               self._filing.set_center(item)
-
-       def set_button(self, item):
-               self.update()
-
-       def clear(self):
-               self._filing.clear()
-
-       def itemAt(self, index):
-               return self._filing.itemAt(index)
-
-       def indexAt(self, point):
-               return self._filing.indexAt(self._cachedCenterPosition, point)
-
-       def innerRadius(self):
-               return self._filing.innerRadius()
-
-       def setInnerRadius(self, radius):
-               self._filing.setInnerRadius(radius)
-
-       def outerRadius(self):
-               return self._filing.outerRadius()
-
-       def setOuterRadius(self, radius):
-               self._filing.setOuterRadius(radius)
-
-       def buttonRadius(self):
-               return self._buttonFiling.outerRadius()
-
-       def setButtonRadius(self, radius):
-               self._buttonFiling.setOuterRadius(radius)
-               self._buttonFiling.setInnerRadius(radius / 2)
-               self._buttonArtist.show(self.palette())
-
-       def minimumSizeHint(self):
-               return self._buttonArtist.centerSize()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mousePressEvent(self, mouseEvent):
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               self._mousePosition = lastMousePos
-               self._update_selection(self._cachedCenterPosition)
-
-               self.highlighted.emit(self._selectionIndex)
-
-               self._display.selectAt(self._selectionIndex)
-               self._pressed = True
-               self.update()
-               self._popupLocation = mouseEvent.globalPos()
-               self._delayPopupTimer.start()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_delayed_popup(self):
-               assert self._popupLocation is not None, "Widget location abuse"
-               self._popup_child(self._popupLocation)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mouseMoveEvent(self, mouseEvent):
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               if self._mousePosition is None:
-                       # Absolute
-                       self._update_selection(lastMousePos)
-               else:
-                       # Relative
-                       self._update_selection(
-                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
-                               ignoreOuter = True,
-                       )
-
-               if lastSelection != self._selectionIndex:
-                       self.highlighted.emit(self._selectionIndex)
-                       self._display.selectAt(self._selectionIndex)
-
-               if self._selectionIndex != PieFiling.SELECTION_CENTER and self._delayPopupTimer.isActive():
-                       self._on_delayed_popup()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mouseReleaseEvent(self, mouseEvent):
-               self._delayPopupTimer.stop()
-               self._popupLocation = None
-
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               if self._mousePosition is None:
-                       # Absolute
-                       self._update_selection(lastMousePos)
-               else:
-                       # Relative
-                       self._update_selection(
-                               self._cachedCenterPosition + (lastMousePos - self._mousePosition),
-                               ignoreOuter = True,
-                       )
-               self._mousePosition = None
-
-               self._activate_at(self._selectionIndex)
-               self._pressed = False
-               self.update()
-               self._hide_child()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def keyPressEvent(self, keyEvent):
-               if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
-                       self._popup_child(QtGui.QCursor.pos())
-                       if self._selectionIndex != len(self._filing) - 1:
-                               nextSelection = self._selectionIndex + 1
-                       else:
-                               nextSelection = 0
-                       self._select_at(nextSelection)
-                       self._display.selectAt(self._selectionIndex)
-               elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
-                       self._popup_child(QtGui.QCursor.pos())
-                       if 0 < self._selectionIndex:
-                               nextSelection = self._selectionIndex - 1
-                       else:
-                               nextSelection = len(self._filing) - 1
-                       self._select_at(nextSelection)
-                       self._display.selectAt(self._selectionIndex)
-               elif keyEvent.key() in [QtCore.Qt.Key_Space]:
-                       self._popup_child(QtGui.QCursor.pos())
-                       self._select_at(PieFiling.SELECTION_CENTER)
-                       self._display.selectAt(self._selectionIndex)
-               elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
-                       self._delayPopupTimer.stop()
-                       self._popupLocation = None
-                       self._activate_at(self._selectionIndex)
-                       self._hide_child()
-               elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
-                       self._delayPopupTimer.stop()
-                       self._popupLocation = None
-                       self._activate_at(PieFiling.SELECTION_NONE)
-                       self._hide_child()
-               else:
-                       QtGui.QWidget.keyPressEvent(self, keyEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def resizeEvent(self, resizeEvent):
-               self.setButtonRadius(min(resizeEvent.size().width(), resizeEvent.size().height()) / 2 - 1)
-               QtGui.QWidget.resizeEvent(self, resizeEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def showEvent(self, showEvent):
-               self._buttonArtist.show(self.palette())
-               self._cachedCenterPosition = self.rect().center()
-
-               QtGui.QWidget.showEvent(self, showEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def hideEvent(self, hideEvent):
-               self._display.hide()
-               self._select_at(PieFiling.SELECTION_NONE)
-               QtGui.QWidget.hideEvent(self, hideEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def paintEvent(self, paintEvent):
-               self.setButtonRadius(min(self.rect().width(), self.rect().height()) / 2 - 1)
-               if self._poppedUp:
-                       selectionIndex = PieFiling.SELECTION_CENTER
-               else:
-                       selectionIndex = PieFiling.SELECTION_NONE
-
-               screen = QtGui.QStylePainter(self)
-               screen.setRenderHint(QtGui.QPainter.Antialiasing, True)
-               option = QtGui.QStyleOptionButton()
-               option.initFrom(self)
-               option.state = QtGui.QStyle.State_Sunken if self._pressed else QtGui.QStyle.State_Raised
-
-               screen.drawControl(QtGui.QStyle.CE_PushButton, option)
-               self._buttonArtist.paintPainter(selectionIndex, screen)
-
-               QtGui.QWidget.paintEvent(self, paintEvent)
-
-       def __iter__(self):
-               return iter(self._filing)
-
-       def __len__(self):
-               return len(self._filing)
-
-       def _popup_child(self, position):
-               self._poppedUp = True
-               self.aboutToShow.emit()
-
-               self._delayPopupTimer.stop()
-               self._popupLocation = None
-
-               position = position - QtCore.QPoint(self._filing.outerRadius(), self._filing.outerRadius())
-               self._display.move(position)
-               self._display.show()
-
-               self.update()
-
-       def _hide_child(self):
-               self._poppedUp = False
-               self.aboutToHide.emit()
-               self._display.hide()
-               self.update()
-
-       def _select_at(self, index):
-               self._selectionIndex = index
-
-       def _update_selection(self, lastMousePos, ignoreOuter = False):
-               radius = _radius_at(self._cachedCenterPosition, lastMousePos)
-               if radius < self._filing.innerRadius():
-                       self._select_at(PieFiling.SELECTION_CENTER)
-               elif radius <= self._filing.outerRadius() or ignoreOuter:
-                       self._select_at(self.indexAt(lastMousePos))
-               else:
-                       self._select_at(PieFiling.SELECTION_NONE)
-
-       def _activate_at(self, index):
-               if index == PieFiling.SELECTION_NONE:
-                       self.canceled.emit()
-                       return
-               elif index == PieFiling.SELECTION_CENTER:
-                       child = self._filing.center()
-               else:
-                       child = self.itemAt(index)
-
-               if child.action().isEnabled():
-                       child.action().trigger()
-                       self.activated.emit(index)
-               else:
-                       self.canceled.emit()
-
-
-class QPieMenu(QtGui.QWidget):
-
-       activated = qt_compat.Signal(int)
-       highlighted = qt_compat.Signal(int)
-       canceled = qt_compat.Signal()
-       aboutToShow = qt_compat.Signal()
-       aboutToHide = qt_compat.Signal()
-
-       def __init__(self, parent = None):
-               QtGui.QWidget.__init__(self, parent)
-               self._cachedCenterPosition = self.rect().center()
-
-               self._filing = PieFiling()
-               self._artist = PieArtist(self._filing)
-               self._selectionIndex = PieFiling.SELECTION_NONE
-
-               self._mousePosition = ()
-               self.setFocusPolicy(QtCore.Qt.StrongFocus)
-
-       def popup(self, pos):
-               self._update_selection(pos)
-               self.show()
-
-       def insertItem(self, item, index = -1):
-               self._filing.insertItem(item, index)
-               self.update()
-
-       def removeItemAt(self, index):
-               self._filing.removeItemAt(index)
-               self.update()
-
-       def set_center(self, item):
-               self._filing.set_center(item)
-               self.update()
-
-       def clear(self):
-               self._filing.clear()
-               self.update()
-
-       def itemAt(self, index):
-               return self._filing.itemAt(index)
-
-       def indexAt(self, point):
-               return self._filing.indexAt(self._cachedCenterPosition, point)
-
-       def innerRadius(self):
-               return self._filing.innerRadius()
-
-       def setInnerRadius(self, radius):
-               self._filing.setInnerRadius(radius)
-               self.update()
-
-       def outerRadius(self):
-               return self._filing.outerRadius()
-
-       def setOuterRadius(self, radius):
-               self._filing.setOuterRadius(radius)
-               self.update()
-
-       def sizeHint(self):
-               return self._artist.pieSize()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mousePressEvent(self, mouseEvent):
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               self._update_selection(lastMousePos)
-               self._mousePosition = lastMousePos
-
-               if lastSelection != self._selectionIndex:
-                       self.highlighted.emit(self._selectionIndex)
-                       self.update()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mouseMoveEvent(self, mouseEvent):
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               self._update_selection(lastMousePos)
-
-               if lastSelection != self._selectionIndex:
-                       self.highlighted.emit(self._selectionIndex)
-                       self.update()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def mouseReleaseEvent(self, mouseEvent):
-               lastSelection = self._selectionIndex
-
-               lastMousePos = mouseEvent.pos()
-               self._update_selection(lastMousePos)
-               self._mousePosition = ()
-
-               self._activate_at(self._selectionIndex)
-               self.update()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def keyPressEvent(self, keyEvent):
-               if keyEvent.key() in [QtCore.Qt.Key_Right, QtCore.Qt.Key_Down, QtCore.Qt.Key_Tab]:
-                       if self._selectionIndex != len(self._filing) - 1:
-                               nextSelection = self._selectionIndex + 1
-                       else:
-                               nextSelection = 0
-                       self._select_at(nextSelection)
-                       self.update()
-               elif keyEvent.key() in [QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Backtab]:
-                       if 0 < self._selectionIndex:
-                               nextSelection = self._selectionIndex - 1
-                       else:
-                               nextSelection = len(self._filing) - 1
-                       self._select_at(nextSelection)
-                       self.update()
-               elif keyEvent.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter, QtCore.Qt.Key_Space]:
-                       self._activate_at(self._selectionIndex)
-               elif keyEvent.key() in [QtCore.Qt.Key_Escape, QtCore.Qt.Key_Backspace]:
-                       self._activate_at(PieFiling.SELECTION_NONE)
-               else:
-                       QtGui.QWidget.keyPressEvent(self, keyEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def showEvent(self, showEvent):
-               self.aboutToShow.emit()
-               self._cachedCenterPosition = self.rect().center()
-
-               mask = self._artist.show(self.palette())
-               self.setMask(mask)
-
-               lastMousePos = self.mapFromGlobal(QtGui.QCursor.pos())
-               self._update_selection(lastMousePos)
-
-               QtGui.QWidget.showEvent(self, showEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def hideEvent(self, hideEvent):
-               self._artist.hide()
-               self._selectionIndex = PieFiling.SELECTION_NONE
-               QtGui.QWidget.hideEvent(self, hideEvent)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def paintEvent(self, paintEvent):
-               canvas = self._artist.paint(self._selectionIndex)
-
-               screen = QtGui.QPainter(self)
-               screen.drawPixmap(QtCore.QPoint(0, 0), canvas)
-
-               QtGui.QWidget.paintEvent(self, paintEvent)
-
-       def __iter__(self):
-               return iter(self._filing)
-
-       def __len__(self):
-               return len(self._filing)
-
-       def _select_at(self, index):
-               self._selectionIndex = index
-
-       def _update_selection(self, lastMousePos):
-               radius = _radius_at(self._cachedCenterPosition, lastMousePos)
-               if radius < self._filing.innerRadius():
-                       self._selectionIndex = PieFiling.SELECTION_CENTER
-               elif radius <= self._filing.outerRadius():
-                       self._select_at(self.indexAt(lastMousePos))
-               else:
-                       self._selectionIndex = PieFiling.SELECTION_NONE
-
-       def _activate_at(self, index):
-               if index == PieFiling.SELECTION_NONE:
-                       self.canceled.emit()
-                       self.aboutToHide.emit()
-                       self.hide()
-                       return
-               elif index == PieFiling.SELECTION_CENTER:
-                       child = self._filing.center()
-               else:
-                       child = self.itemAt(index)
-
-               if child.isEnabled():
-                       child.action().trigger()
-                       self.activated.emit(index)
-               else:
-                       self.canceled.emit()
-               self.aboutToHide.emit()
-               self.hide()
-
-
-def init_pies():
-       PieFiling.NULL_CENTER.setEnabled(False)
-
-
-def _print(msg):
-       print msg
-
-
-def _on_about_to_hide(app):
-       app.exit()
-
-
-if __name__ == "__main__":
-       app = QtGui.QApplication([])
-       init_pies()
-
-       if False:
-               pie = QPieMenu()
-               pie.show()
-
-       if False:
-               singleAction = QtGui.QAction(None)
-               singleAction.setText("Boo")
-               singleItem = QActionPieItem(singleAction)
-               spie = QPieMenu()
-               spie.insertItem(singleItem)
-               spie.show()
-
-       if False:
-               oneAction = QtGui.QAction(None)
-               oneAction.setText("Chew")
-               oneItem = QActionPieItem(oneAction)
-               twoAction = QtGui.QAction(None)
-               twoAction.setText("Foo")
-               twoItem = QActionPieItem(twoAction)
-               iconTextAction = QtGui.QAction(None)
-               iconTextAction.setText("Icon")
-               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
-               iconTextItem = QActionPieItem(iconTextAction)
-               mpie = QPieMenu()
-               mpie.insertItem(oneItem)
-               mpie.insertItem(twoItem)
-               mpie.insertItem(oneItem)
-               mpie.insertItem(iconTextItem)
-               mpie.show()
-
-       if True:
-               oneAction = QtGui.QAction(None)
-               oneAction.setText("Chew")
-               oneAction.triggered.connect(lambda: _print("Chew"))
-               oneItem = QActionPieItem(oneAction)
-               twoAction = QtGui.QAction(None)
-               twoAction.setText("Foo")
-               twoAction.triggered.connect(lambda: _print("Foo"))
-               twoItem = QActionPieItem(twoAction)
-               iconAction = QtGui.QAction(None)
-               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
-               iconAction.triggered.connect(lambda: _print("Icon"))
-               iconItem = QActionPieItem(iconAction)
-               iconTextAction = QtGui.QAction(None)
-               iconTextAction.setText("Icon")
-               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
-               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
-               iconTextItem = QActionPieItem(iconTextAction)
-               mpie = QPieMenu()
-               mpie.set_center(iconItem)
-               mpie.insertItem(oneItem)
-               mpie.insertItem(twoItem)
-               mpie.insertItem(oneItem)
-               mpie.insertItem(iconTextItem)
-               mpie.show()
-               mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
-               mpie.canceled.connect(lambda: _print("Canceled"))
-
-       if False:
-               oneAction = QtGui.QAction(None)
-               oneAction.setText("Chew")
-               oneAction.triggered.connect(lambda: _print("Chew"))
-               oneItem = QActionPieItem(oneAction)
-               twoAction = QtGui.QAction(None)
-               twoAction.setText("Foo")
-               twoAction.triggered.connect(lambda: _print("Foo"))
-               twoItem = QActionPieItem(twoAction)
-               iconAction = QtGui.QAction(None)
-               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
-               iconAction.triggered.connect(lambda: _print("Icon"))
-               iconItem = QActionPieItem(iconAction)
-               iconTextAction = QtGui.QAction(None)
-               iconTextAction.setText("Icon")
-               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
-               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
-               iconTextItem = QActionPieItem(iconTextAction)
-               pieFiling = PieFiling()
-               pieFiling.set_center(iconItem)
-               pieFiling.insertItem(oneItem)
-               pieFiling.insertItem(twoItem)
-               pieFiling.insertItem(oneItem)
-               pieFiling.insertItem(iconTextItem)
-               mpie = QPieDisplay(pieFiling)
-               mpie.show()
-
-       if False:
-               oneAction = QtGui.QAction(None)
-               oneAction.setText("Chew")
-               oneAction.triggered.connect(lambda: _print("Chew"))
-               oneItem = QActionPieItem(oneAction)
-               twoAction = QtGui.QAction(None)
-               twoAction.setText("Foo")
-               twoAction.triggered.connect(lambda: _print("Foo"))
-               twoItem = QActionPieItem(twoAction)
-               iconAction = QtGui.QAction(None)
-               iconAction.setIcon(QtGui.QIcon.fromTheme("gtk-open"))
-               iconAction.triggered.connect(lambda: _print("Icon"))
-               iconItem = QActionPieItem(iconAction)
-               iconTextAction = QtGui.QAction(None)
-               iconTextAction.setText("Icon")
-               iconTextAction.setIcon(QtGui.QIcon.fromTheme("gtk-close"))
-               iconTextAction.triggered.connect(lambda: _print("Icon and text"))
-               iconTextItem = QActionPieItem(iconTextAction)
-               mpie = QPieButton(iconItem)
-               mpie.set_center(iconItem)
-               mpie.insertItem(oneItem)
-               mpie.insertItem(twoItem)
-               mpie.insertItem(oneItem)
-               mpie.insertItem(iconTextItem)
-               mpie.show()
-               mpie.aboutToHide.connect(lambda: _on_about_to_hide(app))
-               mpie.canceled.connect(lambda: _print("Canceled"))
-
-       app.exec_()
diff --git a/src/util/qtpieboard.py b/src/util/qtpieboard.py
deleted file mode 100755 (executable)
index 50ae9ae..0000000
+++ /dev/null
@@ -1,207 +0,0 @@
-#!/usr/bin/env python
-
-
-from __future__ import division
-
-import os
-import warnings
-
-import qt_compat
-QtGui = qt_compat.import_module("QtGui")
-
-import qtpie
-
-
-class PieKeyboard(object):
-
-       SLICE_CENTER = -1
-       SLICE_NORTH = 0
-       SLICE_NORTH_WEST = 1
-       SLICE_WEST = 2
-       SLICE_SOUTH_WEST = 3
-       SLICE_SOUTH = 4
-       SLICE_SOUTH_EAST = 5
-       SLICE_EAST = 6
-       SLICE_NORTH_EAST = 7
-
-       MAX_ANGULAR_SLICES = 8
-
-       SLICE_DIRECTIONS = [
-               SLICE_CENTER,
-               SLICE_NORTH,
-               SLICE_NORTH_WEST,
-               SLICE_WEST,
-               SLICE_SOUTH_WEST,
-               SLICE_SOUTH,
-               SLICE_SOUTH_EAST,
-               SLICE_EAST,
-               SLICE_NORTH_EAST,
-       ]
-
-       SLICE_DIRECTION_NAMES = [
-               "CENTER",
-               "NORTH",
-               "NORTH_WEST",
-               "WEST",
-               "SOUTH_WEST",
-               "SOUTH",
-               "SOUTH_EAST",
-               "EAST",
-               "NORTH_EAST",
-       ]
-
-       def __init__(self):
-               self._layout = QtGui.QGridLayout()
-               self._widget = QtGui.QWidget()
-               self._widget.setLayout(self._layout)
-
-               self.__cells = {}
-
-       @property
-       def toplevel(self):
-               return self._widget
-
-       def add_pie(self, row, column, pieButton):
-               assert len(pieButton) == 8
-               self._layout.addWidget(pieButton, row, column)
-               self.__cells[(row, column)] = pieButton
-
-       def get_pie(self, row, column):
-               return self.__cells[(row, column)]
-
-
-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
-
-
-def parse_keyboard_data(text):
-       return eval(text)
-
-
-def _enumerate_pie_slices(pieData, iconPaths):
-       for direction, directionName in zip(
-               PieKeyboard.SLICE_DIRECTIONS, PieKeyboard.SLICE_DIRECTION_NAMES
-       ):
-               if directionName in pieData:
-                       sliceData = pieData[directionName]
-
-                       action = QtGui.QAction(None)
-                       try:
-                               action.setText(sliceData["text"])
-                       except KeyError:
-                               pass
-                       try:
-                               relativeIconPath = sliceData["path"]
-                       except KeyError:
-                               pass
-                       else:
-                               for iconPath in iconPaths:
-                                       absIconPath = os.path.join(iconPath, relativeIconPath)
-                                       if os.path.exists(absIconPath):
-                                               action.setIcon(QtGui.QIcon(absIconPath))
-                                               break
-                       pieItem = qtpie.QActionPieItem(action)
-                       actionToken = sliceData["action"]
-               else:
-                       pieItem = qtpie.PieFiling.NULL_CENTER
-                       actionToken = ""
-               yield direction, pieItem, actionToken
-
-
-def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler, iconPaths):
-       for (row, column), pieData in dataTree.iteritems():
-               pieItems = list(_enumerate_pie_slices(pieData, iconPaths))
-               assert pieItems[0][0] == PieKeyboard.SLICE_CENTER, pieItems[0]
-               _, center, centerAction = pieItems.pop(0)
-
-               pieButton = qtpie.QPieButton(center)
-               pieButton.set_center(center)
-               keyboardHandler.map_slice_action(center, centerAction)
-               for direction, pieItem, action in pieItems:
-                       pieButton.insertItem(pieItem)
-                       keyboardHandler.map_slice_action(pieItem, action)
-               keyboard.add_pie(row, column, pieButton)
-
-
-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 Look into hooking these up directly to the pie actions
-               self.__commandHandlers["[%s]" % command] = handler
-
-       def unregister_command_handler(self, command):
-               # @todo Look into hooking these up directly to the pie actions
-               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):
-               callback = lambda direction: self(direction, action)
-               slice.action().triggered.connect(callback)
-               self.__sliceActions[slice] = (action, callback)
-
-       def __call__(self, direction, action):
-               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/util/qui_utils.py b/src/util/qui_utils.py
deleted file mode 100644 (file)
index 11b3453..0000000
+++ /dev/null
@@ -1,419 +0,0 @@
-import sys
-import contextlib
-import datetime
-import logging
-
-import qt_compat
-QtCore = qt_compat.QtCore
-QtGui = qt_compat.import_module("QtGui")
-
-import misc
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-@contextlib.contextmanager
-def notify_error(log):
-       try:
-               yield
-       except:
-               log.push_exception()
-
-
-@contextlib.contextmanager
-def notify_busy(log, message):
-       log.push_busy(message)
-       try:
-               yield
-       finally:
-               log.pop(message)
-
-
-class ErrorMessage(object):
-
-       LEVEL_ERROR = 0
-       LEVEL_BUSY = 1
-       LEVEL_INFO = 2
-
-       def __init__(self, message, level):
-               self._message = message
-               self._level = level
-               self._time = datetime.datetime.now()
-
-       @property
-       def level(self):
-               return self._level
-
-       @property
-       def message(self):
-               return self._message
-
-       def __repr__(self):
-               return "%s.%s(%r, %r)" % (__name__, self.__class__.__name__, self._message, self._level)
-
-
-class QErrorLog(QtCore.QObject):
-
-       messagePushed = qt_compat.Signal()
-       messagePopped = qt_compat.Signal()
-
-       def __init__(self):
-               QtCore.QObject.__init__(self)
-               self._messages = []
-
-       def push_busy(self, message):
-               _moduleLogger.info("Entering state: %s" % message)
-               self._push_message(message, ErrorMessage.LEVEL_BUSY)
-
-       def push_message(self, message):
-               self._push_message(message, ErrorMessage.LEVEL_INFO)
-
-       def push_error(self, message):
-               self._push_message(message, ErrorMessage.LEVEL_ERROR)
-
-       def push_exception(self):
-               userMessage = str(sys.exc_info()[1])
-               _moduleLogger.exception(userMessage)
-               self.push_error(userMessage)
-
-       def pop(self, message = None):
-               if message is None:
-                       del self._messages[0]
-               else:
-                       _moduleLogger.info("Exiting state: %s" % message)
-                       messageIndex = [
-                               i
-                               for (i, error) in enumerate(self._messages)
-                               if error.message == message
-                       ]
-                       # Might be removed out of order
-                       if messageIndex:
-                               del self._messages[messageIndex[0]]
-               self.messagePopped.emit()
-
-       def peek_message(self):
-               return self._messages[0]
-
-       def _push_message(self, message, level):
-               self._messages.append(ErrorMessage(message, level))
-               # Sort is defined as stable, so this should be fine
-               self._messages.sort(key=lambda x: x.level)
-               self.messagePushed.emit()
-
-       def __len__(self):
-               return len(self._messages)
-
-
-class ErrorDisplay(object):
-
-       _SENTINEL_ICON = QtGui.QIcon()
-
-       def __init__(self, errorLog):
-               self._errorLog = errorLog
-               self._errorLog.messagePushed.connect(self._on_message_pushed)
-               self._errorLog.messagePopped.connect(self._on_message_popped)
-
-               self._icons = None
-               self._severityLabel = QtGui.QLabel()
-               self._severityLabel.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-
-               self._message = QtGui.QLabel()
-               self._message.setText("Boo")
-               self._message.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
-               self._message.setWordWrap(True)
-
-               self._closeLabel = None
-
-               self._controlLayout = QtGui.QHBoxLayout()
-               self._controlLayout.addWidget(self._severityLabel, 1, QtCore.Qt.AlignCenter)
-               self._controlLayout.addWidget(self._message, 1000)
-
-               self._widget = QtGui.QWidget()
-               self._widget.setLayout(self._controlLayout)
-               self._widget.hide()
-
-       @property
-       def toplevel(self):
-               return self._widget
-
-       def _show_error(self):
-               if self._icons is None:
-                       self._icons = {
-                               ErrorMessage.LEVEL_BUSY:
-                                       get_theme_icon(
-                                               #("process-working", "view-refresh", "general_refresh", "gtk-refresh")
-                                               ("view-refresh", "general_refresh", "gtk-refresh", )
-                                       ).pixmap(32, 32),
-                               ErrorMessage.LEVEL_INFO:
-                                       get_theme_icon(
-                                               ("dialog-information", "general_notes", "gtk-info")
-                                       ).pixmap(32, 32),
-                               ErrorMessage.LEVEL_ERROR:
-                                       get_theme_icon(
-                                               ("dialog-error", "app_install_error", "gtk-dialog-error")
-                                       ).pixmap(32, 32),
-                       }
-               if self._closeLabel is None:
-                       closeIcon = get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON)
-                       if closeIcon is not self._SENTINEL_ICON:
-                               self._closeLabel = QtGui.QPushButton(closeIcon, "")
-                       else:
-                               self._closeLabel = QtGui.QPushButton("X")
-                       self._closeLabel.clicked.connect(self._on_close)
-                       self._controlLayout.addWidget(self._closeLabel, 1, QtCore.Qt.AlignCenter)
-               error = self._errorLog.peek_message()
-               self._message.setText(error.message)
-               self._severityLabel.setPixmap(self._icons[error.level])
-               self._widget.show()
-
-       @qt_compat.Slot()
-       @qt_compat.Slot(bool)
-       @misc.log_exception(_moduleLogger)
-       def _on_close(self, checked = False):
-               self._errorLog.pop()
-
-       @qt_compat.Slot()
-       @misc.log_exception(_moduleLogger)
-       def _on_message_pushed(self):
-               self._show_error()
-
-       @qt_compat.Slot()
-       @misc.log_exception(_moduleLogger)
-       def _on_message_popped(self):
-               if len(self._errorLog) == 0:
-                       self._message.setText("")
-                       self._widget.hide()
-               else:
-                       self._show_error()
-
-
-class QHtmlDelegate(QtGui.QStyledItemDelegate):
-
-       UNDEFINED_SIZE = -1
-
-       def __init__(self, *args, **kwd):
-               QtGui.QStyledItemDelegate.__init__(*((self, ) + args), **kwd)
-               self._width = self.UNDEFINED_SIZE
-
-       def paint(self, painter, option, index):
-               newOption = QtGui.QStyleOptionViewItemV4(option)
-               self.initStyleOption(newOption, index)
-               if newOption.widget is not None:
-                       style = newOption.widget.style()
-               else:
-                       style = QtGui.QApplication.style()
-
-               doc = QtGui.QTextDocument()
-               doc.setHtml(newOption.text)
-               doc.setTextWidth(newOption.rect.width())
-
-               newOption.text = ""
-               style.drawControl(QtGui.QStyle.CE_ItemViewItem, newOption, painter)
-
-               ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
-               if newOption.state & QtGui.QStyle.State_Selected:
-                       ctx.palette.setColor(
-                               QtGui.QPalette.Text,
-                               newOption.palette.color(
-                                       QtGui.QPalette.Active,
-                                       QtGui.QPalette.HighlightedText
-                               )
-                       )
-               else:
-                       ctx.palette.setColor(
-                               QtGui.QPalette.Text,
-                               newOption.palette.color(
-                                       QtGui.QPalette.Active,
-                                       QtGui.QPalette.Text
-                               )
-                       )
-
-               textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, newOption)
-               painter.save()
-               painter.translate(textRect.topLeft())
-               painter.setClipRect(textRect.translated(-textRect.topLeft()))
-               doc.documentLayout().draw(painter, ctx)
-               painter.restore()
-
-       def setWidth(self, width, model):
-               if self._width == width:
-                       return
-               self._width = width
-               for c in xrange(model.rowCount()):
-                       cItem = model.item(c, 0)
-                       for r in xrange(model.rowCount()):
-                               rItem = cItem.child(r, 0)
-                               rIndex = model.indexFromItem(rItem)
-                               self.sizeHintChanged.emit(rIndex)
-                               return
-
-       def sizeHint(self, option, index):
-               newOption = QtGui.QStyleOptionViewItemV4(option)
-               self.initStyleOption(newOption, index)
-
-               doc = QtGui.QTextDocument()
-               doc.setHtml(newOption.text)
-               if self._width != self.UNDEFINED_SIZE:
-                       width = self._width
-               else:
-                       width = newOption.rect.width()
-               doc.setTextWidth(width)
-               size = QtCore.QSize(doc.idealWidth(), doc.size().height())
-               return size
-
-
-class QSignalingMainWindow(QtGui.QMainWindow):
-
-       closed = qt_compat.Signal()
-       hidden = qt_compat.Signal()
-       shown = qt_compat.Signal()
-       resized = qt_compat.Signal()
-
-       def __init__(self, *args, **kwd):
-               QtGui.QMainWindow.__init__(*((self, )+args), **kwd)
-
-       def closeEvent(self, event):
-               val = QtGui.QMainWindow.closeEvent(self, event)
-               self.closed.emit()
-               return val
-
-       def hideEvent(self, event):
-               val = QtGui.QMainWindow.hideEvent(self, event)
-               self.hidden.emit()
-               return val
-
-       def showEvent(self, event):
-               val = QtGui.QMainWindow.showEvent(self, event)
-               self.shown.emit()
-               return val
-
-       def resizeEvent(self, event):
-               val = QtGui.QMainWindow.resizeEvent(self, event)
-               self.resized.emit()
-               return val
-
-def set_current_index(selector, itemText, default = 0):
-       for i in xrange(selector.count()):
-               if selector.itemText(i) == itemText:
-                       selector.setCurrentIndex(i)
-                       break
-       else:
-               itemText.setCurrentIndex(default)
-
-
-def _null_set_stackable(window, isStackable):
-       pass
-
-
-def _maemo_set_stackable(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5StackedWindow, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5StackedWindow
-       set_stackable = _maemo_set_stackable
-except AttributeError:
-       set_stackable = _null_set_stackable
-
-
-def _null_set_autorient(window, doAutoOrient):
-       pass
-
-
-def _maemo_set_autorient(window, doAutoOrient):
-       window.setAttribute(QtCore.Qt.WA_Maemo5AutoOrientation, doAutoOrient)
-
-
-try:
-       QtCore.Qt.WA_Maemo5AutoOrientation
-       set_autorient = _maemo_set_autorient
-except AttributeError:
-       set_autorient = _null_set_autorient
-
-
-def screen_orientation():
-       geom = QtGui.QApplication.desktop().screenGeometry()
-       if geom.width() <= geom.height():
-               return QtCore.Qt.Vertical
-       else:
-               return QtCore.Qt.Horizontal
-
-
-def _null_set_window_orientation(window, orientation):
-       pass
-
-
-def _maemo_set_window_orientation(window, orientation):
-       if orientation == QtCore.Qt.Vertical:
-               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
-               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, True)
-       elif orientation == QtCore.Qt.Horizontal:
-               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, True)
-               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
-       elif orientation is None:
-               window.setAttribute(QtCore.Qt.WA_Maemo5LandscapeOrientation, False)
-               window.setAttribute(QtCore.Qt.WA_Maemo5PortraitOrientation, False)
-       else:
-               raise RuntimeError("Unknown orientation: %r" % orientation)
-
-
-try:
-       QtCore.Qt.WA_Maemo5LandscapeOrientation
-       QtCore.Qt.WA_Maemo5PortraitOrientation
-       set_window_orientation = _maemo_set_window_orientation
-except AttributeError:
-       set_window_orientation = _null_set_window_orientation
-
-
-def _null_show_progress_indicator(window, isStackable):
-       pass
-
-
-def _maemo_show_progress_indicator(window, isStackable):
-       window.setAttribute(QtCore.Qt.WA_Maemo5ShowProgressIndicator, isStackable)
-
-
-try:
-       QtCore.Qt.WA_Maemo5ShowProgressIndicator
-       show_progress_indicator = _maemo_show_progress_indicator
-except AttributeError:
-       show_progress_indicator = _null_show_progress_indicator
-
-
-def _null_mark_numbers_preferred(widget):
-       pass
-
-
-def _newqt_mark_numbers_preferred(widget):
-       widget.setInputMethodHints(QtCore.Qt.ImhPreferNumbers)
-
-
-try:
-       QtCore.Qt.ImhPreferNumbers
-       mark_numbers_preferred = _newqt_mark_numbers_preferred
-except AttributeError:
-       mark_numbers_preferred = _null_mark_numbers_preferred
-
-
-def _null_get_theme_icon(iconNames, fallback = None):
-       icon = fallback if fallback is not None else QtGui.QIcon()
-       return icon
-
-
-def _newqt_get_theme_icon(iconNames, fallback = None):
-       for iconName in iconNames:
-               if QtGui.QIcon.hasThemeIcon(iconName):
-                       icon = QtGui.QIcon.fromTheme(iconName)
-                       break
-       else:
-               icon = fallback if fallback is not None else QtGui.QIcon()
-       return icon
-
-
-try:
-       QtGui.QIcon.fromTheme
-       get_theme_icon = _newqt_get_theme_icon
-except AttributeError:
-       get_theme_icon = _null_get_theme_icon
-
diff --git a/src/util/qwrappers.py b/src/util/qwrappers.py
deleted file mode 100644 (file)
index 2c50c8a..0000000
+++ /dev/null
@@ -1,328 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import with_statement
-from __future__ import division
-
-import logging
-
-import qt_compat
-QtCore = qt_compat.QtCore
-QtGui = qt_compat.import_module("QtGui")
-
-from util import qui_utils
-from util import misc as misc_utils
-
-
-_moduleLogger = logging.getLogger(__name__)
-
-
-class ApplicationWrapper(object):
-
-       DEFAULT_ORIENTATION = "Default"
-       AUTO_ORIENTATION = "Auto"
-       LANDSCAPE_ORIENTATION = "Landscape"
-       PORTRAIT_ORIENTATION = "Portrait"
-
-       def __init__(self, qapp, constants):
-               self._constants = constants
-               self._qapp = qapp
-               self._clipboard = QtGui.QApplication.clipboard()
-
-               self._errorLog = qui_utils.QErrorLog()
-               self._mainWindow = None
-
-               self._fullscreenAction = QtGui.QAction(None)
-               self._fullscreenAction.setText("Fullscreen")
-               self._fullscreenAction.setCheckable(True)
-               self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
-               self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
-
-               self._orientation = self.DEFAULT_ORIENTATION
-               self._orientationAction = QtGui.QAction(None)
-               self._orientationAction.setText("Next Orientation")
-               self._orientationAction.setCheckable(True)
-               self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o"))
-               self._orientationAction.triggered.connect(self._on_next_orientation)
-
-               self._logAction = QtGui.QAction(None)
-               self._logAction.setText("Log")
-               self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
-               self._logAction.triggered.connect(self._on_log)
-
-               self._quitAction = QtGui.QAction(None)
-               self._quitAction.setText("Quit")
-               self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
-               self._quitAction.triggered.connect(self._on_quit)
-
-               self._aboutAction = QtGui.QAction(None)
-               self._aboutAction.setText("About")
-               self._aboutAction.triggered.connect(self._on_about)
-
-               self._qapp.lastWindowClosed.connect(self._on_app_quit)
-               self._mainWindow = self._new_main_window()
-               self._mainWindow.window.destroyed.connect(self._on_child_close)
-
-               self.load_settings()
-
-               self._mainWindow.show()
-               self._idleDelay = QtCore.QTimer()
-               self._idleDelay.setSingleShot(True)
-               self._idleDelay.setInterval(0)
-               self._idleDelay.timeout.connect(self._on_delayed_start)
-               self._idleDelay.start()
-
-       def load_settings(self):
-               raise NotImplementedError("Booh")
-
-       def save_settings(self):
-               raise NotImplementedError("Booh")
-
-       def _new_main_window(self):
-               raise NotImplementedError("Booh")
-
-       @property
-       def qapp(self):
-               return self._qapp
-
-       @property
-       def constants(self):
-               return self._constants
-
-       @property
-       def errorLog(self):
-               return self._errorLog
-
-       @property
-       def fullscreenAction(self):
-               return self._fullscreenAction
-
-       @property
-       def orientationAction(self):
-               return self._orientationAction
-
-       @property
-       def orientation(self):
-               return self._orientation
-
-       @property
-       def logAction(self):
-               return self._logAction
-
-       @property
-       def aboutAction(self):
-               return self._aboutAction
-
-       @property
-       def quitAction(self):
-               return self._quitAction
-
-       def set_orientation(self, orientation):
-               self._orientation = orientation
-               self._mainWindow.update_orientation(self._orientation)
-
-       @classmethod
-       def _next_orientation(cls, current):
-               return {
-                       cls.DEFAULT_ORIENTATION: cls.AUTO_ORIENTATION,
-                       cls.AUTO_ORIENTATION: cls.LANDSCAPE_ORIENTATION,
-                       cls.LANDSCAPE_ORIENTATION: cls.PORTRAIT_ORIENTATION,
-                       cls.PORTRAIT_ORIENTATION: cls.DEFAULT_ORIENTATION,
-               }[current]
-
-       def _close_windows(self):
-               if self._mainWindow is not None:
-                       self.save_settings()
-                       self._mainWindow.window.destroyed.disconnect(self._on_child_close)
-                       self._mainWindow.close()
-                       self._mainWindow = None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_delayed_start(self):
-               self._mainWindow.start()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_app_quit(self, checked = False):
-               if self._mainWindow is not None:
-                       self.save_settings()
-                       self._mainWindow.destroy()
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_child_close(self, obj = None):
-               if self._mainWindow is not None:
-                       self.save_settings()
-                       self._mainWindow = None
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_toggle_fullscreen(self, checked = False):
-               with qui_utils.notify_error(self._errorLog):
-                       self._mainWindow.set_fullscreen(checked)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_next_orientation(self, checked = False):
-               with qui_utils.notify_error(self._errorLog):
-                       self.set_orientation(self._next_orientation(self._orientation))
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_about(self, checked = True):
-               raise NotImplementedError("Booh")
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_log(self, checked = False):
-               with qui_utils.notify_error(self._errorLog):
-                       with open(self._constants._user_logpath_, "r") as f:
-                               logLines = f.xreadlines()
-                               log = "".join(logLines)
-                               self._clipboard.setText(log)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_quit(self, checked = False):
-               with qui_utils.notify_error(self._errorLog):
-                       self._close_windows()
-
-
-class WindowWrapper(object):
-
-       def __init__(self, parent, app):
-               self._app = app
-
-               self._errorDisplay = qui_utils.ErrorDisplay(self._app.errorLog)
-
-               self._layout = QtGui.QBoxLayout(QtGui.QBoxLayout.LeftToRight)
-               self._layout.setContentsMargins(0, 0, 0, 0)
-
-               self._superLayout = QtGui.QVBoxLayout()
-               self._superLayout.addWidget(self._errorDisplay.toplevel)
-               self._superLayout.setContentsMargins(0, 0, 0, 0)
-               self._superLayout.addLayout(self._layout)
-
-               centralWidget = QtGui.QWidget()
-               centralWidget.setLayout(self._superLayout)
-               centralWidget.setContentsMargins(0, 0, 0, 0)
-
-               self._window = qui_utils.QSignalingMainWindow(parent)
-               self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
-               qui_utils.set_stackable(self._window, True)
-               self._window.setCentralWidget(centralWidget)
-
-               self._closeWindowAction = QtGui.QAction(None)
-               self._closeWindowAction.setText("Close")
-               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
-               self._closeWindowAction.triggered.connect(self._on_close_window)
-
-               self._window.addAction(self._closeWindowAction)
-               self._window.addAction(self._app.quitAction)
-               self._window.addAction(self._app.fullscreenAction)
-               self._window.addAction(self._app.orientationAction)
-               self._window.addAction(self._app.logAction)
-
-       @property
-       def window(self):
-               return self._window
-
-       @property
-       def windowOrientation(self):
-               geom = self._window.size()
-               if geom.width() <= geom.height():
-                       return QtCore.Qt.Vertical
-               else:
-                       return QtCore.Qt.Horizontal
-
-       @property
-       def idealWindowOrientation(self):
-               if self._app.orientation ==  self._app.AUTO_ORIENTATION:
-                       windowOrientation = self.windowOrientation
-               elif self._app.orientation ==  self._app.DEFAULT_ORIENTATION:
-                       windowOrientation = qui_utils.screen_orientation()
-               elif self._app.orientation ==  self._app.LANDSCAPE_ORIENTATION:
-                       windowOrientation = QtCore.Qt.Horizontal
-               elif self._app.orientation ==  self._app.PORTRAIT_ORIENTATION:
-                       windowOrientation = QtCore.Qt.Vertical
-               else:
-                       raise RuntimeError("Bad! No %r for you" % self._app.orientation)
-               return windowOrientation
-
-       def walk_children(self):
-               return ()
-
-       def start(self):
-               pass
-
-       def close(self):
-               for child in self.walk_children():
-                       child.window.destroyed.disconnect(self._on_child_close)
-                       child.close()
-               self._window.close()
-
-       def destroy(self):
-               pass
-
-       def show(self):
-               self._window.show()
-               for child in self.walk_children():
-                       child.show()
-               self.set_fullscreen(self._app.fullscreenAction.isChecked())
-
-       def hide(self):
-               for child in self.walk_children():
-                       child.hide()
-               self._window.hide()
-
-       def set_fullscreen(self, isFullscreen):
-               if self._window.isVisible():
-                       if isFullscreen:
-                               self._window.showFullScreen()
-                       else:
-                               self._window.showNormal()
-               for child in self.walk_children():
-                       child.set_fullscreen(isFullscreen)
-
-       def update_orientation(self, orientation):
-               if orientation == self._app.DEFAULT_ORIENTATION:
-                       qui_utils.set_autorient(self.window, False)
-                       qui_utils.set_window_orientation(self.window, None)
-               elif orientation == self._app.AUTO_ORIENTATION:
-                       qui_utils.set_autorient(self.window, True)
-                       qui_utils.set_window_orientation(self.window, None)
-               elif orientation == self._app.LANDSCAPE_ORIENTATION:
-                       qui_utils.set_autorient(self.window, False)
-                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal)
-               elif orientation == self._app.PORTRAIT_ORIENTATION:
-                       qui_utils.set_autorient(self.window, False)
-                       qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical)
-               else:
-                       raise RuntimeError("Unknown orientation: %r" % orientation)
-               for child in self.walk_children():
-                       child.update_orientation(orientation)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_child_close(self, obj = None):
-               raise NotImplementedError("Booh")
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_close_window(self, checked = True):
-               with qui_utils.notify_error(self._errorLog):
-                       self.close()
-
-
-class AutoFreezeWindowFeature(object):
-
-       def __init__(self, app, window):
-               self._app = app
-               self._window = window
-               self._app.qapp.focusChanged.connect(self._on_focus_changed)
-               if self._app.qapp.focusWidget() is not None:
-                       self._window.setUpdatesEnabled(True)
-               else:
-                       self._window.setUpdatesEnabled(False)
-
-       def close(self):
-               self._app.qapp.focusChanged.disconnect(self._on_focus_changed)
-               self._window.setUpdatesEnabled(True)
-
-       @misc_utils.log_exception(_moduleLogger)
-       def _on_focus_changed(self, oldWindow, newWindow):
-               with qui_utils.notify_error(self._app.errorLog):
-                       if oldWindow is None and newWindow is not None:
-                               self._window.setUpdatesEnabled(True)
-                       elif oldWindow is not None and newWindow is None:
-                               self._window.setUpdatesEnabled(False)
diff --git a/src/util/time_utils.py b/src/util/time_utils.py
deleted file mode 100644 (file)
index 90ec84d..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-from datetime import tzinfo, timedelta, datetime
-
-ZERO = timedelta(0)
-HOUR = timedelta(hours=1)
-
-
-def first_sunday_on_or_after(dt):
-       days_to_go = 6 - dt.weekday()
-       if days_to_go:
-               dt += timedelta(days_to_go)
-       return dt
-
-
-# US DST Rules
-#
-# This is a simplified (i.e., wrong for a few cases) set of rules for US
-# DST start and end times. For a complete and up-to-date set of DST rules
-# and timezone definitions, visit the Olson Database (or try pytz):
-# http://www.twinsun.com/tz/tz-link.htm
-# http://sourceforge.net/projects/pytz/ (might not be up-to-date)
-#
-# In the US, since 2007, DST starts at 2am (standard time) on the second
-# Sunday in March, which is the first Sunday on or after Mar 8.
-DSTSTART_2007 = datetime(1, 3, 8, 2)
-# and ends at 2am (DST time; 1am standard time) on the first Sunday of Nov.
-DSTEND_2007 = datetime(1, 11, 1, 1)
-# From 1987 to 2006, DST used to start at 2am (standard time) on the first
-# Sunday in April and to end at 2am (DST time; 1am standard time) on the last
-# Sunday of October, which is the first Sunday on or after Oct 25.
-DSTSTART_1987_2006 = datetime(1, 4, 1, 2)
-DSTEND_1987_2006 = datetime(1, 10, 25, 1)
-# From 1967 to 1986, DST used to start at 2am (standard time) on the last
-# Sunday in April (the one on or after April 24) and to end at 2am (DST time;
-# 1am standard time) on the last Sunday of October, which is the first Sunday
-# on or after Oct 25.
-DSTSTART_1967_1986 = datetime(1, 4, 24, 2)
-DSTEND_1967_1986 = DSTEND_1987_2006
-
-
-class USTimeZone(tzinfo):
-
-       def __init__(self, hours, reprname, stdname, dstname):
-               self.stdoffset = timedelta(hours=hours)
-               self.reprname = reprname
-               self.stdname = stdname
-               self.dstname = dstname
-
-       def __repr__(self):
-               return self.reprname
-
-       def tzname(self, dt):
-               if self.dst(dt):
-                       return self.dstname
-               else:
-                       return self.stdname
-
-       def utcoffset(self, dt):
-               return self.stdoffset + self.dst(dt)
-
-       def dst(self, dt):
-               if dt is None or dt.tzinfo is None:
-                       # An exception may be sensible here, in one or both cases.
-                       # It depends on how you want to treat them.  The default
-                       # fromutc() implementation (called by the default astimezone()
-                       # implementation) passes a datetime with dt.tzinfo is self.
-                       return ZERO
-               assert dt.tzinfo is self
-
-               # Find start and end times for US DST. For years before 1967, return
-               # ZERO for no DST.
-               if 2006 < dt.year:
-                       dststart, dstend = DSTSTART_2007, DSTEND_2007
-               elif 1986 < dt.year < 2007:
-                       dststart, dstend = DSTSTART_1987_2006, DSTEND_1987_2006
-               elif 1966 < dt.year < 1987:
-                       dststart, dstend = DSTSTART_1967_1986, DSTEND_1967_1986
-               else:
-                       return ZERO
-
-               start = first_sunday_on_or_after(dststart.replace(year=dt.year))
-               end = first_sunday_on_or_after(dstend.replace(year=dt.year))
-
-               # Can't compare naive to aware objects, so strip the timezone from
-               # dt first.
-               if start <= dt.replace(tzinfo=None) < end:
-                       return HOUR
-               else:
-                       return ZERO
-
-
-Eastern  = USTimeZone(-5, "Eastern",  "EST", "EDT")
-Central  = USTimeZone(-6, "Central",  "CST", "CDT")
-Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
-Pacific  = USTimeZone(-8, "Pacific",  "PST", "PDT")
diff --git a/src/util/tp_utils.py b/src/util/tp_utils.py
deleted file mode 100644 (file)
index 7c55c42..0000000
+++ /dev/null
@@ -1,220 +0,0 @@
-#!/usr/bin/env python
-
-import logging
-
-import dbus
-import telepathy
-
-import util.go_utils as gobject_utils
-import misc
-
-
-_moduleLogger = logging.getLogger(__name__)
-DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
-
-
-class WasMissedCall(object):
-
-       def __init__(self, bus, conn, chan, on_success, on_error):
-               self.__on_success = on_success
-               self.__on_error = on_error
-
-               self._requested = None
-               self._didMembersChange = False
-               self._didClose = False
-               self._didReport = False
-
-               self._onTimeout = gobject_utils.Timeout(self._on_timeout)
-               self._onTimeout.start(seconds=60)
-
-               chan[telepathy.interfaces.CHANNEL_INTERFACE_GROUP].connect_to_signal(
-                       "MembersChanged",
-                       self._on_members_changed,
-               )
-
-               chan[telepathy.interfaces.CHANNEL].connect_to_signal(
-                       "Closed",
-                       self._on_closed,
-               )
-
-               chan[DBUS_PROPERTIES].GetAll(
-                       telepathy.interfaces.CHANNEL_INTERFACE,
-                       reply_handler = self._on_got_all,
-                       error_handler = self._on_error,
-               )
-
-       def cancel(self):
-               self._report_error("by request")
-
-       def _report_missed_if_ready(self):
-               if self._didReport:
-                       pass
-               elif self._requested is not None and (self._didMembersChange or self._didClose):
-                       if self._requested:
-                               self._report_error("wrong direction")
-                       elif self._didClose:
-                               self._report_success()
-                       else:
-                               self._report_error("members added")
-               else:
-                       if self._didClose:
-                               self._report_error("closed too early")
-
-       def _report_success(self):
-               assert not self._didReport, "Double reporting a missed call"
-               self._didReport = True
-               self._onTimeout.cancel()
-               self.__on_success(self)
-
-       def _report_error(self, reason):
-               assert not self._didReport, "Double reporting a missed call"
-               self._didReport = True
-               self._onTimeout.cancel()
-               self.__on_error(self, reason)
-
-       @misc.log_exception(_moduleLogger)
-       def _on_got_all(self, properties):
-               self._requested = properties["Requested"]
-               self._report_missed_if_ready()
-
-       @misc.log_exception(_moduleLogger)
-       def _on_members_changed(self, message, added, removed, lp, rp, actor, reason):
-               if added:
-                       self._didMembersChange = True
-                       self._report_missed_if_ready()
-
-       @misc.log_exception(_moduleLogger)
-       def _on_closed(self):
-               self._didClose = True
-               self._report_missed_if_ready()
-
-       @misc.log_exception(_moduleLogger)
-       def _on_error(self, *args):
-               self._report_error(args)
-
-       @misc.log_exception(_moduleLogger)
-       def _on_timeout(self):
-               self._report_error("timeout")
-               return False
-
-
-class NewChannelSignaller(object):
-
-       def __init__(self, on_new_channel):
-               self._sessionBus = dbus.SessionBus()
-               self._on_user_new_channel = on_new_channel
-
-       def start(self):
-               self._sessionBus.add_signal_receiver(
-                       self._on_new_channel,
-                       "NewChannel",
-                       "org.freedesktop.Telepathy.Connection",
-                       None,
-                       None
-               )
-
-       def stop(self):
-               self._sessionBus.remove_signal_receiver(
-                       self._on_new_channel,
-                       "NewChannel",
-                       "org.freedesktop.Telepathy.Connection",
-                       None,
-                       None
-               )
-
-       @misc.log_exception(_moduleLogger)
-       def _on_new_channel(
-               self, channelObjectPath, channelType, handleType, handle, supressHandler
-       ):
-               connObjectPath = channel_path_to_conn_path(channelObjectPath)
-               serviceName = path_to_service_name(channelObjectPath)
-               try:
-                       self._on_user_new_channel(
-                               self._sessionBus, serviceName, connObjectPath, channelObjectPath, channelType
-                       )
-               except Exception:
-                       _moduleLogger.exception("Blocking exception from being passed up")
-
-
-class EnableSystemContactIntegration(object):
-
-       ACCOUNT_MGR_NAME = "org.freedesktop.Telepathy.AccountManager"
-       ACCOUNT_MGR_PATH = "/org/freedesktop/Telepathy/AccountManager"
-       ACCOUNT_MGR_IFACE_QUERY = "com.nokia.AccountManager.Interface.Query"
-       ACCOUNT_IFACE_COMPAT = "com.nokia.Account.Interface.Compat"
-       ACCOUNT_IFACE_COMPAT_PROFILE = "com.nokia.Account.Interface.Compat.Profile"
-       DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
-
-       def __init__(self, profileName):
-               self._bus = dbus.SessionBus()
-               self._profileName = profileName
-
-       def start(self):
-               self._accountManager = self._bus.get_object(
-                       self.ACCOUNT_MGR_NAME,
-                       self.ACCOUNT_MGR_PATH,
-               )
-               self._accountManagerQuery = dbus.Interface(
-                       self._accountManager,
-                       dbus_interface=self.ACCOUNT_MGR_IFACE_QUERY,
-               )
-
-               self._accountManagerQuery.FindAccounts(
-                       {
-                               self.ACCOUNT_IFACE_COMPAT_PROFILE: self._profileName,
-                       },
-                       reply_handler = self._on_found_accounts_reply,
-                       error_handler = self._on_error,
-               )
-
-       @misc.log_exception(_moduleLogger)
-       def _on_found_accounts_reply(self, accountObjectPaths):
-               for accountObjectPath in accountObjectPaths:
-                       print accountObjectPath
-                       account = self._bus.get_object(
-                               self.ACCOUNT_MGR_NAME,
-                               accountObjectPath,
-                       )
-                       accountProperties = dbus.Interface(
-                               account,
-                               self.DBUS_PROPERTIES,
-                       )
-                       accountProperties.Set(
-                               self.ACCOUNT_IFACE_COMPAT,
-                               "SecondaryVCardFields",
-                               ["TEL"],
-                               reply_handler = self._on_field_set,
-                               error_handler = self._on_error,
-                       )
-
-       @misc.log_exception(_moduleLogger)
-       def _on_field_set(self):
-               _moduleLogger.info("SecondaryVCardFields Set")
-
-       @misc.log_exception(_moduleLogger)
-       def _on_error(self, error):
-               _moduleLogger.error("%r" % (error, ))
-
-
-def channel_path_to_conn_path(channelObjectPath):
-       """
-       >>> channel_path_to_conn_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
-       '/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME'
-       """
-       return channelObjectPath.rsplit("/", 1)[0]
-
-
-def path_to_service_name(path):
-       """
-       >>> path_to_service_name("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
-       'org.freedesktop.Telepathy.ConnectionManager.theonering.gv.USERNAME'
-       """
-       return ".".join(path[1:].split("/")[0:7])
-
-
-def cm_from_path(path):
-       """
-       >>> cm_from_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
-       'theonering'
-       """
-       return path[1:].split("/")[4]
diff --git a/support/builddeb.py b/support/builddeb.py
deleted file mode 100755 (executable)
index 7710f1f..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/python2.5
-
-import os
-import sys
-
-try:
-       import py2deb
-except ImportError:
-       import fake_py2deb as py2deb
-
-import constants
-
-
-__appname__ = constants.__app_name__
-__description__ = """A Touch Screen Optimized RPN Calculator using Pie Menus
-.
-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__ = """
-* Temporarilly removing redraw reduction due to issues
-""".strip()
-
-
-__postinstall__ = """#!/bin/sh -e
-
-gtk-update-icon-cache -f /usr/share/icons/hicolor
-rm -f ~/.%(name)s/%(name)s.log
-""" % {"name": constants.__app_name__}
-
-__preremove__ = """#!/bin/sh -e
-"""
-
-
-def find_files(prefix, path):
-       for root, dirs, files in os.walk(path):
-               for file in files:
-                       if file.startswith(prefix+"-"):
-                               fileParts = file.split("-")
-                               unused, relPathParts, newName = fileParts[0], fileParts[1:-1], fileParts[-1]
-                               assert unused == prefix
-                               relPath = os.sep.join(relPathParts)
-                               yield relPath, file, newName
-
-
-def unflatten_files(files):
-       d = {}
-       for relPath, oldName, newName in files:
-               if relPath not in d:
-                       d[relPath] = []
-               d[relPath].append((oldName, newName))
-       return d
-
-
-def build_package(distribution):
-       try:
-               os.chdir(os.path.dirname(sys.argv[0]))
-       except:
-               pass
-
-       py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution]
-       p = py2deb.Py2deb(__appname__)
-       p.prettyName = constants.__pretty_app_name__
-       p.description = __description__
-       p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=ejpi"
-       p.author = __author__
-       p.mail = __email__
-       p.license = "lgpl"
-       p.depends = ", ".join([
-               "python2.6 | python2.5",
-       ])
-       p.depends += {
-               "debian": ", python-qt4",
-               "diablo": ", python2.5-qt4-core, python2.5-qt4-gui",
-               "fremantle": ", python2.5-qt4-core, python2.5-qt4-gui, python2.5-qt4-maemo5",
-       }[distribution]
-       p.section = {
-               "debian": "math",
-               "diablo": "user/science",
-               "fremantle": "user/science",
-       }[distribution]
-       p.arch = "all"
-       p.urgency = "low"
-       p.distribution = "diablo fremantle debian"
-       p.repository = "extras"
-       p.changelog = __changelog__
-       p.postinstall = __postinstall__
-       p.icon = {
-               "debian": "26x26-ejpi.png",
-               "diablo": "26x26-ejpi.png",
-               "fremantle": "64x64-ejpi.png", # Fremantle natively uses 48x48
-       }[distribution]
-       p["/opt/%s/bin" % __appname__] = [ "%s.py" % __appname__ ]
-       for relPath, files in unflatten_files(find_files("src", ".")).iteritems():
-               fullPath = "/opt/%s/lib" % __appname__
-               if relPath:
-                       fullPath += os.sep+relPath
-               p[fullPath] = list(
-                       "|".join((oldName, newName))
-                       for (oldName, newName) in files
-               )
-       p["/usr/share/applications/hildon"] = ["%s.desktop" % __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
-               print p.generate(
-                       version="%s-%s" % (__version__, __build__),
-                       changelog=__changelog__,
-                       build=True,
-                       tar=False,
-                       changes=False,
-                       dsc=False,
-               )
-               print "Building for %s finished" % distribution
-       else:
-               print p
-               print p.generate(
-                       version="%s-%s" % (__version__, __build__),
-                       changelog=__changelog__,
-                       build=False,
-                       tar=True,
-                       changes=True,
-                       dsc=True,
-               )
-               print "Building for %s finished" % distribution
-
-
-if __name__ == "__main__":
-       if len(sys.argv) > 1:
-               try:
-                       import optparse
-               except ImportError:
-                       optparse = None
-
-               if optparse is not None:
-                       parser = optparse.OptionParser()
-                       (commandOptions, commandArgs) = parser.parse_args()
-       else:
-               commandArgs = None
-               commandArgs = ["diablo"]
-       build_package(commandArgs[0])
diff --git a/support/ejpi.desktop b/support/ejpi.desktop
deleted file mode 100644 (file)
index 8744cf9..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-[Desktop Entry]
-Encoding=UTF-8
-Version=1.0
-Type=Application
-Name=ejpi
-Exec=/usr/bin/run-standalone.sh /opt/ejpi/bin/ejpi.py
-Icon=ejpi
-Categories=Engineering;Science;Education;Utility;Qt;
diff --git a/support/fake_py2deb.py b/support/fake_py2deb.py
deleted file mode 100644 (file)
index 5d6149d..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-import pprint
-
-
-class Py2deb(object):
-
-       def __init__(self, appName):
-               self._appName = appName
-               self.description = ""
-               self.author = ""
-               self.mail = ""
-               self.license = ""
-               self.depends = ""
-               self.section = ""
-               self.arch = ""
-               self.ugency = ""
-               self.distribution = ""
-               self.repository = ""
-               self.changelog = ""
-               self.postinstall = ""
-               self.icon = ""
-               self._install = {}
-
-       def generate(self, appVersion, appBuild, changelog, tar, dsc, changes, build, src):
-               return """
-Package: %s
-version: %s-%s
-Changes:
-%s
-
-Build Options:
-       Tar: %s
-       Dsc: %s
-       Changes: %s
-       Build: %s
-       Src: %s
-               """ % (
-                       self._appName, appVersion, appBuild, changelog, tar, dsc, changes, build, src
-               )
-
-       def __str__(self):
-               parts = []
-               parts.append("%s Package Settings:" % (self._appName, ))
-               for settingName in dir(self):
-                       if settingName.startswith("_"):
-                               continue
-                       parts.append("\t%s: %s" % (settingName, getattr(self, settingName)))
-
-               parts.append(pprint.pformat(self._install))
-
-               return "\n".join(parts)
-
-       def __getitem__(self, key):
-               return self._install[key]
-
-       def __setitem__(self, key, item):
-               self._install[key] = item
diff --git a/support/icons/26.png b/support/icons/26.png
deleted file mode 100644 (file)
index 35327e9..0000000
Binary files a/support/icons/26.png and /dev/null differ
diff --git a/support/icons/64.png b/support/icons/64.png
deleted file mode 100644 (file)
index 96503e5..0000000
Binary files a/support/icons/64.png and /dev/null differ
diff --git a/support/icons/scalable.png b/support/icons/scalable.png
deleted file mode 100644 (file)
index e65e834..0000000
Binary files a/support/icons/scalable.png and /dev/null differ
diff --git a/support/py2deb.py b/support/py2deb.py
deleted file mode 100644 (file)
index 0518480..0000000
+++ /dev/null
@@ -1,994 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-##
-##    Copyright (C) 2009 manatlan manatlan[at]gmail(dot)com
-##
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published
-## by the Free Software Foundation; version 2 only.
-##
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-##
-"""
-Known limitations :
-- don't sign package (-us -uc)
-- no distinctions between author and maintainer(packager)
-
-depends on :
-- dpkg-dev (dpkg-buildpackage)
-- alien
-- python
-- fakeroot
-
-changelog
- - ??? ?/??/20?? (By epage)
-    - PEP8
-    - added recommends
-    - fixed bug where it couldn't handle the contents of the pre/post scripts being specified
-    - Added customization based on the targeted policy for sections (Maemo support)
-    - Added maemo specific tarball, dsc, changes file generation support (including icon support)
-    - Added armel architecture
-    - Reduced the size of params being passed around by reducing the calls to locals()
-    - Added respository, distribution, priority
-    - Made setting control file a bit more flexible
- - 0.5 05/09/2009
-    - pre/post install/remove scripts enabled
-    - deb package install py2deb in dist-packages for py2.6
- - 0.4 14/10/2008
-    - use os.environ USERNAME or USER (debian way)
-    - install on py 2.(4,5,6) (*FIX* do better here)
-
-"""
-
-import os
-import hashlib
-import sys
-import shutil
-import time
-import string
-import StringIO
-import stat
-import commands
-import base64
-import tarfile
-from glob import glob
-from datetime import datetime
-import socket # gethostname()
-from subprocess import Popen, PIPE
-
-#~ __version__ = "0.4"
-__version__ = "0.5"
-__author__ = "manatlan"
-__mail__ = "manatlan@gmail.com"
-
-
-PERMS_URW_GRW_OR = stat.S_IRUSR | stat.S_IWUSR | \
-                   stat.S_IRGRP | stat.S_IWGRP | \
-                   stat.S_IROTH
-
-UID_ROOT = 0
-GID_ROOT = 0
-
-
-def run(cmds):
-    p = Popen(cmds, shell=False, stdout=PIPE, stderr=PIPE)
-    time.sleep(0.01)    # to avoid "IOError: [Errno 4] Interrupted system call"
-    out = string.join(p.stdout.readlines()).strip()
-    outerr = string.join(p.stderr.readlines()).strip()
-    return out
-
-
-def deb2rpm(file):
-    txt=run(['alien', '-r', file])
-    return txt.split(" generated")[0]
-
-
-def py2src(TEMP, name):
-    l=glob("%(TEMP)s/%(name)s*.tar.gz" % locals())
-    if len(l) != 1:
-        raise Py2debException("don't find source package tar.gz")
-
-    tar = os.path.basename(l[0])
-    shutil.move(l[0], tar)
-
-    return tar
-
-
-def md5sum(filename):
-    f = open(filename, "r")
-    try:
-        return hashlib.md5(f.read()).hexdigest()
-    finally:
-        f.close()
-
-
-class Py2changes(object):
-
-    def __init__(self, ChangedBy, description, changes, files, category, repository, **kwargs):
-      self.options = kwargs # TODO: Is order important?
-      self.description = description
-      self.changes=changes
-      self.files=files
-      self.category=category
-      self.repository=repository
-      self.ChangedBy=ChangedBy
-
-    def getContent(self):
-        content = ["%s: %s" % (k, v)
-                   for k,v in self.options.iteritems()]
-
-        if self.description:
-            description=self.description.replace("\n","\n ")
-            content.append('Description: ')
-            content.append(' %s' % description)
-        if self.changes:
-            changes=self.changes.replace("\n","\n ")
-            content.append('Changes: ')
-            content.append(' %s' % changes)
-        if self.ChangedBy:
-            content.append("Changed-By: %s" % self.ChangedBy)
-
-        content.append('Files:')
-
-        for onefile in self.files:
-            md5 = md5sum(onefile)
-            size = os.stat(onefile).st_size.__str__()
-            content.append(' ' + md5 + ' ' + size + ' ' + self.category +' '+self.repository+' '+os.path.basename(onefile))
-
-        return "\n".join(content) + "\n\n"
-
-
-def py2changes(params):
-    changescontent = Py2changes(
-        "%(author)s <%(mail)s>" % params,
-        "%(description)s" % params,
-        "%(changelog)s" % params,
-        (
-            "%(TEMP)s/%(name)s_%(version)s.tar.gz" % params,
-            "%(TEMP)s/%(name)s_%(version)s.dsc" % params,
-        ),
-        "%(section)s" % params,
-        "%(repository)s" % params,
-        Format='1.7',
-        Date=time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime()),
-        Source="%(name)s" % params,
-        Architecture="%(arch)s" % params,
-        Version="%(version)s" % params,
-        Distribution="%(distribution)s" % params,
-        Urgency="%(urgency)s" % params,
-        Maintainer="%(author)s <%(mail)s>" % params
-    )
-    f = open("%(TEMP)s/%(name)s_%(version)s.changes" % params,"wb")
-    f.write(changescontent.getContent())
-    f.close()
-
-    fileHandle = open('/tmp/py2deb2.tmp', 'w')
-    fileHandle.write('#!/bin/sh\n')
-    fileHandle.write("cd " +os.getcwd()+ "\n")
-    # TODO Renable signing
-    # fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params)
-    fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.changes.asc %(TEMP)s/%(name)s_%(version)s.changes\n" % params)
-    fileHandle.write('\nexit')
-    fileHandle.close()
-    commands.getoutput("chmod 777 /tmp/py2deb2.tmp")
-    commands.getoutput("/tmp/py2deb2.tmp")
-
-    ret = []
-
-    l=glob("%(TEMP)s/%(name)s*.tar.gz" % params)
-    if len(l)!=1:
-        raise Py2debException("don't find source package tar.gz")
-    tar = os.path.basename(l[0])
-    shutil.move(l[0],tar)
-    ret.append(tar)
-
-    l=glob("%(TEMP)s/%(name)s*.dsc" % params)
-    if len(l)!=1:
-        raise Py2debException("don't find source package dsc")
-    tar = os.path.basename(l[0])
-    shutil.move(l[0],tar)
-    ret.append(tar)
-
-    l = glob("%(TEMP)s/%(name)s*.changes" % params)
-    if len(l)!=1:
-        raise Py2debException("don't find source package changes")
-    tar = os.path.basename(l[0])
-    shutil.move(l[0],tar)
-    ret.append(tar)
-
-    return ret
-
-
-class Py2dsc(object):
-
-    def __init__(self, StandardsVersion, BuildDepends, files, **kwargs):
-      self.options = kwargs # TODO: Is order important?
-      self.StandardsVersion = StandardsVersion
-      self.BuildDepends=BuildDepends
-      self.files=files
-
-    @property
-    def content(self):
-        content = ["%s: %s" % (k, v)
-                   for k,v in self.options.iteritems()]
-
-        if self.BuildDepends:
-            content.append("Build-Depends: %s" % self.BuildDepends)
-        if self.StandardsVersion:
-            content.append("Standards-Version: %s" % self.StandardsVersion)
-
-        content.append('Files:')
-
-        for onefile in self.files:
-            print onefile
-            md5 = md5sum(onefile)
-            size = os.stat(onefile).st_size.__str__()
-            content.append(' '+md5 + ' ' + size +' '+os.path.basename(onefile))
-
-        return "\n".join(content)+"\n\n"
-
-
-def py2dsc(TEMP, name, version, depends, author, mail, arch):
-    dsccontent = Py2dsc(
-        "%(version)s" % locals(),
-        "%(depends)s" % locals(),
-        ("%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals(),),
-        Format='1.0',
-        Source="%(name)s" % locals(),
-        Version="%(version)s" % locals(),
-        Maintainer="%(author)s <%(mail)s>" % locals(),
-        Architecture="%(arch)s" % locals(),
-    )
-
-    filename = "%(TEMP)s/%(name)s_%(version)s.dsc" % locals()
-
-    f = open(filename, "wb")
-    try:
-        f.write(dsccontent.content)
-    finally:
-        f.close()
-
-    fileHandle = open('/tmp/py2deb.tmp', 'w')
-    try:
-        fileHandle.write('#!/bin/sh\n')
-        fileHandle.write("cd " + os.getcwd() + "\n")
-        # TODO Renable signing
-        # fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals())
-        fileHandle.write("mv %(TEMP)s/%(name)s_%(version)s.dsc.asc %(filename)s\n" % locals())
-        fileHandle.write('\nexit')
-        fileHandle.close()
-    finally:
-        f.close()
-
-    commands.getoutput("chmod 777 /tmp/py2deb.tmp")
-    commands.getoutput("/tmp/py2deb.tmp")
-
-    return filename
-
-
-class Py2tar(object):
-
-    def __init__(self, dataDirectoryPath):
-        self._dataDirectoryPath = dataDirectoryPath
-
-    def packed(self):
-        return self._getSourcesFiles()
-
-    def _getSourcesFiles(self):
-        directoryPath = self._dataDirectoryPath
-
-        outputFileObj = StringIO.StringIO() # TODO: Do more transparently?
-
-        tarOutput = tarfile.TarFile.open('sources',
-                                 mode = "w:gz",
-                                 fileobj = outputFileObj)
-
-        # Note: We can't use this because we need to fiddle permissions:
-        #       tarOutput.add(directoryPath, arcname = "")
-
-        for root, dirs, files in os.walk(directoryPath):
-            archiveRoot = root[len(directoryPath):]
-
-            tarinfo = tarOutput.gettarinfo(root, archiveRoot)
-            # TODO: Make configurable?
-            tarinfo.uid = UID_ROOT
-            tarinfo.gid = GID_ROOT
-            tarinfo.uname = ""
-            tarinfo.gname = ""
-            tarOutput.addfile(tarinfo)
-
-            for f in  files:
-                tarinfo = tarOutput.gettarinfo(os.path.join(root, f),
-                                               os.path.join(archiveRoot, f))
-                tarinfo.uid = UID_ROOT
-                tarinfo.gid = GID_ROOT
-                tarinfo.uname = ""
-                tarinfo.gname = ""
-                tarOutput.addfile(tarinfo, file(os.path.join(root, f)))
-
-        tarOutput.close()
-
-        data_tar_gz = outputFileObj.getvalue()
-
-        return data_tar_gz
-
-
-def py2tar(DEST, TEMP, name, version):
-    tarcontent = Py2tar("%(DEST)s" % locals())
-    filename = "%(TEMP)s/%(name)s_%(version)s.tar.gz" % locals()
-    f = open(filename, "wb")
-    try:
-        f.write(tarcontent.packed())
-    finally:
-        f.close()
-    return filename
-
-
-class Py2debException(Exception):
-    pass
-
-
-SECTIONS_BY_POLICY = {
-    # http://www.debian.org/doc/debian-policy/ch-archive.html#s-subsections
-    "debian": "admin, base, comm, contrib, devel, doc, editors, electronics, embedded, games, gnome, graphics, hamradio, interpreters, kde, libs, libdevel, mail, math, misc, net, news, non-free, oldlibs, otherosfs, perl, python, science, shells, sound, tex, text, utils, web, x11",
-    # http://maemo.org/forrest-images/pdf/maemo-policy.pdf
-    "chinook": "accessories, communication, games, multimedia, office, other, programming, support, themes, tools",
-    # http://wiki.maemo.org/Task:Package_categories
-    "diablo": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
-    # http://wiki.maemo.org/Task:Fremantle_application_categories
-    "mer": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
-    # http://wiki.maemo.org/Task:Fremantle_application_categories
-    "fremantle": "user/desktop, user/development, user/education, user/games, user/graphics, user/multimedia, user/navigation, user/network, user/office, user/science, user/system, user/utilities",
-}
-
-
-LICENSE_AGREEMENT = {
-        "gpl": """
-    This package is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License as published by
-    the Free Software Foundation; either version 2 of the License, or
-    (at your option) any later version.
-
-    This package is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-    GNU General Public License for more details.
-
-    You should have received a copy of the GNU General Public License
-    along with this package; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-
-On Debian systems, the complete text of the GNU General
-Public License can be found in `/usr/share/common-licenses/GPL'.
-""",
-        "lgpl":"""
-    This package is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Lesser General Public
-    License as published by the Free Software Foundation; either
-    version 2 of the License, or (at your option) any later version.
-
-    This package is distributed in the hope that it will be useful,
-    but WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-    Lesser General Public License for more details.
-
-    You should have received a copy of the GNU Lesser General Public
-    License along with this package; if not, write to the Free Software
-    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
-
-On Debian systems, the complete text of the GNU Lesser General
-Public License can be found in `/usr/share/common-licenses/LGPL'.
-""",
-        "bsd": """
-    Redistribution and use in source and binary forms, with or without
-    modification, are permitted under the terms of the BSD License.
-
-    THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
-    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-    ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
-    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
-    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
-    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-    SUCH DAMAGE.
-
-On Debian systems, the complete text of the BSD License can be
-found in `/usr/share/common-licenses/BSD'.
-""",
-        "artistic": """
-    This program is free software; you can redistribute it and/or modify it
-    under the terms of the "Artistic License" which comes with Debian.
-
-    THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
-    WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES
-    OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-
-On Debian systems, the complete text of the Artistic License
-can be found in `/usr/share/common-licenses/Artistic'.
-"""
-}
-
-
-class Py2deb(object):
-    """
-    heavily based on technic described here :
-    http://wiki.showmedo.com/index.php?title=LinuxJensMakingDeb
-    """
-    ## STATICS
-    clear = False  # clear build folder after py2debianization
-
-    SECTIONS = SECTIONS_BY_POLICY["debian"]
-
-    #http://www.debian.org/doc/debian-policy/footnotes.html#f69
-    ARCHS = "all i386 ia64 alpha amd64 armeb arm hppa m32r m68k mips mipsel powerpc ppc64 s390 s390x sh3 sh3eb sh4 sh4eb sparc darwin-i386 darwin-ia64 darwin-alpha darwin-amd64 darwin-armeb darwin-arm darwin-hppa darwin-m32r darwin-m68k darwin-mips darwin-mipsel darwin-powerpc darwin-ppc64 darwin-s390 darwin-s390x darwin-sh3 darwin-sh3eb darwin-sh4 darwin-sh4eb darwin-sparc freebsd-i386 freebsd-ia64 freebsd-alpha freebsd-amd64 freebsd-armeb freebsd-arm freebsd-hppa freebsd-m32r freebsd-m68k freebsd-mips freebsd-mipsel freebsd-powerpc freebsd-ppc64 freebsd-s390 freebsd-s390x freebsd-sh3 freebsd-sh3eb freebsd-sh4 freebsd-sh4eb freebsd-sparc kfreebsd-i386 kfreebsd-ia64 kfreebsd-alpha kfreebsd-amd64 kfreebsd-armeb kfreebsd-arm kfreebsd-hppa kfreebsd-m32r kfreebsd-m68k kfreebsd-mips kfreebsd-mipsel kfreebsd-powerpc kfreebsd-ppc64 kfreebsd-s390 kfreebsd-s390x kfreebsd-sh3 kfreebsd-sh3eb kfreebsd-sh4 kfreebsd-sh4eb kfreebsd-sparc knetbsd-i386 knetbsd-ia64 knetbsd-alpha knetbsd-amd64 knetbsd-armeb knetbsd-arm knetbsd-hppa knetbsd-m32r knetbsd-m68k knetbsd-mips knetbsd-mipsel knetbsd-powerpc knetbsd-ppc64 knetbsd-s390 knetbsd-s390x knetbsd-sh3 knetbsd-sh3eb knetbsd-sh4 knetbsd-sh4eb knetbsd-sparc netbsd-i386 netbsd-ia64 netbsd-alpha netbsd-amd64 netbsd-armeb netbsd-arm netbsd-hppa netbsd-m32r netbsd-m68k netbsd-mips netbsd-mipsel netbsd-powerpc netbsd-ppc64 netbsd-s390 netbsd-s390x netbsd-sh3 netbsd-sh3eb netbsd-sh4 netbsd-sh4eb netbsd-sparc openbsd-i386 openbsd-ia64 openbsd-alpha openbsd-amd64 openbsd-armeb openbsd-arm openbsd-hppa openbsd-m32r openbsd-m68k openbsd-mips openbsd-mipsel openbsd-powerpc openbsd-ppc64 openbsd-s390 openbsd-s390x openbsd-sh3 openbsd-sh3eb openbsd-sh4 openbsd-sh4eb openbsd-sparc hurd-i386 hurd-ia64 hurd-alpha hurd-amd64 hurd-armeb hurd-arm hurd-hppa hurd-m32r hurd-m68k hurd-mips hurd-mipsel hurd-powerpc hurd-ppc64 hurd-s390 hurd-s390x hurd-sh3 hurd-sh3eb hurd-sh4 hurd-sh4eb hurd-sparc armel".split(" ")
-
-    # license terms taken from dh_make
-    LICENSES = list(LICENSE_AGREEMENT.iterkeys())
-
-    def __setitem__(self, path, files):
-
-        if not type(files)==list:
-            raise Py2debException("value of key path '%s' is not a list"%path)
-        if not files:
-            raise Py2debException("value of key path '%s' should'nt be empty"%path)
-        if not path.startswith("/"):
-            raise Py2debException("key path '%s' malformed (don't start with '/')"%path)
-        if path.endswith("/"):
-            raise Py2debException("key path '%s' malformed (shouldn't ends with '/')"%path)
-
-        nfiles=[]
-        for file in files:
-
-            if ".." in file:
-                raise Py2debException("file '%s' contains '..', please avoid that!"%file)
-
-
-            if "|" in file:
-                if file.count("|")!=1:
-                    raise Py2debException("file '%s' is incorrect (more than one pipe)"%file)
-
-                file, nfile = file.split("|")
-            else:
-                nfile=file  # same localisation
-
-            if os.path.isdir(file):
-                raise Py2debException("file '%s' is a folder, and py2deb refuse folders !"%file)
-
-            if not os.path.isfile(file):
-                raise Py2debException("file '%s' doesn't exist"%file)
-
-            if file.startswith("/"):    # if an absolute file is defined
-                if file==nfile:         # and not renamed (pipe trick)
-                    nfile=os.path.basename(file)   # it's simply copied to 'path'
-
-            nfiles.append((file, nfile))
-
-        nfiles.sort(lambda a, b: cmp(a[1], b[1]))    #sort according new name (nfile)
-
-        self.__files[path]=nfiles
-
-    def __getitem__(self, k):
-        return self.__files[k]
-
-    def __delitem__(self, k):
-        del self.__files[k]
-
-    def __init__(self,
-                    name,
-                    description="no description",
-                    license="gpl",
-                    depends="",
-                    section="utils",
-                    arch="all",
-
-                    url="",
-                    author = None,
-                    mail = None,
-
-                    preinstall = None,
-                    postinstall = None,
-                    preremove = None,
-                    postremove = None
-                ):
-
-        if author is None:
-            author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None
-            if author is None:
-                author = ("USER" in os.environ) and os.environ["USER"] or "unknown"
-
-        if mail is None:
-            mail = author+"@"+socket.gethostname()
-
-        self.name = name
-        self.prettyName = ""
-        self.description = description
-        self.upgradeDescription = ""
-        self.bugTracker = ""
-        self.license = license
-        self.depends = depends
-        self.recommends = ""
-        self.section = section
-        self.arch = arch
-        self.url = url
-        self.author = author
-        self.mail = mail
-        self.icon = ""
-        self.distribution = ""
-        self.respository = ""
-        self.urgency = "low"
-
-        self.preinstall = preinstall
-        self.postinstall = postinstall
-        self.preremove = preremove
-        self.postremove = postremove
-
-        self.__files={}
-
-    def __repr__(self):
-        name = self.name
-        license = self.license
-        description = self.description
-        depends = self.depends
-        recommends = self.recommends
-        section = self.section
-        arch = self.arch
-        url = self.url
-        author = self.author
-        mail = self.mail
-
-        preinstall = self.preinstall
-        postinstall = self.postinstall
-        preremove = self.preremove
-        postremove = self.postremove
-
-        paths=self.__files.keys()
-        paths.sort()
-        files=[]
-        for path in paths:
-            for file, nfile in self.__files[path]:
-                #~ rfile=os.path.normpath(os.path.join(path, nfile))
-                rfile=os.path.join(path, nfile)
-                if nfile==file:
-                    files.append(rfile)
-                else:
-                    files.append(rfile + " (%s)"%file)
-
-        files.sort()
-        files = "\n".join(files)
-
-
-        lscripts = [    preinstall and "preinst",
-                        postinstall and "postinst",
-                        preremove and "prerm",
-                        postremove and "postrm",
-                    ]
-        scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None"
-        return """
-----------------------------------------------------------------------
-NAME        : %(name)s
-----------------------------------------------------------------------
-LICENSE     : %(license)s
-URL         : %(url)s
-AUTHOR      : %(author)s
-MAIL        : %(mail)s
-----------------------------------------------------------------------
-DEPENDS     : %(depends)s
-RECOMMENDS  : %(recommends)s
-ARCH        : %(arch)s
-SECTION     : %(section)s
-----------------------------------------------------------------------
-DESCRIPTION :
-%(description)s
-----------------------------------------------------------------------
-SCRIPTS : %(scripts)s
-----------------------------------------------------------------------
-FILES :
-%(files)s
-""" % locals()
-
-    def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False):
-        """ generate a deb of version 'version', with or without 'changelog', with or without a rpm
-            (in the current folder)
-            return a list of generated files
-        """
-        if not sum([len(i) for i in self.__files.values()])>0:
-            raise Py2debException("no files are defined")
-
-        if not changelog:
-            changelog="* no changelog"
-
-        name = self.name
-        description = self.description
-        license = self.license
-        depends = self.depends
-        recommends = self.recommends
-        section = self.section
-        arch = self.arch
-        url = self.url
-        distribution = self.distribution
-        repository = self.repository
-        urgency = self.urgency
-        author = self.author
-        mail = self.mail
-        files = self.__files
-        preinstall = self.preinstall
-        postinstall = self.postinstall
-        preremove = self.preremove
-        postremove = self.postremove
-
-        if section not in Py2deb.SECTIONS:
-            raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS)))
-
-        if arch not in Py2deb.ARCHS:
-            raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS)))
-
-        if license not in Py2deb.LICENSES:
-            raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES)))
-
-        # create dates (buildDate, buildDateYear)
-        d=datetime.now()
-        buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000")
-        buildDateYear=str(d.year)
-
-        #clean description (add a space before each next lines)
-        description=description.replace("\r", "").strip()
-        description = "\n ".join(description.split("\n"))
-
-        #clean changelog (add 2 spaces before each next lines)
-        changelog=changelog.replace("\r", "").strip()
-        changelog = "\n  ".join(changelog.split("\n"))
-
-        TEMP = ".py2deb_build_folder"
-        DEST = os.path.join(TEMP, name)
-        DEBIAN = os.path.join(DEST, "debian")
-
-        packageContents = locals()
-
-        # let's start the process
-        try:
-            shutil.rmtree(TEMP)
-        except:
-            pass
-
-        os.makedirs(DEBIAN)
-        try:
-            rules=[]
-            dirs=[]
-            for path in files:
-                for ofile, nfile in files[path]:
-                    if os.path.isfile(ofile):
-                        # it's a file
-
-                        if ofile.startswith("/"): # if absolute path
-                            # we need to change dest
-                            dest=os.path.join(DEST, nfile)
-                        else:
-                            dest=os.path.join(DEST, ofile)
-
-                        # copy file to be packaged
-                        destDir = os.path.dirname(dest)
-                        if not os.path.isdir(destDir):
-                            os.makedirs(destDir)
-
-                        shutil.copy2(ofile, dest)
-
-                        ndir = os.path.join(path, os.path.dirname(nfile))
-                        nname = os.path.basename(nfile)
-
-                        # make a line RULES to be sure the destination folder is created
-                        # and one for copying the file
-                        fpath = "/".join(["$(CURDIR)", "debian", name+ndir])
-                        rules.append('mkdir -p "%s"' % fpath)
-                        rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname)))
-
-                        # append a dir
-                        dirs.append(ndir)
-
-                    else:
-                        raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before)
-
-            # make rules right
-            rules= "\n\t".join(rules) + "\n"
-            packageContents["rules"] = rules
-
-            # make dirs right
-            dirs= [i[1:] for i in set(dirs)]
-            dirs.sort()
-
-            #==========================================================================
-            # CREATE debian/dirs
-            #==========================================================================
-            open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs))
-
-            #==========================================================================
-            # CREATE debian/changelog
-            #==========================================================================
-            clog="""%(name)s (%(version)s) stable; urgency=low
-
-  %(changelog)s
-
- -- %(author)s <%(mail)s>  %(buildDate)s
-""" % packageContents
-
-            open(os.path.join(DEBIAN, "changelog"), "w").write(clog)
-
-            #==========================================================================
-            #Create pre/post install/remove
-            #==========================================================================
-            def mkscript(name, dest):
-                if name and name.strip()!="":
-                    if os.path.isfile(name):    # it's a file
-                        content = file(name).read()
-                    else:   # it's a script
-                        content = name
-                    open(os.path.join(DEBIAN, dest), "w").write(content)
-
-            mkscript(preinstall, "preinst")
-            mkscript(postinstall, "postinst")
-            mkscript(preremove, "prerm")
-            mkscript(postremove, "postrm")
-
-
-            #==========================================================================
-            # CREATE debian/compat
-            #==========================================================================
-            open(os.path.join(DEBIAN, "compat"), "w").write("5\n")
-
-            #==========================================================================
-            # CREATE debian/control
-            #==========================================================================
-            generalParagraphFields = [
-                "Source: %(name)s",
-                "Maintainer: %(author)s <%(mail)s>",
-                "Section: %(section)s",
-                "Priority: extra",
-                "Build-Depends: debhelper (>= 5)",
-                "Standards-Version: 3.7.2",
-            ]
-
-            specificParagraphFields = [
-                "Package: %(name)s",
-                "Architecture: %(arch)s",
-                "Depends: %(depends)s",
-                "Recommends: %(recommends)s",
-                "Description: %(description)s",
-            ]
-
-            if self.prettyName:
-                prettyName = "XSBC-Maemo-Display-Name: %s" % self.prettyName.strip()
-                specificParagraphFields.append("\n  ".join(prettyName.split("\n")))
-
-            if self.bugTracker:
-                bugTracker = "XSBC-Bugtracker: %s" % self.bugTracker.strip()
-                specificParagraphFields.append("\n  ".join(bugTracker.split("\n")))
-
-            if self.upgradeDescription:
-                upgradeDescription = "XSBC-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip()
-                specificParagraphFields.append("\n  ".join(upgradeDescription.split("\n")))
-
-            if self.icon:
-                f = open(self.icon, "rb")
-                try:
-                    rawIcon = f.read()
-                finally:
-                    f.close()
-                uueIcon = base64.b64encode(rawIcon)
-                uueIconLines = []
-                for i, c in enumerate(uueIcon):
-                    if i % 60 == 0:
-                        uueIconLines.append("")
-                    uueIconLines[-1] += c
-                uueIconLines[0:0] = ("XSBC-Maemo-Icon-26:", )
-                specificParagraphFields.append("\n  ".join(uueIconLines))
-
-            generalParagraph = "\n".join(generalParagraphFields)
-            specificParagraph = "\n".join(specificParagraphFields)
-            controlContent = "\n\n".join((generalParagraph, specificParagraph)) % packageContents
-            open(os.path.join(DEBIAN, "control"), "w").write(controlContent)
-
-            #==========================================================================
-            # CREATE debian/copyright
-            #==========================================================================
-            packageContents["txtLicense"] = LICENSE_AGREEMENT[license]
-            packageContents["pv"] =__version__
-            txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on
-%(buildDate)s.
-
-It was downloaded from %(url)s
-
-Upstream Author: %(author)s <%(mail)s>
-
-Copyright: %(buildDateYear)s by %(author)s
-
-License:
-
-%(txtLicense)s
-
-The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and
-is licensed under the GPL, see above.
-
-
-# Please also look if there are files or directories which have a
-# different copyright/license attached and list them here.
-""" % packageContents
-            open(os.path.join(DEBIAN, "copyright"), "w").write(txt)
-
-            #==========================================================================
-            # CREATE debian/rules
-            #==========================================================================
-            txt="""#!/usr/bin/make -f
-# -*- makefile -*-
-# Sample debian/rules that uses debhelper.
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-
-
-
-CFLAGS = -Wall -g
-
-ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
-       CFLAGS += -O0
-else
-       CFLAGS += -O2
-endif
-
-configure: configure-stamp
-configure-stamp:
-       dh_testdir
-       # Add here commands to configure the package.
-
-       touch configure-stamp
-
-
-build: build-stamp
-
-build-stamp: configure-stamp
-       dh_testdir
-       touch build-stamp
-
-clean:
-       dh_testdir
-       dh_testroot
-       rm -f build-stamp configure-stamp
-       dh_clean
-
-install: build
-       dh_testdir
-       dh_testroot
-       dh_clean -k
-       dh_installdirs
-
-       # ======================================================
-       #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install
-       mkdir -p "$(CURDIR)/debian/%(name)s"
-
-       %(rules)s
-       # ======================================================
-
-# Build architecture-independent files here.
-binary-indep: build install
-# We have nothing to do by default.
-
-# Build architecture-dependent files here.
-binary-arch: build install
-       dh_testdir
-       dh_testroot
-       dh_installchangelogs debian/changelog
-       dh_installdocs
-       dh_installexamples
-#      dh_install
-#      dh_installmenu
-#      dh_installdebconf
-#      dh_installlogrotate
-#      dh_installemacsen
-#      dh_installpam
-#      dh_installmime
-#      dh_python
-#      dh_installinit
-#      dh_installcron
-#      dh_installinfo
-       dh_installman
-       dh_link
-       dh_strip
-       dh_compress
-       dh_fixperms
-#      dh_perl
-#      dh_makeshlibs
-       dh_installdeb
-       dh_shlibdeps
-       dh_gencontrol
-       dh_md5sums
-       dh_builddeb
-
-binary: binary-indep binary-arch
-.PHONY: build clean binary-indep binary-arch binary install configure
-""" % packageContents
-            open(os.path.join(DEBIAN, "rules"), "w").write(txt)
-            os.chmod(os.path.join(DEBIAN, "rules"), 0755)
-
-            ###########################################################################
-            ###########################################################################
-            ###########################################################################
-
-            generatedFiles = []
-
-            if build:
-                #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html
-                ret = os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % packageContents)
-                if ret != 0:
-                    raise Py2debException("buildpackage failed (see output)")
-
-                l=glob("%(TEMP)s/%(name)s*.deb" % packageContents)
-                if len(l) != 1:
-                    raise Py2debException("didn't find builded deb")
-
-                tdeb = l[0]
-                deb = os.path.basename(tdeb)
-                shutil.move(tdeb, deb)
-
-                generatedFiles = [deb, ]
-
-                if rpm:
-                    rpmFilename = deb2rpm(deb)
-                    generatedFiles.append(rpmFilename)
-
-                if src:
-                    tarFilename = py2src(TEMP, name)
-                    generatedFiles.append(tarFilename)
-
-            if tar:
-                tarFilename = py2tar(DEST, TEMP, name, version)
-                generatedFiles.append(tarFilename)
-
-            if dsc:
-                dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch)
-                generatedFiles.append(dscFilename)
-
-            if changes:
-                changesFilenames = py2changes(packageContents)
-                generatedFiles.extend(changesFilenames)
-
-            return generatedFiles
-
-        #~ except Exception,m:
-            #~ raise Py2debException("build error :"+str(m))
-
-        finally:
-            if Py2deb.clear:
-                shutil.rmtree(TEMP)
-
-
-if __name__ == "__main__":
-    try:
-        os.chdir(os.path.dirname(sys.argv[0]))
-    except:
-        pass
-
-    p=Py2deb("python-py2deb")
-    p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)"
-    p.url = "http://www.manatlan.com/page/py2deb"
-    p.author=__author__
-    p.mail=__mail__
-    p.depends = "dpkg-dev, fakeroot, alien, python"
-    p.section="python"
-    p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ]
-    p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ]
-    p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ]
-    #~ p.postinstall = "s.py"
-    #~ p.preinstall = "s.py"
-    #~ p.postremove = "s.py"
-    #~ p.preremove = "s.py"
-    print p
-    print p.generate(__version__, changelog = __doc__, src=True)
diff --git a/support/scale.py b/support/scale.py
new file mode 100755 (executable)
index 0000000..c706802
--- /dev/null
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+from __future__ import division
+
+from PIL import Image
+import logging
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+def main(args):
+       import optparse
+       parser = optparse.OptionParser()
+       parser.add_option(
+               "--input", dest="input",
+               help="Input image to scale", metavar="INPUT"
+       )
+       parser.add_option(
+               "--output", dest="output",
+               help="Scaled image", metavar="OUTPUT"
+       )
+       parser.add_option(
+               "--size", dest="size",
+               help="Icon size", metavar="SIZE"
+       )
+       options, positional  = parser.parse_args(args)
+       if positional:
+               parser.error("No positional arguments supported")
+       if None in [options.input, options.output, options.size]:
+               parser.error("Missing argument")
+
+       icon = Image.open(options.input)
+       icon.thumbnail((options.size, options.size), Image.ANTIALIAS)
+       icon.save(options.output)
+
+
+if __name__ == "__main__":
+       import sys
+       retcode = main(sys.argv[1:])
+       sys.exit(retcode)