From 41a369309370bc2bc4f85dcc00eb76f6dcc83e69 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Tue, 17 Nov 2009 18:49:55 -0600 Subject: [PATCH 01/16] Minor tweaks --- src/constants.py | 2 +- src/hildonize.py | 14 ++++++++++++++ support/builddeb.py | 6 +++--- support/py2deb.py | 6 ++++-- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/constants.py b/src/constants.py index 665504f..764b3c8 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,7 +3,7 @@ import os __pretty_app_name__ = "Gonvert" __app_name__ = "gonvert" __version__ = "0.9.0" -__build__ = 4 +__build__ = 5 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ diff --git a/src/hildonize.py b/src/hildonize.py index 84e3c7a..9c83b77 100755 --- a/src/hildonize.py +++ b/src/hildonize.py @@ -184,6 +184,20 @@ 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) diff --git a/support/builddeb.py b/support/builddeb.py index 32b7114..e6d0828 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -14,8 +14,8 @@ A conversion utility that allows conversion between many units like CGS, Ancient . Homepage: http://www.unihedron.com/projects/gonvert/index.php """ -__author__ = "Ed Page (Maemo Porter)" -__email__ = "eopage@byu.net" +__author__ = "Anthony Tekatch" +__email__ = "anthony@unihedron.com" __version__ = constants.__version__ __build__ = constants.__build__ __changelog__ = """ @@ -174,7 +174,7 @@ def build_package(distribution): p.prettyName = constants.__pretty_app_name__ p.description = __description__ p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=Gonvert" - p.upgradeDescription = __changelog__.split("\n\n", 1)[0] + #p.upgradeDescription = __changelog__.split("\n\n", 1)[0] p.author = __author__ p.mail = __email__ p.license = "gpl" diff --git a/support/py2deb.py b/support/py2deb.py index 6018f91..59426c7 100644 --- a/support/py2deb.py +++ b/support/py2deb.py @@ -168,7 +168,8 @@ def py2changes(params): fileHandle = open('/tmp/py2deb2.tmp', 'w') fileHandle.write('#!/bin/sh\n') fileHandle.write("cd " +os.getcwd()+ "\n") - fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.changes\n" % params) + # 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() @@ -254,7 +255,8 @@ def py2dsc(TEMP, name, version, depends, author, mail, arch): try: fileHandle.write('#!/bin/sh\n') fileHandle.write("cd " + os.getcwd() + "\n") - fileHandle.write("gpg --local-user %(mail)s --clearsign %(TEMP)s/%(name)s_%(version)s.dsc\n" % locals()) + # 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() -- 1.7.9.5 From e37a5079b925dff50c777623f8803a5baff8dbcc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 19 Nov 2009 20:12:18 -0600 Subject: [PATCH 02/16] Email fix, version bump --- data/pixmaps/gonvert.png | Bin 1332 -> 1283 bytes src/constants.py | 2 +- src/gonvert_glade.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/pixmaps/gonvert.png b/data/pixmaps/gonvert.png index 20ddeaa2f5e2f5ba226ef006e7cf62a822644a34..acd383785abc118bbf91505e792c6741a7e5a536 100644 GIT binary patch delta 1203 zcmV;k1Wfz13WExeV1E+?3n(4Y_sReO1cOOLK~!ko-I+~jTvZguf9JmU=HtyIwN58( znwmyQt1gP)6;UWyt()#b(JmFSAnKxsAlQYVZVRFnkz(Dr6k6JXx^<~!A)x_FKT}(# z?UZJ|GVjg1$Hh#nolM)wbdr~N;KJk0yZ4@R&-tJKxo1RG0e=vY=EEuxRTThHRgB}V zD&+w%1{($<;(Du3`TvNEn0tP-A?=b+xd2#&iVJF(U~#_CJUUegfSZ6 z4=ylZbH?Mxmwz_0J(s%Y0zaI&$p^m|s5WdoSVj!(#)9+ak`bKA51dfd6Z%ZBUjoykWahhl4enh zJe=_u&bX9Ijre)MV$elq#zH-!7(h3ivgAB)an3S%Gk;{qx@6aj{933}Y&7XTb`Q)( zaOg~p=hi0h9l7TMr%DYDovooUoPG-f&VrXG3n9lY2fVTA?p}x#4xgXnTn##{6I(45 zC>)rsQdihJk|fdLaI2B>{e?P5E(Wp0{=ZD@4v*gvvc4niT<7v?-s8zN4!#j0;8ZE% z=#`KYg@1s+603;2acfsOC*uh7B~iJdXlGuBq(V)WGYfSWyA^GfqVtNjOG>d5H5hGR zz8rvxEE9RKZcC;9`zsp)I`hsQ?|cm?;mg^#}B(Z-tc+a1{(hZpG6H_+$jAb+Do@694tJmj}lVVG8QEP_aoOJ3hd zuYVpWdN`l2L;h%$;e(dwqZ{b<)@dgN1}uaEH{8B?z+fEuHRN&<=c^nt)4G0cY7l)` zM8_;Tq-Z`ut~y9D(YHN~h~d76bL-hm`8Zz;A(vZ5j0f0{uff(3JcY7@gbsb#w;veL zTz>=m=5^#u5}8gRmpsg577j-=_7}jn*k`M#Zy}+`g#^^y&Z&5*+A4RI(FW1(Sx8x^ znkI1nN@4y?AVqg*CI1bs2&w$@$6Ms#b zbK3QFW4jKD^fpJ#%D_NR2PbAh6&o7{dN!a<-}pSx%f6({wK_o}F@7vHU6^7M z2~a3PMkt}vnd!VY?;aPOf=mlc%e1fcBqy1?_wKpp+;jft<9}vQRaBM!{(c3P1o!v% z>y+L%lo#VHDV{RI5fR<9cOQr>DgNo_UqEfYh?i9ELaS0P*(MhNt8k$ZG8RTG268dx zwl7;e!}jj`c=wT39!Pso4Z?xZ5}yrS<=9A`z-p`ph-u2qsKN7XK6@U^@?17W!pOAu z#fs&~$T+(Pa(@gL!Wa$kTc#P%mG=1Rh1G0qZMv&~FV9@(z29;aD>fDb3}UF|x7IGN zOL>4D-7UP-o@ORF3&GPckai85R;KDcfrJrWZqIP^?@~-47N-Wd4lAn}r(SyU{v^$w zM?Quya;3%t*_2Cb(#%T8gK3ZUw99zD5{CzFRn}tAS$}56TsdMifLd9x)PV~VmVxUb zn^q+27V>?r%xDGfR8tnr17i^!JX7MCu@c=-euzRRTSz$**~rmX-Al9iN-66&Ro|asZf%I z%tH0WZeeO3no_iuniShtg3b!&lV&K$0)e;@@tahloq23u5gbKwF6M9sYQjsO2+$Ub z8T8;vV?b{j)>s(xj9h?&gaX}t_u3>1f-Sd zoiXIHhx}S?3{#4BM-T~e(Q8=IYn%s0J)C_jkl(9qc#9?a;2OHIdfG{WW(%RfHMe0N zFc^nMv0O^xeAjDVmLte;i~q(Xc&@h~b{ux%t}+`ZyoAA(yH~ z^nV1{53j(w5Ilu(2MHY-vTq|Wpsf|`>sOI8No1%Ax#(dAGH@uOvNH$1#Xem`eG3Ui z&L^Pk&YX%H)k@5YIx9q5$DqkV(M$sOuO`eNDV*Mmrr zE#<>L?tymfQw4Nw30)PU50$a6T%R)RhOc>3HtVqg~JI1ZV#PqSa=C7MjGsnyqwZ5$Mt+Zr*80|Rq9I5E?@*w`>IX9H^V zjn4yf+1JeA0gwCo-AZ0b7TDrZcbwVD-108EEHEI4#J4Rvm<0u^$!0FQ2xbjoZ8hZ0RIAwruAY5X@v3s O0000", "Ed Page "]) + dlg.set_authors(["Anthony Tekatch ", "Ed Page (Blame him for the most recent bugs)"]) dlg.run() dlg.destroy() -- 1.7.9.5 From 26a0319cee778b4458d5ff3a29547263b5259abc Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 7 Dec 2009 19:10:28 -0600 Subject: [PATCH 03/16] Fixed a bug with fremantle and contexts plus logging device to file --- src/constants.py | 2 +- src/gonvert.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/constants.py b/src/constants.py index 85247c0..5752095 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,7 +3,7 @@ import os __pretty_app_name__ = "Gonvert" __app_name__ = "gonvert" __version__ = "0.9.0" -__build__ = 6 +__build__ = 7 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ diff --git a/src/gonvert.py b/src/gonvert.py index 6a535aa..17672fc 100755 --- a/src/gonvert.py +++ b/src/gonvert.py @@ -21,6 +21,9 @@ except OSError, e: logging.basicConfig(level=logging.DEBUG, filename=constants._user_logpath_) _moduleLogger.info("gonvert %s-%s" % (constants.__version__, constants.__build__)) +_moduleLogger.info("OS: %s" % (os.uname()[0], )) +_moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:]) +_moduleLogger.info("Hostname: %s" % os.uname()[1]) gonvert_glade.run_gonvert() -- 1.7.9.5 From 5957dbcb97f3a2d05fbd2bf59f316911b1b16fe5 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 7 Dec 2009 21:47:41 -0600 Subject: [PATCH 04/16] Adding a webpage --- www/download.html | 37 +++++++++++++++++++++++++++++++++ www/images/gonvert-n900.jpg | Bin 0 -> 25022 bytes www/index.html | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 www/download.html create mode 100644 www/images/gonvert-n900.jpg create mode 100644 www/index.html diff --git a/www/download.html b/www/download.html new file mode 100644 index 0000000..1a91713 --- /dev/null +++ b/www/download.html @@ -0,0 +1,37 @@ + + + Gonvert + + +

