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
all: test
run: $(OBJ)
- $(SOURCE_PATH)/$(PROJECT_NAME).py
+ $(PROGRAM)
profile: $(OBJ)
$(PROFILE_GEN) $(PROGRAM)
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) ; )
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)
--- /dev/null
+<?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>
--- /dev/null
+Your icon's dominant color is #808080
+A suggested disabled color is #dadada
+A suggested pressed color is #585858
--- /dev/null
+[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
--- /dev/null
+<?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>
--- /dev/null
+#!/usr/bin/env python
+
+import logging
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+from ejpi import ejpi_qt
+
+
+if __name__ == "__main__":
+ ejpi_qt.run()
--- /dev/null
+#!/usr/bin/env python
--- /dev/null
+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
--- /dev/null
+#!/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)
--- /dev/null
+#!/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
--- /dev/null
+#!/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)
--- /dev/null
+#!/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
--- /dev/null
+#!/usr/bin/env python
--- /dev/null
+[Operator]
+name=Alphabet
+version=0.1
+description=
+
+[Constants]
+name=Alphabet
+version=0.1
+description=
+
+[Keyboard]
+name=Alphabet
+version=0.1
+description=
--- /dev/null
+"""
+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)
--- /dev/null
+[Operator]
+name=Builtins
+version=0.1
+description=
+
+[Constants]
+name=Builtins
+version=0.1
+description=
+
+[Keyboard]
+name=Builtins
+version=0.1
+description=
--- /dev/null
+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)
--- /dev/null
+[Operator]
+name=Computer
+version=0.1
+description=
+
+[Constants]
+name=Computer
+version=0.1
+description=
+
+[Keyboard]
+name=Computer
+version=0.1
+description=
--- /dev/null
+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)
--- /dev/null
+[Operator]
+name=Entry
+version=0.1
+description=
+
+[Constants]
+name=Entry
+version=0.1
+description=
+
+[Keyboard]
+name=Entry
+version=0.1
+description=
--- /dev/null
+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)
--- /dev/null
+[Operator]
+name=Trigonometry
+version=0.1
+description=
+
+[Constants]
+name=Trigonometry
+version=0.1
+description=
+
+[Keyboard]
+name=Trigonometry
+version=0.1
+description=
--- /dev/null
+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)
+
--- /dev/null
+#!/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()
--- /dev/null
+#!/usr/bin/env python
--- /dev/null
+#!/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()
--- /dev/null
+#!/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)
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
+
--- /dev/null
+#!/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)
--- /dev/null
+#!/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 = {
+ """: '"',
+ " ": " ",
+ "'": "'",
+}
+
+
+_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
--- /dev/null
+#!/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), ))
--- /dev/null
+#!/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
--- /dev/null
+#!/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))
--- /dev/null
+#!/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
+
--- /dev/null
+#!/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()
--- /dev/null
+#!/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
+
--- /dev/null
+#!/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_()
--- /dev/null
+#!/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()
--- /dev/null
+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
+
--- /dev/null
+#!/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)
--- /dev/null
+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")
--- /dev/null
+#!/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]
--- /dev/null
+#!/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",
+ },
+ },
+)
--- /dev/null
+ejpi/
\ No newline at end of file
+++ /dev/null
-#!/usr/bin/env python
+++ /dev/null
-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
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/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)
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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)
+++ /dev/null
-#!/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
+++ /dev/null
-#!/usr/bin/env python
+++ /dev/null
-[Operator]
-name=Alphabet
-version=0.1
-description=
-
-[Constants]
-name=Alphabet
-version=0.1
-description=
-
-[Keyboard]
-name=Alphabet
-version=0.1
-description=
+++ /dev/null
-"""
-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)
+++ /dev/null
-[Operator]
-name=Builtins
-version=0.1
-description=
-
-[Constants]
-name=Builtins
-version=0.1
-description=
-
-[Keyboard]
-name=Builtins
-version=0.1
-description=
+++ /dev/null
-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)
+++ /dev/null
-[Operator]
-name=Computer
-version=0.1
-description=
-
-[Constants]
-name=Computer
-version=0.1
-description=
-
-[Keyboard]
-name=Computer
-version=0.1
-description=
+++ /dev/null
-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)
+++ /dev/null
-[Operator]
-name=Entry
-version=0.1
-description=
-
-[Constants]
-name=Entry
-version=0.1
-description=
-
-[Keyboard]
-name=Entry
-version=0.1
-description=
+++ /dev/null
-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)
+++ /dev/null
-[Operator]
-name=Trigonometry
-version=0.1
-description=
-
-[Constants]
-name=Trigonometry
-version=0.1
-description=
-
-[Keyboard]
-name=Trigonometry
-version=0.1
-description=
+++ /dev/null
-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)
-
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/usr/bin/env python
+++ /dev/null
-#!/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()
+++ /dev/null
-#!/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)
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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
-
+++ /dev/null
-#!/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)
+++ /dev/null
-#!/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 = {
- """: '"',
- " ": " ",
- "'": "'",
-}
-
-
-_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
+++ /dev/null
-#!/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), ))
+++ /dev/null
-#!/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
+++ /dev/null
-#!/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))
+++ /dev/null
-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")
+++ /dev/null
-#!/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
-
+++ /dev/null
-#!/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_()
+++ /dev/null
-#!/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()
+++ /dev/null
-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
-
+++ /dev/null
-#!/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)
+++ /dev/null
-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")
+++ /dev/null
-#!/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]
+++ /dev/null
-#!/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])
+++ /dev/null
-[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;
+++ /dev/null
-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
+++ /dev/null
-#!/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)
--- /dev/null
+#!/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)