+ Gonvert +

+

Unit Converter

+

[About] [Download] +

Download

+ +

Packages

+

Maemo 5 and Later: Go to the application installer, enable Extras, and download Gonvert (Note: technically its still in extras-testing because people haven't been reviewing it)

+ +

Maemo 4.1 and Earlier: Add the Extras Repository and check the App Manager for DialCentral

+ +

Linux: Browse for .deb files

+ +

Development

+

Source

+

For the most up to date version check out svn. +

+

Requires

+
    +
  • PyGTK / Glade
  • +
  • Python bindings for hildon, and osso
  • +
+ +

Bugs

+ +

Discuss your issue on Maemo.Org Talk

+ +

File a bug report against Extras->ejpi

+

View existing bug reports

+ + diff --git a/www/images/gonvert-n900.jpg b/www/images/gonvert-n900.jpg new file mode 100644 index 0000000000000000000000000000000000000000..317cb7d0545f33a174c29483915911c62bed479c GIT binary patch literal 25022 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⪇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ɝ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 K2Qd5R`2PXy&=qX} literal 0 HcmV?d00001 diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..b1e6616 --- /dev/null +++ b/www/index.html @@ -0,0 +1,48 @@ + + + Gonvert + + +

+ Gonvert +

+

Unit Converter

+

[About] [Download] + +

Documentation

+ +

About

+

+ Gonvert is a thorough unit converter originally developed by Anthony Tekatch for Linux and ported to Maemo by Ed Page +

+ +

Gonvert has been tested on Ubuntu 9.05, Maemo 5, and Maemo 4.1

+ +

Gonvert is Free Software and available under the GPLv2.1. + +

Features

+

Gonvert v0.9.0-6 (See t.m.o Thread)

+ +
    +
  • 51+ categories with 972+ units
  • +
  • Everything is updated as you type
  • +
  • Unit Search
  • +
  • Keyboard shortcuts: +
      +
    • Ctrl+enter to toggle fullscreen
    • +
    • Ctrl+l to copy debug logs to the clipboard
    • +
    • Ctrl+f show/hide the search box
    • +
    • Ctrl+p previous search result
    • +
    • Ctrl+n next search result
    • +
    +
  • +
+ +

Sample Screenshots

+ +
+

Screenshot of Gonvert v0.9.0 on an n900

+

Gonvert on an n900

+
+ + -- 1.7.9.5 From 04d028d096855e416bef1a205c9febd4c74e30c1 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 7 Dec 2009 22:23:04 -0600 Subject: [PATCH 05/16] Adding upload to extras support --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index cfefc13..00b1947 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,11 @@ package: $(OBJ) cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/mer cd $(BUILD_PATH)/mer ; python builddeb.py mer +upload: package + dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes + dput diablo-extras-builder $(BUILD_PATH)/diablo/$(PROJECT_NAME)*.changes + dput chinook-extras-builder $(BUILD_PATH)/chinook/$(PROJECT_NAME)*.changes + lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) -- 1.7.9.5 From 5d9c6ba29dde92ef01ca31f861ded43f9428912d Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 11 Dec 2009 23:26:02 -0600 Subject: [PATCH 06/16] Major version bump --- src/constants.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants.py b/src/constants.py index 5752095..66036f8 100644 --- a/src/constants.py +++ b/src/constants.py @@ -2,8 +2,8 @@ import os __pretty_app_name__ = "Gonvert" __app_name__ = "gonvert" -__version__ = "0.9.0" -__build__ = 7 +__version__ = "0.9.1" +__build__ = 8 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ -- 1.7.9.5 From 83aa2416ac8fb85cc1dabe1529142451332ff3cd Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 11 Dec 2009 23:51:22 -0600 Subject: [PATCH 07/16] Fixing a font inconsistency bug --- src/gonvert_glade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index 5326717..0016f2f 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -635,7 +635,7 @@ class Gonvert(object): ) selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] - self._categorySelectionButton.set_label(selectedCategoryName) + self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) self._categoryView.set_cursor(newIndex, self._categoryColumn, False) self._categoryView.grab_focus() except Exception: -- 1.7.9.5 From a2b497580d162a10819615ada95470b5ca6b4469 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Fri, 11 Dec 2009 23:51:53 -0600 Subject: [PATCH 08/16] Updating changelist --- support/builddeb.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/support/builddeb.py b/support/builddeb.py index e6d0828..c6e2d49 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -19,6 +19,9 @@ __email__ = "anthony@unihedron.com" __version__ = constants.__version__ __build__ = constants.__build__ __changelog__ = """ +0.9.1 +* Bug fix: font of the category button was inconsistent + 0.9.0 * Added Radioactivity and Radiation dose categories. * Aligning the numbers by their decimal place -- 1.7.9.5 From 39a38e609c3a9509c05e195aef62e361299349d6 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 11:05:03 -0600 Subject: [PATCH 09/16] Adding a fluid dram --- src/unit_data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/unit_data.py b/src/unit_data.py index 65400e3..649f52f 100644 --- a/src/unit_data.py +++ b/src/unit_data.py @@ -1973,6 +1973,8 @@ _(u"Electrical Voltage"): {".base_unit": _(u"volt"), [(converters.m, 10*10*1.0), "dl", ''], _(u"mil"): [(converters.m, 1.0), '', _(u"Equal to one thousandth of a liter syn: milliliter, millilitre, ml, cubic centimeter, cubic centimeter, cc")], + _(u"fluid dram"): + [(converters.m, 2*3*4.92892159375/8), '', _(u"Used in Pharmaceutical")], _(u"minim"): [(converters.m, 2*3*4.92892159375/480), '', _(u"Used in Pharmaceutical to represent one drop. 1/60 fluid dram or 1/480 fluid ounce. A U.S. minim is about 0.003760 in\xb3 or 61.610 \xb5l. The British minim is about 0.003612 in\xb3 or 59.194 \xb5l. Origin of the word is from the Latin minimus, or small.")], }, -- 1.7.9.5 From bb8e13c683c105ddf66e22ca36ba243999f7d629 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 11:05:12 -0600 Subject: [PATCH 10/16] Adding support for building .deb --- Makefile | 3 +++ support/builddeb.py | 2 +- support/py2deb.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 00b1947..8d93fba 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,9 @@ package: $(OBJ) mkdir -p $(BUILD_PATH)/mer cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/mer cd $(BUILD_PATH)/mer ; python builddeb.py mer + mkdir -p $(BUILD_PATH)/debian + cp -R $(BUILD_PATH)/generic/* $(BUILD_PATH)/debian + cd $(BUILD_PATH)/debian ; python builddeb.py debian upload: package dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes diff --git a/support/builddeb.py b/support/builddeb.py index c6e2d49..4402c2e 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -244,7 +244,7 @@ def build_package(distribution): print p.generate( version="%s-%s" % (__version__, __build__), changelog=__changelog__, - build=False, + build=True, tar=True, changes=True, dsc=True, diff --git a/support/py2deb.py b/support/py2deb.py index 59426c7..d1eac8a 100644 --- a/support/py2deb.py +++ b/support/py2deb.py @@ -192,7 +192,7 @@ def py2changes(params): shutil.move(l[0],tar) ret.append(tar) - l=glob("%(TEMP)s/%(name)s*.changes" % params) + 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]) -- 1.7.9.5 From 7994539b8cf4aca338a372c9a8fedff3776a0c72 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 11:34:20 -0600 Subject: [PATCH 11/16] Arrows in unit view and unit entry always browse the units --- src/gonvert_glade.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index 0016f2f..bc1c291 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -227,7 +227,9 @@ class Gonvert(object): self._findEntry.connect("changed", self._on_findEntry_changed) self._previousUnitValue.connect("changed", self._on_previous_unit_value_changed) self._unitValue.connect("changed", self._on_unit_value_changed) + self._unitValue.connect("key-press-event", self._on_browse_key_press) self._unitsView.connect("cursor-changed", self._on_click_unit) + self._unitsView.connect("key-press-event", self._on_browse_key_press) if hildonize.GTK_MENU_USED: widgets.get_widget("aboutMenuItem").connect("activate", self._on_about_clicked) widgets.get_widget("exitMenuItem").connect("activate", self._on_user_exit) @@ -568,6 +570,21 @@ class Gonvert(object): except Exception, e: _moduleLogger.exception("_on_key_press") + def _on_browse_key_press(self, widget, event, *args): + try: + if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up: + index, column = self._unitsView.get_cursor() + newIndex = max(index[0]-1, 0) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down: + index, column = self._unitsView.get_cursor() + newIndex = min(index[0]+1, len(self._unitModel)-1) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + except Exception, e: + _moduleLogger.exception("_on_key_press") + def _on_window_state_change(self, widget, event, *args): """ @note Hildon specific -- 1.7.9.5 From 91909712ac1ea9c8c271bc72757f04f2eec34106 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 11:44:37 -0600 Subject: [PATCH 12/16] Minor cleanup using reusable exception logger and moving constants out of main file --- src/constants.py | 3 + src/gonvert_glade.py | 383 ++++++++++++------------- src/gtk_toolbox.py | 772 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 956 insertions(+), 202 deletions(-) create mode 100644 src/gtk_toolbox.py diff --git a/src/constants.py b/src/constants.py index 66036f8..675bf08 100644 --- a/src/constants.py +++ b/src/constants.py @@ -8,3 +8,6 @@ __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ _user_logpath_ = "%s/gonvert.log" % _data_path_ + +PROFILE_STARTUP = False +FORCE_HILDON_LIKE = False diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index bc1c291..4882b9b 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -16,6 +16,7 @@ import gtk.gdk import constants import hildonize +import gtk_toolbox import unit_data try: @@ -28,8 +29,6 @@ else: _moduleLogger = logging.getLogger("gonvert_glade") -PROFILE_STARTUP = False -FORCE_HILDON_LIKE = False if gettext is not None: gettext.bindtextdomain('gonvert', '/usr/share/locale') @@ -144,7 +143,7 @@ class Gonvert(object): self._unitsNameRenderer = gtk.CellRendererText() self._unitsNameRenderer.set_property("scale", 0.75) - if FORCE_HILDON_LIKE: + if constants.FORCE_HILDON_LIKE: self._unitsNameRenderer.set_property("ellipsize", pango.ELLIPSIZE_END) self._unitsNameRenderer.set_property("width-chars", 5) self._unitNameColumn = gtk.TreeViewColumn(_('Name'), self._unitsNameRenderer) @@ -241,7 +240,7 @@ class Gonvert(object): assert scrollingWidget is not None, scrollingWidgetName hildonize.hildonize_scrollwindow_with_viewport(scrollingWidget) - if hildonize.IS_HILDON_SUPPORTED or FORCE_HILDON_LIKE: + if hildonize.IS_HILDON_SUPPORTED or constants.FORCE_HILDON_LIKE: self._categoryView.get_parent().hide() self._unitsView.set_headers_visible(False) self._previousUnitName.get_parent().hide() @@ -476,7 +475,7 @@ class Gonvert(object): nameLength = max(nameLength, len(key)) self._sortedUnitModel.sort_column_changed() - if FORCE_HILDON_LIKE: + if constants.FORCE_HILDON_LIKE: maxCatCharWidth = int(nameLength * 0.75) maxCharWidth = int(len("nibble | hexit | quadbit") * 0.75) charWidth = min(maxCatCharWidth, maxCharWidth) @@ -542,248 +541,227 @@ class Gonvert(object): value = float(userEntry) return value + @gtk_toolbox.log_exception(_moduleLogger) def _on_key_press(self, widget, event, *args): """ @note Hildon specific """ RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter) - try: - if ( - event.keyval == gtk.keysyms.F6 or - event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK - ): - if self._isFullScreen: - self._mainWindow.unfullscreen() - else: - self._mainWindow.fullscreen() - elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK: - self._toggle_find() - elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK: - self._find_previous() - elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK: - self._find_next() - elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK: - with open(constants._user_logpath_, "r") as f: - logLines = f.xreadlines() - log = "".join(logLines) - self._clipboard.set_text(str(log)) - except Exception, e: - _moduleLogger.exception("_on_key_press") + if ( + event.keyval == gtk.keysyms.F6 or + event.keyval in RETURN_TYPES and event.get_state() & gtk.gdk.CONTROL_MASK + ): + if self._isFullScreen: + self._mainWindow.unfullscreen() + else: + self._mainWindow.fullscreen() + elif event.keyval == gtk.keysyms.f and event.get_state() & gtk.gdk.CONTROL_MASK: + self._toggle_find() + elif event.keyval == gtk.keysyms.p and event.get_state() & gtk.gdk.CONTROL_MASK: + self._find_previous() + elif event.keyval == gtk.keysyms.n and event.get_state() & gtk.gdk.CONTROL_MASK: + self._find_next() + elif event.keyval == ord("l") and event.get_state() & gtk.gdk.CONTROL_MASK: + with open(constants._user_logpath_, "r") as f: + logLines = f.xreadlines() + log = "".join(logLines) + self._clipboard.set_text(str(log)) + @gtk_toolbox.log_exception(_moduleLogger) def _on_browse_key_press(self, widget, event, *args): - try: - if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up: - index, column = self._unitsView.get_cursor() - newIndex = max(index[0]-1, 0) - self._unitsView.set_cursor((newIndex, ), column, True) - return True # override default behavior - elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down: - index, column = self._unitsView.get_cursor() - newIndex = min(index[0]+1, len(self._unitModel)-1) - self._unitsView.set_cursor((newIndex, ), column, True) - return True # override default behavior - except Exception, e: - _moduleLogger.exception("_on_key_press") - + if event.keyval == gtk.keysyms.uparrow or event.keyval == gtk.keysyms.Up: + index, column = self._unitsView.get_cursor() + newIndex = max(index[0]-1, 0) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + elif event.keyval == gtk.keysyms.downarrow or event.keyval == gtk.keysyms.Down: + index, column = self._unitsView.get_cursor() + newIndex = min(index[0]+1, len(self._unitModel)-1) + self._unitsView.set_cursor((newIndex, ), column, True) + return True # override default behavior + + @gtk_toolbox.log_exception(_moduleLogger) def _on_window_state_change(self, widget, event, *args): """ @note Hildon specific """ - try: - if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: - self._isFullScreen = True - else: - self._isFullScreen = False - except Exception, e: - _moduleLogger.exception("_on_window_state_change") + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self._isFullScreen = True + else: + self._isFullScreen = False + @gtk_toolbox.log_exception(_moduleLogger) def _on_findEntry_changed(self, *args): """ Clear out find results since the user wants to look for something new """ - try: - self._clear_find() - except Exception: - _moduleLogger.exception("_on_findEntry_changed") + self._clear_find() + @gtk_toolbox.log_exception(_moduleLogger) def _on_find_activate(self, *args): - try: - self._find_next() - self._findButton.grab_focus() - except Exception: - _moduleLogger.exception("_on_find_activate") + self._find_next() + self._findButton.grab_focus() + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_unit_column(self, col): """ Sort the contents of the col when the user clicks on the title. """ - try: - #Determine which column requires sorting - columns = self._get_column_sort_stuff() - for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns): - if col is maybeCol: - direction = getattr(self, directionName) - gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING - - # cause a sort - self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection) - - # set the visual for sorting - col.set_sort_indicator(True) - col.set_sort_order(not direction) - - setattr(self, directionName, not direction) - break - else: - maybeCol.set_sort_indicator(False) + #Determine which column requires sorting + columns = self._get_column_sort_stuff() + for columnIndex, (maybeCol, directionName, col_cmp) in enumerate(columns): + if col is maybeCol: + direction = getattr(self, directionName) + gtkDirection = gtk.SORT_ASCENDING if direction else gtk.SORT_DESCENDING + + # cause a sort + self._sortedUnitModel.set_sort_column_id(columnIndex, gtkDirection) + + # set the visual for sorting + col.set_sort_indicator(True) + col.set_sort_order(not direction) + + setattr(self, directionName, not direction) + break else: - assert False, "Unknown column: %s" % (col.get_title(), ) - except Exception: - _moduleLogger.exception("_on_click_unit_column") + maybeCol.set_sort_indicator(False) + else: + assert False, "Unknown column: %s" % (col.get_title(), ) + @gtk_toolbox.log_exception(_moduleLogger) def _on_category_selector_clicked(self, *args): - try: - currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName) - newIndex = hildonize.touch_selector( - self._mainWindow, - "Categories", - unit_data.UNIT_CATEGORIES, - currenntIndex, - ) + currenntIndex = unit_data.UNIT_CATEGORIES.index(self._selectedCategoryName) + newIndex = hildonize.touch_selector( + self._mainWindow, + "Categories", + unit_data.UNIT_CATEGORIES, + currenntIndex, + ) - selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] - self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) - self._categoryView.set_cursor(newIndex, self._categoryColumn, False) - self._categoryView.grab_focus() - except Exception: - _moduleLogger.exception("_on_category_selector_clicked") + selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] + self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) + self._categoryView.set_cursor(newIndex, self._categoryColumn, False) + self._categoryView.grab_focus() + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_category(self, *args): - try: - selected, iter = self._categoryView.get_selection().get_selected() - if iter is None: - # User is typing in an invalid string, not selecting any category - return - selectedCategory = self._categoryModel.get_value(iter, 0) - self._switch_category(selectedCategory) - except Exception: - _moduleLogger.exception("_on_click_category") + selected, iter = self._categoryView.get_selection().get_selected() + if iter is None: + # User is typing in an invalid string, not selecting any category + return + selectedCategory = self._categoryModel.get_value(iter, 0) + self._switch_category(selectedCategory) + @gtk_toolbox.log_exception(_moduleLogger) def _on_click_unit(self, *args): - try: - selected, iter = self._unitsView.get_selection().get_selected() - selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX) - unit_spec = self._unitDataInCategory[selected_unit] + selected, iter = self._unitsView.get_selection().get_selected() + selected_unit = selected.get_value(iter, self.UNITS_NAME_IDX) + unit_spec = self._unitDataInCategory[selected_unit] - showSymbol = False + showSymbol = False - if self._unitName.get_text() != selected_unit: - self._previousUnitName.set_text(self._unitName.get_text()) - self._previousUnitValue.set_text(self._unitValue.get_text()) - self._previousUnitSymbol.set_text(self._unitSymbol.get_text()) - if self._unitSymbol.get_text(): - showSymbol = True - - self._unitName.set_text(selected_unit) - self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX)) - buffer = self._unitDescription.get_buffer() - buffer.set_text(unit_spec[2]) - self._unitSymbol.set_text(unit_spec[1]) # put units into label text - if unit_spec[1]: + if self._unitName.get_text() != selected_unit: + self._previousUnitName.set_text(self._unitName.get_text()) + self._previousUnitValue.set_text(self._unitValue.get_text()) + self._previousUnitSymbol.set_text(self._unitSymbol.get_text()) + if self._unitSymbol.get_text(): showSymbol = True - else: - showSymbol = False - if showSymbol: - self._unitSymbol.show() - self._previousUnitSymbol.show() + self._unitName.set_text(selected_unit) + self._unitValue.set_text(selected.get_value(iter, self.UNITS_VALUE_IDX)) + buffer = self._unitDescription.get_buffer() + buffer.set_text(unit_spec[2]) + self._unitSymbol.set_text(unit_spec[1]) # put units into label text + if unit_spec[1]: + showSymbol = True + else: + showSymbol = False + + if showSymbol: + self._unitSymbol.show() + self._previousUnitSymbol.show() + else: + self._unitSymbol.hide() + self._previousUnitSymbol.hide() + + if self._unitValue.get_text() == '': + if self._selectedCategoryName == "Computer Numbers": + self._unitValue.set_text("0") else: - self._unitSymbol.hide() - self._previousUnitSymbol.hide() - - if self._unitValue.get_text() == '': - if self._selectedCategoryName == "Computer Numbers": - self._unitValue.set_text("0") - else: - self._unitValue.set_text("0.0") - - self._defaultUnitForCategory[self._selectedCategoryName] = [ - self._unitName.get_text(), self._previousUnitName.get_text() - ] - - # select the text so user can start typing right away - self._unitValue.grab_focus() - self._unitValue.select_region(0, -1) - except Exception: - _moduleLogger.exception("_on_click_unit") + self._unitValue.set_text("0.0") + + self._defaultUnitForCategory[self._selectedCategoryName] = [ + self._unitName.get_text(), self._previousUnitName.get_text() + ] + + # select the text so user can start typing right away + self._unitValue.grab_focus() + self._unitValue.select_region(0, -1) + @gtk_toolbox.log_exception(_moduleLogger) def _on_unit_value_changed(self, *args): - try: - if self._unitName.get_text() == '': - return - if not self._unitValue.is_focus(): - return - - #retrieve the conversion function and value from the selected unit - value = self._sanitize_value(self._unitValue.get_text()) - func, arg = self._unitDataInCategory[self._unitName.get_text()][0] - base = func.to_base(value, arg) - - #point to the first row - for row in self._unitModel: - func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] - newValue = func.from_base(base, arg) - - newValueDisplay = str(newValue) - integerDisplay, fractionalDisplay = split_number(newValue) - - row[self.UNITS_VALUE_IDX] = newValueDisplay - row[self.UNITS_INTEGER_IDX] = integerDisplay - row[self.UNITS_FRACTION_IDX] = fractionalDisplay - - # Update the secondary unit entry - if self._previousUnitName.get_text() != '': - func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] - self._previousUnitValue.set_text(str(func.from_base(base, arg, ))) - - self._sortedUnitModel.sort_column_changed() - self._refresh_columns() - except Exception: - _moduleLogger.exception("_on_unit_value_changed") + if self._unitName.get_text() == '': + return + if not self._unitValue.is_focus(): + return - def _on_previous_unit_value_changed(self, *args): - try: - if self._previousUnitName.get_text() == '': - return - if not self._previousUnitValue.is_focus(): - return + #retrieve the conversion function and value from the selected unit + value = self._sanitize_value(self._unitValue.get_text()) + func, arg = self._unitDataInCategory[self._unitName.get_text()][0] + base = func.to_base(value, arg) - #retrieve the conversion function and value from the selected unit - value = self._sanitize_value(self._previousUnitValue.get_text()) + #point to the first row + for row in self._unitModel: + func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] + newValue = func.from_base(base, arg) + + newValueDisplay = str(newValue) + integerDisplay, fractionalDisplay = split_number(newValue) + + row[self.UNITS_VALUE_IDX] = newValueDisplay + row[self.UNITS_INTEGER_IDX] = integerDisplay + row[self.UNITS_FRACTION_IDX] = fractionalDisplay + + # Update the secondary unit entry + if self._previousUnitName.get_text() != '': func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] - base = func.to_base(value, arg) + self._previousUnitValue.set_text(str(func.from_base(base, arg, ))) + + self._sortedUnitModel.sort_column_changed() + self._refresh_columns() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_previous_unit_value_changed(self, *args): + if self._previousUnitName.get_text() == '': + return + if not self._previousUnitValue.is_focus(): + return - #point to the first row - for row in self._unitModel: - func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] - newValue = func.from_base(base, arg) + #retrieve the conversion function and value from the selected unit + value = self._sanitize_value(self._previousUnitValue.get_text()) + func, arg = self._unitDataInCategory[self._previousUnitName.get_text()][0] + base = func.to_base(value, arg) - newValueDisplay = str(newValue) - integerDisplay, fractionalDisplay = split_number(newValue) + #point to the first row + for row in self._unitModel: + func, arg = self._unitDataInCategory[row[self.UNITS_NAME_IDX]][0] + newValue = func.from_base(base, arg) - row[self.UNITS_VALUE_IDX] = newValueDisplay - row[self.UNITS_INTEGER_IDX] = integerDisplay - row[self.UNITS_FRACTION_IDX] = fractionalDisplay + newValueDisplay = str(newValue) + integerDisplay, fractionalDisplay = split_number(newValue) - # Update the primary unit entry - func, arg = self._unitDataInCategory[self._unitName.get_text()][0] - self._unitValue.set_text(str(func.from_base(base, arg, ))) + row[self.UNITS_VALUE_IDX] = newValueDisplay + row[self.UNITS_INTEGER_IDX] = integerDisplay + row[self.UNITS_FRACTION_IDX] = fractionalDisplay - self._sortedUnitModel.sort_column_changed() - self._refresh_columns() - except Exception: - _moduleLogger.exception("_on_previous_unit_value_changed") + # Update the primary unit entry + func, arg = self._unitDataInCategory[self._unitName.get_text()][0] + self._unitValue.set_text(str(func.from_base(base, arg, ))) + + self._sortedUnitModel.sort_column_changed() + self._refresh_columns() + @gtk_toolbox.log_exception(_moduleLogger) def _on_about_clicked(self, a): dlg = gtk.AboutDialog() dlg.set_name(constants.__pretty_app_name__) @@ -795,11 +773,12 @@ class Gonvert(object): dlg.run() dlg.destroy() + @gtk_toolbox.log_exception(_moduleLogger) def _on_user_exit(self, *args): try: self._save_settings() except Exception: - _moduleLogger.exception("_on_user_exit") + pass finally: gtk.main_quit() @@ -809,7 +788,7 @@ def run_gonvert(): if hildonize.IS_HILDON_SUPPORTED: gtk.set_application_name(constants.__pretty_app_name__) handle = Gonvert() - if not PROFILE_STARTUP: + if not constants.PROFILE_STARTUP: gtk.main() diff --git a/src/gtk_toolbox.py b/src/gtk_toolbox.py new file mode 100644 index 0000000..8c2947d --- /dev/null +++ b/src/gtk_toolbox.py @@ -0,0 +1,772 @@ +#!/usr/bin/python + +from __future__ import with_statement + +import os +import errno +import sys +import time +import itertools +import functools +import contextlib +import logging +import threading +import Queue + +import gobject +import gtk + + +_moduleLogger = logging.getLogger("gtk_toolbox") + + +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + +@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) + + +@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 + + +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 asynchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + def execute(allArgs): + args, kwargs = allArgs + with gtk_lock(): + original_func(*args, **kwargs) + return False + + @functools.wraps(original_func) + def delayed_func(*args, **kwargs): + gobject.idle_add(execute, (args, kwargs)) + + return delayed_func + + +def synchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + @functools.wraps(original_func) + def immediate_func(*args, **kwargs): + with gtk_lock(): + return original_func(*args, **kwargs) + + return immediate_func + + +def autostart(func): + """ + >>> @autostart + ... def grep_sink(pattern): + ... print "Looking for %s" % pattern + ... while True: + ... line = yield + ... if pattern in line: + ... print line, + >>> g = grep_sink("python") + Looking for python + >>> g.send("Yeah but no but yeah but no") + >>> g.send("A series of tubes") + >>> g.send("python generators rock!") + python generators rock! + >>> g.close() + """ + + @functools.wraps(func) + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + + return start + + +@autostart +def printer_sink(format = "%s"): + """ + >>> pr = printer_sink("%r") + >>> pr.send("Hello") + 'Hello' + >>> pr.send("5") + '5' + >>> pr.send(5) + 5 + >>> p = printer_sink() + >>> p.send("Hello") + Hello + >>> p.send("World") + World + >>> # p.throw(RuntimeError, "Goodbye") + >>> # p.send("Meh") + >>> # p.close() + """ + while True: + item = yield + print format % (item, ) + + +@autostart +def null_sink(): + """ + Good for uses like with cochain to pick up any slack + """ + while True: + item = yield + + +@autostart +def comap(function, target): + """ + >>> p = printer_sink() + >>> cm = comap(lambda x: x+1, p) + >>> cm.send((0, )) + 1 + >>> cm.send((1.0, )) + 2.0 + >>> cm.send((-2, )) + -1 + """ + while True: + try: + item = yield + mappedItem = function(*item) + target.send(mappedItem) + except Exception, e: + _moduleLogger.exception("Forwarding exception!") + target.throw(e.__class__, str(e)) + + +def _flush_queue(queue): + while not queue.empty(): + yield queue.get() + + +@autostart +def queue_sink(queue): + """ + >>> q = Queue.Queue() + >>> qs = queue_sink(q) + >>> qs.send("Hello") + >>> qs.send("World") + >>> qs.throw(RuntimeError, "Goodbye") + >>> qs.send("Meh") + >>> qs.close() + >>> print [i for i in _flush_queue(q)] + [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] + """ + while True: + try: + item = yield + queue.put((None, item)) + except Exception, e: + queue.put((e.__class__, str(e))) + except GeneratorExit: + queue.put((GeneratorExit, None)) + raise + + +def decode_item(item, target): + if item[0] is None: + target.send(item[1]) + return False + elif item[0] is GeneratorExit: + target.close() + return True + else: + target.throw(item[0], item[1]) + return False + + +def nonqueue_source(queue, target): + isDone = False + while not isDone: + item = queue.get() + isDone = decode_item(item, target) + while not queue.empty(): + queue.get_nowait() + + +def threaded_stage(target, thread_factory = threading.Thread): + messages = Queue.Queue() + + run_source = functools.partial(nonqueue_source, messages, target) + thread = thread_factory(target=run_source) + thread.setDaemon(True) + thread.start() + + # Sink running in current thread + return queue_sink(messages) + + +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__) + + return wrapper + + return log_exception_decorator + + +class LoginWindow(object): + + def __init__(self, widgetTree): + """ + @note Thread agnostic + """ + self._dialog = widgetTree.get_widget("loginDialog") + self._parentWindow = widgetTree.get_widget("mainWindow") + self._serviceCombo = widgetTree.get_widget("serviceCombo") + self._usernameEntry = widgetTree.get_widget("usernameentry") + self._passwordEntry = widgetTree.get_widget("passwordentry") + + self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) + self._serviceCombo.set_model(self._serviceList) + cell = gtk.CellRendererText() + self._serviceCombo.pack_start(cell, True) + self._serviceCombo.add_attribute(cell, 'text', 1) + self._serviceCombo.set_active(0) + + widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked) + widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked) + + def request_credentials(self, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceCombo.hide() + self._serviceList.clear() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + self._passwordEntry.set_text("") + finally: + self._dialog.hide() + + return username, password + + def request_credentials_from(self, + services, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceList.clear() + for serviceIdserviceName in services: + self._serviceList.append(serviceIdserviceName) + self._serviceCombo.set_active(0) + self._serviceCombo.show() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + finally: + self._dialog.hide() + + itr = self._serviceCombo.get_active_iter() + serviceId = int(self._serviceList.get_value(itr, 0)) + self._serviceList.clear() + return serviceId, username, password + + def _on_loginbutton_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_loginclose_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) + + +def safecall(f, errorDisplay=None, default=None, exception=Exception): + ''' + Returns modified f. When the modified f is called and throws an + exception, the default value is returned + ''' + def _safecall(*args, **argv): + try: + return f(*args,**argv) + except exception, e: + if errorDisplay is not None: + errorDisplay.push_exception(e) + return default + return _safecall + + +class ErrorDisplay(object): + + def __init__(self, widgetTree): + super(ErrorDisplay, self).__init__() + self.__errorBox = widgetTree.get_widget("errorEventBox") + self.__errorDescription = widgetTree.get_widget("errorDescription") + self.__errorClose = widgetTree.get_widget("errorClose") + self.__parentBox = self.__errorBox.get_parent() + + self.__errorBox.connect("button_release_event", self._on_close) + + self.__messages = [] + self.__parentBox.remove(self.__errorBox) + + def push_message_with_lock(self, message): + with gtk_lock(): + self.push_message(message) + + def push_message(self, message): + self.__messages.append(message) + if 1 == len(self.__messages): + self.__show_message(message) + + def push_exception_with_lock(self): + with gtk_lock(): + self.push_exception() + + def push_exception(self): + userMessage = str(sys.exc_info()[1]) + self.push_message(userMessage) + _moduleLogger.exception(userMessage) + + def pop_message(self): + del self.__messages[0] + if 0 == len(self.__messages): + self.__hide_message() + else: + self.__errorDescription.set_text(self.__messages[0]) + + def _on_close(self, *args): + self.pop_message() + + def __show_message(self, message): + self.__errorDescription.set_text(message) + self.__parentBox.pack_start(self.__errorBox, False, False) + self.__parentBox.reorder_child(self.__errorBox, 1) + + def __hide_message(self): + self.__errorDescription.set_text("") + self.__parentBox.remove(self.__errorBox) + + +class DummyErrorDisplay(object): + + def __init__(self): + super(DummyErrorDisplay, self).__init__() + + self.__messages = [] + + def push_message_with_lock(self, message): + self.push_message(message) + + def push_message(self, message): + if 0 < len(self.__messages): + self.__messages.append(message) + else: + self.__show_message(message) + + def push_exception(self, exception = None): + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) + + def pop_message(self): + if 0 < len(self.__messages): + self.__show_message(self.__messages[0]) + del self.__messages[0] + + def __show_message(self, message): + _moduleLogger.debug(message) + + +class MessageBox(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class MessageBox2(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class PopupCalendar(object): + + def __init__(self, parent, displayDate, title = ""): + self._displayDate = displayDate + + self._calendar = gtk.Calendar() + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + self._calendar.set_display_options( + gtk.CALENDAR_SHOW_HEADING | + gtk.CALENDAR_SHOW_DAY_NAMES | + gtk.CALENDAR_NO_MONTH_CHANGE | + 0 + ) + self._calendar.connect("day-selected", self._on_day_selected) + + self._popupWindow = gtk.Window() + self._popupWindow.set_title(title) + self._popupWindow.add(self._calendar) + self._popupWindow.set_transient_for(parent) + self._popupWindow.set_modal(True) + self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self._popupWindow.set_skip_pager_hint(True) + self._popupWindow.set_skip_taskbar_hint(True) + + def run(self): + self._popupWindow.show_all() + + def _on_day_selected(self, *args): + try: + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + except Exception, e: + _moduleLogger.exception(e) + + +class QuickAddView(object): + + def __init__(self, widgetTree, errorDisplay, signalSink, prefix): + self._errorDisplay = errorDisplay + self._manager = None + self._signalSink = signalSink + + self._clipboard = gtk.clipboard_get() + + self._taskNameEntry = widgetTree.get_widget(prefix+"-nameEntry") + self._addTaskButton = widgetTree.get_widget(prefix+"-addButton") + self._pasteTaskNameButton = widgetTree.get_widget(prefix+"-pasteNameButton") + self._clearTaskNameButton = widgetTree.get_widget(prefix+"-clearNameButton") + self._onAddId = None + self._onAddClickedId = None + self._onAddReleasedId = None + self._addToEditTimerId = None + self._onClearId = None + self._onPasteId = None + + def enable(self, manager): + self._manager = manager + + self._onAddId = self._addTaskButton.connect("clicked", self._on_add) + self._onAddClickedId = self._addTaskButton.connect("pressed", self._on_add_pressed) + self._onAddReleasedId = self._addTaskButton.connect("released", self._on_add_released) + self._onPasteId = self._pasteTaskNameButton.connect("clicked", self._on_paste) + self._onClearId = self._clearTaskNameButton.connect("clicked", self._on_clear) + + def disable(self): + self._manager = None + + self._addTaskButton.disconnect(self._onAddId) + self._addTaskButton.disconnect(self._onAddClickedId) + self._addTaskButton.disconnect(self._onAddReleasedId) + self._pasteTaskNameButton.disconnect(self._onPasteId) + self._clearTaskNameButton.disconnect(self._onClearId) + + def set_addability(self, addability): + self._addTaskButton.set_sensitive(addability) + + def _on_add(self, *args): + try: + name = self._taskNameEntry.get_text() + self._taskNameEntry.set_text("") + + self._signalSink.stage.send(("add", name)) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_add_edit(self, *args): + try: + name = self._taskNameEntry.get_text() + self._taskNameEntry.set_text("") + + self._signalSink.stage.send(("add-edit", name)) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_add_pressed(self, widget): + try: + self._addToEditTimerId = gobject.timeout_add(1000, self._on_add_edit) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_add_released(self, widget): + try: + if self._addToEditTimerId is not None: + gobject.source_remove(self._addToEditTimerId) + self._addToEditTimerId = None + except Exception, e: + self._errorDisplay.push_exception() + + def _on_paste(self, *args): + try: + entry = self._taskNameEntry.get_text() + addedText = self._clipboard.wait_for_text() + if addedText: + entry += addedText + self._taskNameEntry.set_text(entry) + except Exception, e: + self._errorDisplay.push_exception() + + def _on_clear(self, *args): + try: + self._taskNameEntry.set_text("") + except Exception, e: + self._errorDisplay.push_exception() + + +class TapOrHold(object): + + def __init__(self, widget): + self._widget = widget + self._isTap = True + self._isPointerInside = True + self._holdTimeoutId = None + self._tapTimeoutId = None + self._taps = 0 + + self._bpeId = None + self._breId = None + self._eneId = None + self._lneId = None + + def enable(self): + self._bpeId = self._widget.connect("button-press-event", self._on_button_press) + self._breId = self._widget.connect("button-release-event", self._on_button_release) + self._eneId = self._widget.connect("enter-notify-event", self._on_enter) + self._lneId = self._widget.connect("leave-notify-event", self._on_leave) + + def disable(self): + self._widget.disconnect(self._bpeId) + self._widget.disconnect(self._breId) + self._widget.disconnect(self._eneId) + self._widget.disconnect(self._lneId) + + def on_tap(self, taps): + print "TAP", taps + + def on_hold(self, taps): + print "HOLD", taps + + def on_holding(self): + print "HOLDING" + + def on_cancel(self): + print "CANCEL" + + def _on_button_press(self, *args): + # Hack to handle weird notebook behavior + self._isPointerInside = True + self._isTap = True + + if self._tapTimeoutId is not None: + gobject.source_remove(self._tapTimeoutId) + self._tapTimeoutId = None + + # Handle double taps + if self._holdTimeoutId is None: + self._tapTimeoutId = None + + self._taps = 1 + self._holdTimeoutId = gobject.timeout_add(1000, self._on_hold_timeout) + else: + self._taps = 2 + + def _on_button_release(self, *args): + assert self._tapTimeoutId is None + # Handle release after timeout if user hasn't double-clicked + self._tapTimeoutId = gobject.timeout_add(100, self._on_tap_timeout) + + def _on_actual_press(self, *args): + if self._holdTimeoutId is not None: + gobject.source_remove(self._holdTimeoutId) + self._holdTimeoutId = None + + if self._isPointerInside: + if self._isTap: + self.on_tap(self._taps) + else: + self.on_hold(self._taps) + else: + self.on_cancel() + + def _on_tap_timeout(self, *args): + self._tapTimeoutId = None + self._on_actual_press() + return False + + def _on_hold_timeout(self, *args): + self._holdTimeoutId = None + self._isTap = False + self.on_holding() + return False + + def _on_enter(self, *args): + self._isPointerInside = True + + def _on_leave(self, *args): + self._isPointerInside = False + + +if __name__ == "__main__": + if True: + win = gtk.Window() + win.set_title("Tap'N'Hold") + eventBox = gtk.EventBox() + win.add(eventBox) + + context = ContextHandler(eventBox, coroutines.printer_sink()) + context.enable() + win.connect("destroy", lambda w: gtk.main_quit()) + + win.show_all() + + if False: + import datetime + cal = PopupCalendar(None, datetime.datetime.now()) + cal._popupWindow.connect("destroy", lambda w: gtk.main_quit()) + cal.run() + + gtk.main() -- 1.7.9.5 From dfb233f19f79a41ab48cf303d26c5fd8706c61eb Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 11:54:57 -0600 Subject: [PATCH 13/16] Fixed unit entry selection issue for Hildon-mode --- src/gonvert_glade.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index 4882b9b..2060252 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -640,8 +640,7 @@ class Gonvert(object): selectedCategoryName = unit_data.UNIT_CATEGORIES[newIndex] self._categorySelectionButton.get_child().set_markup("%s" % selectedCategoryName) - self._categoryView.set_cursor(newIndex, self._categoryColumn, False) - self._categoryView.grab_focus() + self._switch_category(selectedCategoryName) @gtk_toolbox.log_exception(_moduleLogger) def _on_click_category(self, *args): -- 1.7.9.5 From a5745570efc59386d16544b9255a18b0568547e3 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 12:08:43 -0600 Subject: [PATCH 14/16] More .deb work --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 8d93fba..43132c0 100644 --- a/Makefile +++ b/Makefile @@ -71,6 +71,7 @@ upload: package dput fremantle-extras-builder $(BUILD_PATH)/fremantle/$(PROJECT_NAME)*.changes dput diablo-extras-builder $(BUILD_PATH)/diablo/$(PROJECT_NAME)*.changes dput chinook-extras-builder $(BUILD_PATH)/chinook/$(PROJECT_NAME)*.changes + cp $(BUILD_PATH)/debian/*.deb www/$(PROJECT_NAME).deb lint: $(OBJ) $(foreach file, $(SOURCE), $(LINT) $(file) ; ) -- 1.7.9.5 From 2a6f89aed9a24d02d7dd0e112dc815dbe64ab433 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Sat, 12 Dec 2009 12:41:47 -0600 Subject: [PATCH 15/16] Making app menu possible --- src/gonvert_glade.py | 2 -- src/hildonize.py | 33 +++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/gonvert_glade.py b/src/gonvert_glade.py index 2060252..8bf50f3 100755 --- a/src/gonvert_glade.py +++ b/src/gonvert_glade.py @@ -248,11 +248,9 @@ class Gonvert(object): else: self._categorySelectionButton.hide() - replacementButtons = [] menu = hildonize.hildonize_menu( self._mainWindow, widgets.get_widget("mainMenuBar"), - replacementButtons ) if not hildonize.IS_HILDON_SUPPORTED: diff --git a/src/hildonize.py b/src/hildonize.py index 9c83b77..09ee705 100755 --- a/src/hildonize.py +++ b/src/hildonize.py @@ -1,5 +1,11 @@ #!/usr/bin/env python +""" +Open Issues + @bug not all of a message is shown + @bug Buttons are too small +""" + import gobject import gtk @@ -86,16 +92,14 @@ except AttributeError: hildonize_window = _null_hildonize_window -def _fremantle_hildonize_menu(window, gtkMenu, buttons): +def _fremantle_hildonize_menu(window, gtkMenu): appMenu = hildon.AppMenu() - for button in buttons: - appMenu.append(button) window.set_app_menu(appMenu) gtkMenu.get_parent().remove(gtkMenu) return appMenu -def _hildon_hildonize_menu(window, gtkMenu, ignoredButtons): +def _hildon_hildonize_menu(window, gtkMenu): hildonMenu = gtk.Menu() for child in gtkMenu.get_children(): child.reparent(hildonMenu) @@ -104,7 +108,7 @@ def _hildon_hildonize_menu(window, gtkMenu, ignoredButtons): return hildonMenu -def _null_hildonize_menu(window, gtkMenu, ignoredButtons): +def _null_hildonize_menu(window, gtkMenu): return gtkMenu @@ -717,12 +721,21 @@ if __name__ == "__main__": win = gtk.Window() win.add(label) win = hildonize_window(app, win) - if False: + 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) - if True: + elif False: print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") - if False: + elif False: import pprint name, value = "", "" goodLocals = [ @@ -730,11 +743,11 @@ if __name__ == "__main__": if not name.startswith("_") ] pprint.pprint(goodLocals) - if False: + elif False: import time show_information_banner(win, "Hello World") time.sleep(5) - if False: + elif False: import time banner = show_busy_banner_start(win, "Hello World") time.sleep(5) -- 1.7.9.5 From 9d8c4189dfa79bca9569fcf0c72b12cbdb079849 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Mon, 14 Dec 2009 18:35:18 -0600 Subject: [PATCH 16/16] Version bump and changelist --- src/constants.py | 2 +- support/builddeb.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/constants.py b/src/constants.py index 675bf08..ff45110 100644 --- a/src/constants.py +++ b/src/constants.py @@ -3,7 +3,7 @@ import os __pretty_app_name__ = "Gonvert" __app_name__ = "gonvert" __version__ = "0.9.1" -__build__ = 8 +__build__ = 0 __app_magic__ = 0xdeadbeef _data_path_ = os.path.join(os.path.expanduser("~"), ".gonvert") _user_settings_ = "%s/settings.ini" % _data_path_ diff --git a/support/builddeb.py b/support/builddeb.py index 4402c2e..1270f20 100755 --- a/support/builddeb.py +++ b/support/builddeb.py @@ -20,7 +20,11 @@ __version__ = constants.__version__ __build__ = constants.__build__ __changelog__ = """ 0.9.1 +* Added support for creating generic .deb files +* Added an apothecary unit +* Bug fix: Can directly enter numbers after selecting category * Bug fix: font of the category button was inconsistent +* Bug fix: Improved up/down arrow keys 0.9.0 * Added Radioactivity and Radiation dose categories. -- 1.7.9.5