Updating from Dialcentral/ejpi
authorepage <eopage@byu.net>
Wed, 28 Oct 2009 01:06:27 +0000 (01:06 +0000)
committerepage <eopage@byu.net>
Wed, 28 Oct 2009 01:06:27 +0000 (01:06 +0000)
git-svn-id: file:///svnroot/quicknote/trunk@59 bb7704e3-badb-4cfa-9ab3-9374dc87eaa2

16 files changed:
Makefile
data/quicknote.desktop
src/constants.py
src/gtk_toolbox.py
src/hildonize.py [new file with mode: 0755]
src/libhistory.py
src/libkopfzeile.py
src/libnotizen.py
src/libquicknote.py
src/libspeichern.py
src/libsqldialog.py
src/libsync.py
src/quicknote.py
support/builddeb.py
support/py2deb.py [new file with mode: 0644]
support/todo.py [new file with mode: 0755]

index c83ef96..9ede64b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -13,23 +13,28 @@ POTFILES=$(wildcard src/quicknoteclasses/*.py)
 
 UNIT_TEST=nosetests --with-doctest -w .
 SYNTAX_TEST=support/test_syntax.py
 
 UNIT_TEST=nosetests --with-doctest -w .
 SYNTAX_TEST=support/test_syntax.py
+STYLE_TEST=../../Python/tools/pep8.py --ignore=W191,E501
 LINT_RC=./support/pylint.rc
 LINT=pylint --rcfile=$(LINT_RC)
 PROFILE_GEN=python -m cProfile -o .profile
 PROFILE_VIEW=python -m pstats .profile
 LINT_RC=./support/pylint.rc
 LINT=pylint --rcfile=$(LINT_RC)
 PROFILE_GEN=python -m cProfile -o .profile
 PROFILE_VIEW=python -m pstats .profile
+TODO_FINDER=support/todo.py
+CTAGS=ctags-exuberant
 
 
-.PHONY: all run profile test lint clean distclean install update_po build_mo
+.PHONY: all run profile debug test lint tags todo clean distclean install update_po build_mo
 
 
-all: build_mo
-       python2.5 setup.py build  
+all: test
 
 
-run:
+run: $(OBJ)
        $(PROGRAM)
 
        $(PROGRAM)
 
-profile:
+profile: $(OBJ)
        $(PROFILE_GEN) $(PROGRAM)
        $(PROFILE_VIEW)
 
        $(PROFILE_GEN) $(PROGRAM)
        $(PROFILE_VIEW)
 
+debug: $(OBJ)
+       $(DEBUGGER) $(PROGRAM)
+
 test: $(OBJ)
        $(UNIT_TEST)
 
 test: $(OBJ)
        $(UNIT_TEST)
 
@@ -72,20 +77,31 @@ build: $(OBJ) build_mo
 lint: $(OBJ)
        $(foreach file, $(SOURCE), $(LINT) $(file) ; )
 
 lint: $(OBJ)
        $(foreach file, $(SOURCE), $(LINT) $(file) ; )
 
-clean: 
+tags: $(TAG_FILE) 
+
+todo: $(TODO_FILE)
+
+clean:
        rm -rf ./locale
        rm -rf ./locale
-       rm -rf $(OBJ)
+       rm -Rf $(OBJ)
        rm -Rf $(BUILD_PATH)
        rm -Rf $(BUILD_PATH)
-       python2.5 setup.py clean --all
+       rm -Rf $(TODO_FILE)
 
 
-distclean: clean
+distclean:
+       rm -Rf $(OBJ)
+       rm -Rf $(BUILD_PATH)
+       rm -Rf $(TAG_FILE)
        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
 
        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
 
-install: build_mo
-       python2.5 setup.py install --root $(DESTDIR) 
+$(TAG_FILE): $(OBJ)
+       mkdir -p $(dir $(TAG_FILE))
+       $(CTAGS) -o $(TAG_FILE) $(SOURCE)
+
+$(TODO_FILE): $(SOURCE)
+       @- $(TODO_FINDER) $(SOURCE) > $(TODO_FILE)
 
 %.pyc: %.py
        $(SYNTAX_TEST) $<
 
 %.pyc: %.py
        $(SYNTAX_TEST) $<
index 84ed6e2..6cc9b00 100644 (file)
@@ -3,7 +3,7 @@ Encoding=UTF-8
 Name=Quicknote
 Comment=Quicknote
 Type=Application
 Name=Quicknote
 Comment=Quicknote
 Type=Application
-Exec=/usr/bin/quicknote.py
+Exec=/usr/bin/run-standalone.sh /usr/bin/quicknote.py
 Icon=quicknote
 X-Window-Icon=quicknote
 X-Window-Icon-Dimmed=quicknote
 Icon=quicknote
 X-Window-Icon=quicknote
 X-Window-Icon-Dimmed=quicknote
index 38bfd6f..a5f94e3 100644 (file)
@@ -1,4 +1,8 @@
+import os
+
 __pretty_app_name__ = "Quicknote"
 __app_name__ = "quicknote"
 __version__ = "0.7.8"
 __pretty_app_name__ = "Quicknote"
 __app_name__ = "quicknote"
 __version__ = "0.7.8"
+__build__ = 1
+_data_path_ = os.path.join(os.path.expanduser("~"), ".quicknote")
 __app_magic__ = 0xdeadbeef
 __app_magic__ = 0xdeadbeef
index 1490c3d..a2a2e2a 100644 (file)
@@ -2,16 +2,76 @@
 
 from __future__ import with_statement
 
 
 from __future__ import with_statement
 
+import os
+import errno
 import sys
 import sys
-import traceback
+import time
+import itertools
 import functools
 import contextlib
 import functools
 import contextlib
-import warnings
+import logging
+import threading
+import Queue
 
 import gobject
 import gtk
 
 
 
 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()
 @contextlib.contextmanager
 def gtk_lock():
        gtk.gdk.threads_enter()
@@ -80,6 +140,262 @@ def synchronous_gtk_message(original_func):
        return immediate_func
 
 
        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'), (<type 'exceptions.RuntimeError'>, 'Goodbye'), (None, 'Meh'), (<type 'exceptions.GeneratorExit'>, 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)
+
+
+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):
 class ErrorDisplay(object):
 
        def __init__(self, widgetTree):
@@ -99,31 +415,25 @@ class ErrorDisplay(object):
                        self.push_message(message)
 
        def push_message(self, message):
                        self.push_message(message)
 
        def push_message(self, message):
-               if 0 < len(self.__messages):
-                       self.__messages.append(message)
-               else:
+               self.__messages.append(message)
+               if 1 == len(self.__messages):
                        self.__show_message(message)
 
                        self.__show_message(message)
 
-       def push_exception_with_lock(self, exception = None):
+       def push_exception_with_lock(self):
                with gtk_lock():
                with gtk_lock():
-                       self.push_exception(exception)
+                       self.push_exception()
 
 
-       def push_exception(self, exception = None):
-               if exception is None:
-                       userMessage = str(sys.exc_value)
-                       warningMessage = str(traceback.format_exc())
-               else:
-                       userMessage = str(exception)
-                       warningMessage = str(exception)
+       def push_exception(self):
+               userMessage = str(sys.exc_info()[1])
                self.push_message(userMessage)
                self.push_message(userMessage)
-               warnings.warn(warningMessage, stacklevel=3)
+               _moduleLogger.exception(userMessage)
 
        def pop_message(self):
 
        def pop_message(self):
-               if 0 < len(self.__messages):
-                       self.__show_message(self.__messages[0])
-                       del self.__messages[0]
-               else:
+               del self.__messages[0]
+               if 0 == len(self.__messages):
                        self.__hide_message()
                        self.__hide_message()
+               else:
+                       self.__errorDescription.set_text(self.__messages[0])
 
        def _on_close(self, *args):
                self.pop_message()
 
        def _on_close(self, *args):
                self.pop_message()
@@ -155,11 +465,8 @@ class DummyErrorDisplay(object):
                        self.__show_message(message)
 
        def push_exception(self, exception = None):
                        self.__show_message(message)
 
        def push_exception(self, exception = None):
-               if exception is None:
-                       warningMessage = traceback.format_exc()
-               else:
-                       warningMessage = exception
-               warnings.warn(warningMessage, stacklevel=3)
+               userMessage = str(sys.exc_value)
+               _moduleLogger.exception(userMessage)
 
        def pop_message(self):
                if 0 < len(self.__messages):
 
        def pop_message(self):
                if 0 < len(self.__messages):
@@ -167,7 +474,7 @@ class DummyErrorDisplay(object):
                        del self.__messages[0]
 
        def __show_message(self, message):
                        del self.__messages[0]
 
        def __show_message(self, message):
-               warnings.warn(message, stacklevel=2)
+               _moduleLogger.debug(message)
 
 
 class MessageBox(gtk.MessageDialog):
 
 
 class MessageBox(gtk.MessageDialog):
@@ -240,8 +547,8 @@ class PopupCalendar(object):
                try:
                        self._calendar.select_month(self._displayDate.month, self._displayDate.year)
                        self._calendar.select_day(self._displayDate.day)
                try:
                        self._calendar.select_month(self._displayDate.month, self._displayDate.year)
                        self._calendar.select_day(self._displayDate.day)
-               except StandardError, e:
-                       warnings.warn(e.message)
+               except Exception, e:
+                       _moduleLogger.exception(e)
 
 
 if __name__ == "__main__":
 
 
 if __name__ == "__main__":
diff --git a/src/hildonize.py b/src/hildonize.py
new file mode 100755 (executable)
index 0000000..998a6c5
--- /dev/null
@@ -0,0 +1,731 @@
+#!/usr/bin/env python
+
+"""
+Open Issues
+       @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_title(window, title):
+       pass
+
+
+def _null_set_application_title(window, title):
+       window.set_title(title)
+
+
+if IS_HILDON_SUPPORTED:
+       set_application_title = _hildon_set_application_title
+else:
+       set_application_title = _null_set_application_title
+
+
+def _fremantle_hildonize_window(app, window):
+       oldWindow = window
+       newWindow = hildon.StackableWindow()
+       oldWindow.get_child().reparent(newWindow)
+       app.add_window(newWindow)
+       return newWindow
+
+
+def _hildon_hildonize_window(app, window):
+       oldWindow = window
+       newWindow = hildon.Window()
+       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, buttons):
+       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):
+       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, ignoredButtons):
+       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 _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_mark_window_rotatable(window):
+       # gtk documentation is unclear whether this does a "=" or a "|="
+       window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
+
+
+def _null_mark_window_rotatable(window):
+       pass
+
+
+try:
+       hildon.HILDON_PORTRAIT_MODE_SUPPORT
+       mark_window_rotatable = _hildon_mark_window_rotatable
+except AttributeError:
+       mark_window_rotatable = _null_mark_window_rotatable
+
+
+def _hildon_window_to_portrait(window):
+       # gtk documentation is unclear whether this does a "=" or a "|="
+       window.set_flags(hildon.HILDON_PORTRAIT_MODE_SUPPORT)
+
+
+def _hildon_window_to_landscape(window):
+       # gtk documentation is unclear whether this does a "=" or a "&= ~"
+       window.unset_flags(hildon.HILDON_PORTRAIT_MODE_REQUEST)
+
+
+def _null_window_to_portrait(window):
+       pass
+
+
+def _null_window_to_landscape(window):
+       pass
+
+
+try:
+       hildon.HILDON_PORTRAIT_MODE_SUPPORT
+       hildon.HILDON_PORTRAIT_MODE_REQUEST
+
+       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 _fremantle_hildonize_scrollwindow(scrolledWindow):
+       pannableWindow = hildon.PannableArea()
+
+       child = scrolledWindow.get_child()
+       scrolledWindow.remove(child)
+       pannableWindow.add(child)
+
+       parent = scrolledWindow.get_parent()
+       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:
+               print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
+       if True:
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
+       if False:
+               import pprint
+               name, value = "", ""
+               goodLocals = [
+                       (name, value) for (name, value) in locals().iteritems()
+                       if not name.startswith("_")
+               ]
+               pprint.pprint(goodLocals)
+       if False:
+               import time
+               show_information_banner(win, "Hello World")
+               time.sleep(5)
+       if False:
+               import time
+               banner = show_busy_banner_start(win, "Hello World")
+               time.sleep(5)
+               show_busy_banner_end(banner)
index 2bf0a8b..c897cd9 100644 (file)
@@ -10,6 +10,8 @@ published by the Free Software Foundation.
 """
 
 
 """
 
 
+import logging
+
 import gtk
 
 
 import gtk
 
 
@@ -19,6 +21,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("history")
+
+
 class Dialog(gtk.Dialog):
 
        def __init__(self, daten = None):
 class Dialog(gtk.Dialog):
 
        def __init__(self, daten = None):
index 6b0a7ba..017d22b 100644 (file)
@@ -22,6 +22,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("kopfzeile")
+
+
 class Kopfzeile(gtk.HBox):
 
        __gsignals__ = {
 class Kopfzeile(gtk.HBox):
 
        __gsignals__ = {
@@ -33,7 +36,7 @@ class Kopfzeile(gtk.HBox):
                self._db = db
 
                gtk.HBox.__init__(self, homogeneous = False, spacing = 3)
                self._db = db
 
                gtk.HBox.__init__(self, homogeneous = False, spacing = 3)
-               logging.info("libkopfzeile, init")
+               _moduleLogger.info("libkopfzeile, init")
 
                categoryHBox = gtk.HBox()
                self.pack_start(categoryHBox, expand = False, fill = True, padding = 0)
 
                categoryHBox = gtk.HBox()
                self.pack_start(categoryHBox, expand = False, fill = True, padding = 0)
@@ -57,7 +60,7 @@ class Kopfzeile(gtk.HBox):
                self._searchEntry.connect("changed", self.search_entry_changed, None)
 
        def category_combo_changed(self, widget = None, data = None):
                self._searchEntry.connect("changed", self.search_entry_changed, None)
 
        def category_combo_changed(self, widget = None, data = None):
-               logging.debug("comboCategoryChanged")
+               _moduleLogger.debug("comboCategoryChanged")
                if self._lastCategory != self.categoryCombo.get_active():
                        sql = "UPDATE categories SET liste = ? WHERE id = 1"
                        self._db.speichereSQL(sql, (self.categoryCombo.get_active(), ))
                if self._lastCategory != self.categoryCombo.get_active():
                        sql = "UPDATE categories SET liste = ? WHERE id = 1"
                        self._db.speichereSQL(sql, (self.categoryCombo.get_active(), ))
@@ -65,7 +68,7 @@ class Kopfzeile(gtk.HBox):
                self.emit("reload_notes")
 
        def search_entry_changed(self, widget = None, data = None):
                self.emit("reload_notes")
 
        def search_entry_changed(self, widget = None, data = None):
-               logging.debug("search_entry_changed")
+               _moduleLogger.debug("search_entry_changed")
                self.emit("reload_notes")
 
        def get_category(self):
                self.emit("reload_notes")
 
        def get_category(self):
index 9f720e8..b18e599 100644 (file)
@@ -29,6 +29,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("notizen")
+
+
 class Notizen(gtk.HBox):
 
        def __init__(self, db, topBox):
 class Notizen(gtk.HBox):
 
        def __init__(self, db, topBox):
@@ -40,7 +43,7 @@ class Notizen(gtk.HBox):
                self._categoryName = ""
 
                gtk.HBox.__init__(self, homogeneous = False, spacing = 0)
                self._categoryName = ""
 
                gtk.HBox.__init__(self, homogeneous = False, spacing = 0)
-               logging.info("libnotizen, init")
+               _moduleLogger.info("libnotizen, init")
 
                self._noteslist = simple_list.SimpleList()
                self._noteslist.set_eventfunction_cursor_changed(self._update_noteslist)
 
                self._noteslist = simple_list.SimpleList()
                self._noteslist.set_eventfunction_cursor_changed(self._update_noteslist)
@@ -119,7 +122,7 @@ class Notizen(gtk.HBox):
                        self._historyBox.hide()
 
        def load_notes(self, data = None):
                        self._historyBox.hide()
 
        def load_notes(self, data = None):
-               logging.info("load_notes params: pos:"+str(self._pos)+" noteid:"+str(self.noteId))
+               _moduleLogger.info("load_notes params: pos:"+str(self._pos)+" noteid:"+str(self.noteId))
                self._noteslist.clear_items()
                self._noteslist.append_item(_("New Note..."), "new")
 
                self._noteslist.clear_items()
                self._noteslist.append_item(_("New Note..."), "new")
 
@@ -138,7 +141,7 @@ class Notizen(gtk.HBox):
                self._noteBodyView.get_buffer().set_text("")
 
        def save_note(self, widget = None, data = None, data2 = None):
                self._noteBodyView.get_buffer().set_text("")
 
        def save_note(self, widget = None, data = None, data2 = None):
-               logging.info("save_note params: pos:"+str(self._pos)+" noteid:"+str(self.noteId))
+               _moduleLogger.info("save_note params: pos:"+str(self._pos)+" noteid:"+str(self.noteId))
                #print "params:", data, data2
                buf = self._noteBodyView.get_buffer().get_text(self._noteBodyView.get_buffer().get_start_iter(), self._noteBodyView.get_buffer().get_end_iter())
                if buf is None or len(buf) == 0:
                #print "params:", data, data2
                buf = self._noteBodyView.get_buffer().get_text(self._noteBodyView.get_buffer().get_start_iter(), self._noteBodyView.get_buffer().get_end_iter())
                if buf is None or len(buf) == 0:
@@ -147,7 +150,7 @@ class Notizen(gtk.HBox):
                if buf == self._noteBody:
                        return
 
                if buf == self._noteBody:
                        return
 
-               logging.info("Saving note: "+buf)
+               _moduleLogger.info("Saving note: "+buf)
                if self._pos == -1 or self.noteId == -1:
                        self._pos = -1
                        self.noteId = str(uuid.uuid4())
                if self._pos == -1 or self.noteId == -1:
                        self._pos = -1
                        self.noteId = str(uuid.uuid4())
@@ -261,7 +264,7 @@ class Notizen(gtk.HBox):
                        data = dialog.get_selected_row()
                        if data is not None:
                                self._db.speichereSQL(data[2], data[3].split(" <<Tren-ner>> "), rowid = self.noteId)
                        data = dialog.get_selected_row()
                        if data is not None:
                                self._db.speichereSQL(data[2], data[3].split(" <<Tren-ner>> "), rowid = self.noteId)
-                               logging.info("loading History")
+                               _moduleLogger.info("loading History")
                                self._update_noteslist()
 
                dialog.destroy()
                                self._update_noteslist()
 
                dialog.destroy()
index bf2870f..d44925f 100644 (file)
@@ -1,4 +1,4 @@
-#/usr/bin/env python2.5
+#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 """
 # -*- coding: utf-8 -*-
 
 """
@@ -49,6 +49,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("quick")
+
+
 class QuicknoteProgram(hildon.Program):
 
        _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
 class QuicknoteProgram(hildon.Program):
 
        _user_data = os.path.join(os.path.expanduser("~"), ".%s" % constants.__app_name__)
@@ -61,17 +64,7 @@ class QuicknoteProgram(hildon.Program):
 
                dblog = os.path.join(self._user_data, "quicknote.log")
 
 
                dblog = os.path.join(self._user_data, "quicknote.log")
 
-               # define a Handler which writes INFO messages or higher to the sys.stderr
-               console = logging.StreamHandler()
-               console.setLevel(logging.DEBUG)
-               # set a format which is simpler for console use
-               formatter = logging.Formatter('%(asctime)s  %(levelname)-8s %(message)s')
-               # tell the handler to use this format
-               console.setFormatter(formatter)
-               # add the handler to the root logger
-               logging.getLogger('').addHandler(console)
-
-               logging.info('Starting quicknote')
+               _moduleLogger.info('Starting quicknote')
 
                if osso is not None:
                        self._osso_c = osso.Context(constants.__app_name__, constants.__version__, False)
 
                if osso is not None:
                        self._osso_c = osso.Context(constants.__app_name__, constants.__version__, False)
@@ -310,7 +303,7 @@ class QuicknoteProgram(hildon.Program):
                res = sqldiag.run()
                sqldiag.hide()
                if res == sqldiag.EXPORT_RESPONSE:
                res = sqldiag.run()
                sqldiag.hide()
                if res == sqldiag.EXPORT_RESPONSE:
-                       logging.info("exporting sql")
+                       _moduleLogger.info("exporting sql")
 
                        dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
 
 
                        dlg = hildon.FileChooserDialog(parent=self._window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
 
@@ -419,3 +412,9 @@ class QuicknoteProgram(hildon.Program):
                dialog.set_authors(["Christoph Wurstle <n800@axique.net>", "Ed Page <edpage@byu.net> (Blame him for the most recent bugs)"])
                dialog.run()
                dialog.destroy()
                dialog.set_authors(["Christoph Wurstle <n800@axique.net>", "Ed Page <edpage@byu.net> (Blame him for the most recent bugs)"])
                dialog.run()
                dialog.destroy()
+
+
+if __name__ == "__main__":
+       logging.basicConfig(level=logging.DEBUG)
+       app = QuicknoteProgram()
+       app.main()
index 6e0d9e4..44d1525 100644 (file)
@@ -24,6 +24,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("speichern")
+
+
 class Speichern():
 
        def __init__(self):
 class Speichern():
 
        def __init__(self):
@@ -34,7 +37,7 @@ class Speichern():
 
        def speichereDirekt(self, schluessel, daten):
                self.d[schluessel] = daten
 
        def speichereDirekt(self, schluessel, daten):
                self.d[schluessel] = daten
-               logging.info("speichereDirekt "+str(schluessel)+" "+str(daten)+" lesen: "+str(self.d[schluessel]))
+               _moduleLogger.info("speichereDirekt "+str(schluessel)+" "+str(daten)+" lesen: "+str(self.d[schluessel]))
 
        def ladeDirekt(self, schluessel, default = ""):
                if (self.d.has_key(schluessel) == True):
 
        def ladeDirekt(self, schluessel, default = ""):
                if (self.d.has_key(schluessel) == True):
@@ -69,9 +72,9 @@ class Speichern():
                        s = str(sys.exc_info())
                        if s.find(" already exists") == -1:
                                if (programSQLError == True):
                        s = str(sys.exc_info())
                        if s.find(" already exists") == -1:
                                if (programSQLError == True):
-                                       logging.error("speichereSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
+                                       _moduleLogger.error("speichereSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
                                else:
                                else:
-                                       logging.error("speichereSQL-Exception in Logging!!!! :"+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
+                                       _moduleLogger.error("speichereSQL-Exception in Logging!!!! :"+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
                        return False
 
        def commitSQL(self):
                        return False
 
        def commitSQL(self):
@@ -86,7 +89,7 @@ class Speichern():
                                self.cur.execute(sql, tupel)
                        return self.cur.fetchall()
                except StandardError:
                                self.cur.execute(sql, tupel)
                        return self.cur.fetchall()
                except StandardError:
-                       logging.error("ladeSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
+                       _moduleLogger.error("ladeSQL-Exception "+str(sys.exc_info())+" "+str(sql)+" "+str(tupel))
                        return ()
 
        def ladeHistory(self, sql_condition, param_condition):
                        return ()
 
        def ladeHistory(self, sql_condition, param_condition):
@@ -199,7 +202,7 @@ class Speichern():
                        self.conn.close()
                except StandardError:
                        pass
                        self.conn.close()
                except StandardError:
                        pass
-               logging.info("Alle Data saved")
+               _moduleLogger.info("Alle Data saved")
 
        def __del__(self):
                self.close()
 
        def __del__(self):
                self.close()
index 682ca6f..a6cc2d6 100755 (executable)
@@ -22,6 +22,9 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("sqldialog")
+
+
 class SqlDialog(gtk.Dialog):
 
        EXPORT_RESPONSE = 444
 class SqlDialog(gtk.Dialog):
 
        EXPORT_RESPONSE = 444
@@ -29,7 +32,7 @@ class SqlDialog(gtk.Dialog):
        def __init__(self, db):
                self.db = db
 
        def __init__(self, db):
                self.db = db
 
-               logging.info("sqldialog, init")
+               _moduleLogger.info("sqldialog, init")
 
                gtk.Dialog.__init__(self, _("SQL History (the past two days):"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
 
 
                gtk.Dialog.__init__(self, _("SQL History (the past two days):"), None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
 
index 48bcc86..f8acdac 100755 (executable)
@@ -38,12 +38,15 @@ except NameError:
        _ = lambda x: x
 
 
        _ = lambda x: x
 
 
+_moduleLogger = logging.getLogger("sync")
+
+
 class ProgressDialog(gtk.Dialog):
 
        def __init__(self, title = _("Sync process"), parent = None):
                gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ())
 
 class ProgressDialog(gtk.Dialog):
 
        def __init__(self, title = _("Sync process"), parent = None):
                gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, ())
 
-               logging.info("ProgressDialog, init")
+               _moduleLogger.info("ProgressDialog, init")
 
                label = gtk.Label(_("Sync process running...please wait"))
                self.vbox.pack_start(label, True, True, 0)
 
                label = gtk.Label(_("Sync process running...please wait"))
                self.vbox.pack_start(label, True, True, 0)
@@ -67,7 +70,7 @@ class Sync(gtk.VBox):
        def __init__(self, db, parentwindow, port):
                gtk.VBox.__init__(self, homogeneous = False, spacing = 0)
 
        def __init__(self, db, parentwindow, port):
                gtk.VBox.__init__(self, homogeneous = False, spacing = 0)
 
-               logging.info("Sync, init")
+               _moduleLogger.info("Sync, init")
                self.db = db
                self.progress = None
                self.server = None
                self.db = db
                self.progress = None
                self.server = None
@@ -168,13 +171,13 @@ class Sync(gtk.VBox):
                        syncpartner, pcdatum = rows[0]
                else:
                        pcdatum = -1
                        syncpartner, pcdatum = rows[0]
                else:
                        pcdatum = -1
-               logging.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time())))
+               _moduleLogger.info("LastSyncDatum: "+str(pcdatum)+" Jetzt "+str(int(time.time())))
                return pcdatum
 
        def check4commit(self, newSQL, lastdate):
                return pcdatum
 
        def check4commit(self, newSQL, lastdate):
-               logging.info("check4commit 1")
+               _moduleLogger.info("check4commit 1")
                if self.concernedRows is None:
                if self.concernedRows is None:
-                       logging.info("check4commit Updatung concernedRows")
+                       _moduleLogger.info("check4commit Updatung concernedRows")
                        sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC"
                        self.concernedRows = self.db.ladeSQL(sql, (lastdate, ))
 
                        sql = "SELECT pcdatum, rowid FROM logtable WHERE pcdatum>? ORDER BY pcdatum DESC"
                        self.concernedRows = self.db.ladeSQL(sql, (lastdate, ))
 
@@ -185,7 +188,7 @@ class Sync(gtk.VBox):
                                for x in self.concernedRows:
                                        if x[1] == rowid:
                                                if pcdatum < x[0]:
                                for x in self.concernedRows:
                                        if x[1] == rowid:
                                                if pcdatum < x[0]:
-                                                       logging.info("newer sync entry, ignoring old one")
+                                                       _moduleLogger.info("newer sync entry, ignoring old one")
                                                        return False
                                                else:
                                                        return True
                                                        return False
                                                else:
                                                        return True
@@ -198,7 +201,7 @@ class Sync(gtk.VBox):
 
                self.concernedRows = None
                pausenzaehler = 0
 
                self.concernedRows = None
                pausenzaehler = 0
-               logging.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels")
+               _moduleLogger.info("writeSQLTupel got "+str(len(newSQLs))+" sql tupels")
                for newSQL in newSQLs:
                        if newSQL[3] != "":
                                param = newSQL[3].split(" <<Tren-ner>> ")
                for newSQL in newSQLs:
                        if newSQL[3] != "":
                                param = newSQL[3].split(" <<Tren-ner>> ")
@@ -214,7 +217,7 @@ class Sync(gtk.VBox):
                                if (commitSQL == True):
                                        self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5])
                        else:
                                if (commitSQL == True):
                                        self.db.speichereSQL(newSQL[2], param, commit = False, pcdatum = newSQL[1], rowid = newSQL[5])
                        else:
-                               logging.error("writeSQLTupel: Error")
+                               _moduleLogger.error("writeSQLTupel: Error")
 
                        pausenzaehler += 1
                        if (pausenzaehler % 10) == 0:
 
                        pausenzaehler += 1
                        if (pausenzaehler % 10) == 0:
@@ -222,9 +225,9 @@ class Sync(gtk.VBox):
                                while gtk.events_pending():
                                        gtk.main_iteration()
 
                                while gtk.events_pending():
                                        gtk.main_iteration()
 
-               logging.info("Alle SQLs an sqlite geschickt, commiting now")
+               _moduleLogger.info("Alle SQLs an sqlite geschickt, commiting now")
                self.db.commitSQL()
                self.db.commitSQL()
-               logging.info("Alle SQLs commited")
+               _moduleLogger.info("Alle SQLs commited")
 
        def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt):
                self.changeSyncStatus(True, "sync process running")
 
        def doSync(self, sync_uuid, pcdatum, newSQLs, pcdatumjetzt):
                self.changeSyncStatus(True, "sync process running")
@@ -236,13 +239,13 @@ class Sync(gtk.VBox):
                if 30 < diff:
                        return -1
 
                if 30 < diff:
                        return -1
 
-               logging.info("doSync read sqls")
+               _moduleLogger.info("doSync read sqls")
                sql = "SELECT * FROM logtable WHERE pcdatum>?"
                rows = self.db.ladeSQL(sql, (pcdatum, ))
                sql = "SELECT * FROM logtable WHERE pcdatum>?"
                rows = self.db.ladeSQL(sql, (pcdatum, ))
-               logging.info("doSync read sqls")
+               _moduleLogger.info("doSync read sqls")
                self.writeSQLTupel(newSQLs, pcdatum)
                self.writeSQLTupel(newSQLs, pcdatum)
-               logging.info("doSync wrote "+str(len(newSQLs))+" sqls")
-               logging.info("doSync sending "+str(len(rows))+" sqls")
+               _moduleLogger.info("doSync wrote "+str(len(newSQLs))+" sqls")
+               _moduleLogger.info("doSync sending "+str(len(rows))+" sqls")
                return rows
 
        def getRemoteSyncUUID(self):
                return rows
 
        def getRemoteSyncUUID(self):
@@ -253,7 +256,7 @@ class Sync(gtk.VBox):
                self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text())
 
                if widget.get_active():
                self.db.speichereDirekt("syncServerIP", self.comboIP.get_child().get_text())
 
                if widget.get_active():
-                       logging.info("Starting Server")
+                       _moduleLogger.info("Starting Server")
 
                        try:
                                ip = self.comboIP.get_child().get_text()
 
                        try:
                                ip = self.comboIP.get_child().get_text()
@@ -274,7 +277,7 @@ class Sync(gtk.VBox):
 
                        except StandardError:
                                s = str(sys.exc_info())
 
                        except StandardError:
                                s = str(sys.exc_info())
-                               logging.error("libsync: could not start server. Error: "+s)
+                               _moduleLogger.error("libsync: could not start server. Error: "+s)
                                mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Could not start SyncServer. Check IP, port settings.")) #gtk.DIALOG_MODAL
                                mbox.set_modal(False)
                                response = mbox.run()
                                mbox = gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("Could not start SyncServer. Check IP, port settings.")) #gtk.DIALOG_MODAL
                                mbox.set_modal(False)
                                response = mbox.run()
@@ -282,7 +285,7 @@ class Sync(gtk.VBox):
                                mbox.destroy()
                                widget.set_active(False)
                else:
                                mbox.destroy()
                                widget.set_active(False)
                else:
-                       logging.info("Stopping Server")
+                       _moduleLogger.info("Stopping Server")
                        try:
                                del self.rpcserver
                        except StandardError:
                        try:
                                del self.rpcserver
                        except StandardError:
@@ -309,7 +312,7 @@ class Sync(gtk.VBox):
                return (self.sync_uuid, pcdatum)
 
        def syncButton(self, widget, data = None):
                return (self.sync_uuid, pcdatum)
 
        def syncButton(self, widget, data = None):
-               logging.info("Syncing")
+               _moduleLogger.info("Syncing")
 
                self.changeSyncStatus(True, _("sync process running"))
                while (gtk.events_pending()):
 
                self.changeSyncStatus(True, _("sync process running"))
                while (gtk.events_pending()):
@@ -324,11 +327,11 @@ class Sync(gtk.VBox):
                        sql = "SELECT * FROM logtable WHERE pcdatum>?"
                        rows = self.db.ladeSQL(sql, (lastDate, ))
 
                        sql = "SELECT * FROM logtable WHERE pcdatum>?"
                        rows = self.db.ladeSQL(sql, (lastDate, ))
 
-                       logging.info("loaded concerned rows")
+                       _moduleLogger.info("loaded concerned rows")
 
                        newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time())
 
 
                        newSQLs = self.server.doSync(self.sync_uuid, lastDate, rows, time.time())
 
-                       logging.info("did do sync, processing sqls now")
+                       _moduleLogger.info("did do sync, processing sqls now")
                        if newSQLs != -1:
                                self.writeSQLTupel(newSQLs, lastDate)
 
                        if newSQLs != -1:
                                self.writeSQLTupel(newSQLs, lastDate)
 
@@ -342,14 +345,14 @@ class Sync(gtk.VBox):
                                mbox.hide()
                                mbox.destroy()
                        else:
                                mbox.hide()
                                mbox.destroy()
                        else:
-                               logging.warning("Zeitdiff zu groß/oder anderer db-Fehler")
+                               _moduleLogger.warning("Zeitdiff zu groß/oder anderer db-Fehler")
                                self.changeSyncStatus(False, _("no sync process (at the moment)"))
                                mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations"))
                                response = mbox.run()
                                mbox.hide()
                                mbox.destroy()
                except StandardError:
                                self.changeSyncStatus(False, _("no sync process (at the moment)"))
                                mbox =  gtk.MessageDialog(None, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("The clocks are not synchronized between stations"))
                                response = mbox.run()
                                mbox.hide()
                                mbox.destroy()
                except StandardError:
-                       logging.warning("Sync connect failed")
+                       _moduleLogger.warning("Sync connect failed")
                        self.changeSyncStatus(False, _("no sync process (at the moment)"))
                        mbox =  gtk.MessageDialog(
                                None,
                        self.changeSyncStatus(False, _("no sync process (at the moment)"))
                        mbox =  gtk.MessageDialog(
                                None,
index 0848b53..9410944 100755 (executable)
@@ -8,17 +8,30 @@ it under the terms of the GNU General Public License version 2 as
 published by the Free Software Foundation.
 """
 
 published by the Free Software Foundation.
 """
 
-
+import os
 import sys
 import sys
-sys.path.append('/usr/lib/quicknote')
-
-import locale
+import logging
 import gettext
 import gettext
+
+_moduleLogger = logging.getLogger("quicknote")
 gettext.install('quicknote', unicode = 1)
 gettext.install('quicknote', unicode = 1)
+sys.path.append('/usr/lib/quicknote')
+
 
 
+import constants
 import libquicknote
 
 
 if __name__ == "__main__":
 import libquicknote
 
 
 if __name__ == "__main__":
+       try:
+               os.makedirs(constants._data_path_)
+       except OSError, e:
+               if e.errno != 17:
+                       raise
+
+       userLogPath = "%s/quicknote.log" % constants._data_path_
+       logging.basicConfig(level=logging.DEBUG, filename=userLogPath)
+       _moduleLogger.info("quicknote %s-%s" % (constants.__version__, constants.__build__))
+
        app = libquicknote.QuicknoteProgram()
        app.main()
        app = libquicknote.QuicknoteProgram()
        app.main()
index 3a2f793..6234f03 100755 (executable)
@@ -12,12 +12,15 @@ import constants
 
 
 __appname__ = constants.__app_name__
 
 
 __appname__ = constants.__app_name__
-__description__ = "Simple note taking application in a similar vein as PalmOS Memos"
+__description__ = """Simple note taking application in a similar vein as PalmOS Memos
+.
+Homepage: http://quicknote.garage.maemo.org/
+"""
 __author__ = "Christoph Wurstle"
 __email__ = "n800@axique.net"
 __version__ = constants.__version__
 __author__ = "Christoph Wurstle"
 __email__ = "n800@axique.net"
 __version__ = constants.__version__
-__build__ = 0
-__changelog__ = '''
+__build__ = constants.__build__
+__changelog__ = """
 0.7.8
  * Spell checking
  * Fixing the application title
 0.7.8
  * Spell checking
  * Fixing the application title
@@ -48,14 +51,14 @@ __changelog__ = '''
 
 0.7.0
   * Initial Release.
 
 0.7.0
   * Initial Release.
-'''
+"""
 
 
 
 
-__postinstall__ = '''#!/bin/sh -e
+__postinstall__ = """#!/bin/sh -e
 
 gtk-update-icon-cache -f /usr/share/icons/hicolor
 
 gtk-update-icon-cache -f /usr/share/icons/hicolor
-exit 0
-'''
+rm -f ~/.quicknote/quicknote.log
+"""
 
 
 def find_files(path, root):
 
 
 def find_files(path, root):
@@ -86,23 +89,48 @@ def build_package(distribution):
        except:
                pass
 
        except:
                pass
 
+       py2deb.Py2deb.SECTIONS = py2deb.SECTIONS_BY_POLICY[distribution]
        p = py2deb.Py2deb(__appname__)
        p = py2deb.Py2deb(__appname__)
+       p.prettyName = constants.__pretty_app_name__
        p.description = __description__
        p.description = __description__
+       p.bugTracker = "https://bugs.maemo.org/enter_bug.cgi?product=ejpi"
+       p.upgradeDescription = __changelog__.split("\n\n", 1)[0]
        p.author = __author__
        p.mail = __email__
        p.license = "lgpl"
        p.author = __author__
        p.mail = __email__
        p.license = "lgpl"
-       p.depends = {
-               "diablo": "python2.5, python2.5-gtk2",
-               "mer": "python2.6, python-gtk2",
+       p.depends = ", ".join([
+               "python2.6 | python2.5",
+               "python-gtk2 | python2.5-gtk2",
+               "python-xml | python2.5-xml",
+       ])
+       maemoSpecificDepends = ", python-osso | python2.5-osso, python-hildon | python2.5-hildon"
+       p.depends += {
+               "debian": ", python-glade2",
+               "chinook": maemoSpecificDepends,
+               "diablo": maemoSpecificDepends,
+               "fremantle": maemoSpecificDepends + ", python-glade2",
+               "mer": maemoSpecificDepends + ", python-glade2",
+       }[distribution]
+       p.section = {
+               "debian": "accessories",
+               "chinook": "accessories",
+               "diablo": "user/office",
+               "fremantle": "user/office",
+               "mer": "user/office",
        }[distribution]
        }[distribution]
-       p.section = "user/other"
        p.arch = "all"
        p.urgency = "low"
        p.arch = "all"
        p.urgency = "low"
-       p.distribution = "chinook diablo fremantle mer"
+       p.distribution = "chinook diablo fremantle mer debian"
        p.repository = "extras"
        p.changelog = __changelog__
        p.postinstall = __postinstall__
        p.repository = "extras"
        p.changelog = __changelog__
        p.postinstall = __postinstall__
-       p.icon = "26x26-quicknote.png"
+       p.icon = {
+               "debian": "26x26-quicknote.png",
+               "chinook": "26x26-quicknote.png",
+               "diablo": "26x26-quicknote.png",
+               "fremantle": "64x64-quicknote.png", # Fremantle natively uses 48x48
+               "mer": "64x64-quicknote.png",
+       }[distribution]
        p["/usr/bin"] = [ "quicknote.py" ]
        for relPath, files in unflatten_files(find_files(".", "locale")).iteritems():
                fullPath = "/usr/share/locale"
        p["/usr/bin"] = [ "quicknote.py" ]
        for relPath, files in unflatten_files(find_files(".", "locale")).iteritems():
                fullPath = "/usr/share/locale"
@@ -121,7 +149,7 @@ def build_package(distribution):
                        for (oldName, newName) in files
                )
        p["/usr/share/applications/hildon"] = ["quicknote.desktop"]
                        for (oldName, newName) in files
                )
        p["/usr/share/applications/hildon"] = ["quicknote.desktop"]
-       p["/usr/share/dbus-1/services"] = ["quicknote.service"]
+       #p["/usr/share/dbus-1/services"] = ["quicknote.service"]
        p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-quicknote.png|quicknote.png"]
        p["/usr/share/icons/hicolor/40x40/hildon"] = ["40x40-quicknote.png|quicknote.png"]
        p["/usr/share/icons/hicolor/48x48/hildon"] = ["48x48-quicknote.png|quicknote.png"]
        p["/usr/share/icons/hicolor/26x26/hildon"] = ["26x26-quicknote.png|quicknote.png"]
        p["/usr/share/icons/hicolor/40x40/hildon"] = ["40x40-quicknote.png|quicknote.png"]
        p["/usr/share/icons/hicolor/48x48/hildon"] = ["48x48-quicknote.png|quicknote.png"]
@@ -129,8 +157,12 @@ def build_package(distribution):
 
        print p
        print p.generate(
 
        print p
        print p.generate(
-               __version__, __build__, changelog=__changelog__,
-               tar=True, dsc=True, changes=True, build=False, src=True
+               version="%s-%s" % (__version__, __build__),
+               changelog=__changelog__,
+               build=False,
+               tar=True,
+               changes=True,
+               dsc=True,
        )
        print "Building for %s finished" % distribution
 
        )
        print "Building for %s finished" % distribution
 
diff --git a/support/py2deb.py b/support/py2deb.py
new file mode 100644 (file)
index 0000000..6018f91
--- /dev/null
@@ -0,0 +1,989 @@
+#!/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")
+    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")
+        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 __delitem__(self, k):
+        del self.__files[k]
+
+    def __init__(self,
+                    name,
+                    description="no description",
+                    license="gpl",
+                    depends="",
+                    section="utils",
+                    arch="all",
+
+                    url="",
+                    author = None,
+                    mail = None,
+
+                    preinstall = None,
+                    postinstall = None,
+                    preremove = None,
+                    postremove = None
+                ):
+
+        if author is None:
+            author = ("USERNAME" in os.environ) and os.environ["USERNAME"] or None
+            if author is None:
+                author = ("USER" in os.environ) and os.environ["USER"] or "unknown"
+
+        if mail is None:
+            mail = author+"@"+socket.gethostname()
+
+        self.name = name
+        self.prettyName = ""
+        self.description = description
+        self.upgradeDescription = ""
+        self.bugTracker = ""
+        self.license = license
+        self.depends = depends
+        self.recommends = ""
+        self.section = section
+        self.arch = arch
+        self.url = url
+        self.author = author
+        self.mail = mail
+        self.icon = ""
+        self.distribution = ""
+        self.respository = ""
+        self.urgency = "low"
+
+        self.preinstall = preinstall
+        self.postinstall = postinstall
+        self.preremove = preremove
+        self.postremove = postremove
+
+        self.__files={}
+
+    def __repr__(self):
+        name = self.name
+        license = self.license
+        description = self.description
+        depends = self.depends
+        recommends = self.recommends
+        section = self.section
+        arch = self.arch
+        url = self.url
+        author = self.author
+        mail = self.mail
+
+        preinstall = self.preinstall
+        postinstall = self.postinstall
+        preremove = self.preremove
+        postremove = self.postremove
+
+        paths=self.__files.keys()
+        paths.sort()
+        files=[]
+        for path in paths:
+            for file, nfile in self.__files[path]:
+                #~ rfile=os.path.normpath(os.path.join(path, nfile))
+                rfile=os.path.join(path, nfile)
+                if nfile==file:
+                    files.append(rfile)
+                else:
+                    files.append(rfile + " (%s)"%file)
+
+        files.sort()
+        files = "\n".join(files)
+
+
+        lscripts = [    preinstall and "preinst",
+                        postinstall and "postinst",
+                        preremove and "prerm",
+                        postremove and "postrm",
+                    ]
+        scripts = lscripts and ", ".join([i for i in lscripts if i]) or "None"
+        return """
+----------------------------------------------------------------------
+NAME        : %(name)s
+----------------------------------------------------------------------
+LICENSE     : %(license)s
+URL         : %(url)s
+AUTHOR      : %(author)s
+MAIL        : %(mail)s
+----------------------------------------------------------------------
+DEPENDS     : %(depends)s
+RECOMMENDS  : %(recommends)s
+ARCH        : %(arch)s
+SECTION     : %(section)s
+----------------------------------------------------------------------
+DESCRIPTION :
+%(description)s
+----------------------------------------------------------------------
+SCRIPTS : %(scripts)s
+----------------------------------------------------------------------
+FILES :
+%(files)s
+""" % locals()
+
+    def generate(self, version, changelog="", rpm=False, src=False, build=True, tar=False, changes=False, dsc=False):
+        """ generate a deb of version 'version', with or without 'changelog', with or without a rpm
+            (in the current folder)
+            return a list of generated files
+        """
+        if not sum([len(i) for i in self.__files.values()])>0:
+            raise Py2debException("no files are defined")
+
+        if not changelog:
+            changelog="* no changelog"
+
+        name = self.name
+        description = self.description
+        license = self.license
+        depends = self.depends
+        recommends = self.recommends
+        section = self.section
+        arch = self.arch
+        url = self.url
+        distribution = self.distribution
+        repository = self.repository
+        urgency = self.urgency
+        author = self.author
+        mail = self.mail
+        files = self.__files
+        preinstall = self.preinstall
+        postinstall = self.postinstall
+        preremove = self.preremove
+        postremove = self.postremove
+
+        if section not in Py2deb.SECTIONS:
+            raise Py2debException("section '%s' is unknown (%s)" % (section, str(Py2deb.SECTIONS)))
+
+        if arch not in Py2deb.ARCHS:
+            raise Py2debException("arch '%s' is unknown (%s)"% (arch, str(Py2deb.ARCHS)))
+
+        if license not in Py2deb.LICENSES:
+            raise Py2debException("License '%s' is unknown (%s)" % (license, str(Py2deb.LICENSES)))
+
+        # create dates (buildDate, buildDateYear)
+        d=datetime.now()
+        buildDate=d.strftime("%a, %d %b %Y %H:%M:%S +0000")
+        buildDateYear=str(d.year)
+
+        #clean description (add a space before each next lines)
+        description=description.replace("\r", "").strip()
+        description = "\n ".join(description.split("\n"))
+
+        #clean changelog (add 2 spaces before each next lines)
+        changelog=changelog.replace("\r", "").strip()
+        changelog = "\n  ".join(changelog.split("\n"))
+
+        TEMP = ".py2deb_build_folder"
+        DEST = os.path.join(TEMP, name)
+        DEBIAN = os.path.join(DEST, "debian")
+
+        packageContents = locals()
+
+        # let's start the process
+        try:
+            shutil.rmtree(TEMP)
+        except:
+            pass
+
+        os.makedirs(DEBIAN)
+        try:
+            rules=[]
+            dirs=[]
+            for path in files:
+                for ofile, nfile in files[path]:
+                    if os.path.isfile(ofile):
+                        # it's a file
+
+                        if ofile.startswith("/"): # if absolute path
+                            # we need to change dest
+                            dest=os.path.join(DEST, nfile)
+                        else:
+                            dest=os.path.join(DEST, ofile)
+
+                        # copy file to be packaged
+                        destDir = os.path.dirname(dest)
+                        if not os.path.isdir(destDir):
+                            os.makedirs(destDir)
+
+                        shutil.copy2(ofile, dest)
+
+                        ndir = os.path.join(path, os.path.dirname(nfile))
+                        nname = os.path.basename(nfile)
+
+                        # make a line RULES to be sure the destination folder is created
+                        # and one for copying the file
+                        fpath = "/".join(["$(CURDIR)", "debian", name+ndir])
+                        rules.append('mkdir -p "%s"' % fpath)
+                        rules.append('cp -a "%s" "%s"' % (ofile, os.path.join(fpath, nname)))
+
+                        # append a dir
+                        dirs.append(ndir)
+
+                    else:
+                        raise Py2debException("unknown file '' "%ofile) # shouldn't be raised (because controlled before)
+
+            # make rules right
+            rules= "\n\t".join(rules) + "\n"
+            packageContents["rules"] = rules
+
+            # make dirs right
+            dirs= [i[1:] for i in set(dirs)]
+            dirs.sort()
+
+            #==========================================================================
+            # CREATE debian/dirs
+            #==========================================================================
+            open(os.path.join(DEBIAN, "dirs"), "w").write("\n".join(dirs))
+
+            #==========================================================================
+            # CREATE debian/changelog
+            #==========================================================================
+            clog="""%(name)s (%(version)s) stable; urgency=low
+
+  %(changelog)s
+
+ -- %(author)s <%(mail)s>  %(buildDate)s
+""" % packageContents
+
+            open(os.path.join(DEBIAN, "changelog"), "w").write(clog)
+
+            #==========================================================================
+            #Create pre/post install/remove
+            #==========================================================================
+            def mkscript(name, dest):
+                if name and name.strip()!="":
+                    if os.path.isfile(name):    # it's a file
+                        content = file(name).read()
+                    else:   # it's a script
+                        content = name
+                    open(os.path.join(DEBIAN, dest), "w").write(content)
+
+            mkscript(preinstall, "preinst")
+            mkscript(postinstall, "postinst")
+            mkscript(preremove, "prerm")
+            mkscript(postremove, "postrm")
+
+
+            #==========================================================================
+            # CREATE debian/compat
+            #==========================================================================
+            open(os.path.join(DEBIAN, "compat"), "w").write("5\n")
+
+            #==========================================================================
+            # CREATE debian/control
+            #==========================================================================
+            generalParagraphFields = [
+                "Source: %(name)s",
+                "Maintainer: %(author)s <%(mail)s>",
+                "Section: %(section)s",
+                "Priority: extra",
+                "Build-Depends: debhelper (>= 5)",
+                "Standards-Version: 3.7.2",
+            ]
+
+            specificParagraphFields = [
+                "Package: %(name)s",
+                "Architecture: %(arch)s",
+                "Depends: %(depends)s",
+                "Recommends: %(recommends)s",
+                "Description: %(description)s",
+            ]
+
+            if self.prettyName:
+                prettyName = "XSBC-Maemo-Display-Name: %s" % self.prettyName.strip()
+                specificParagraphFields.append("\n  ".join(prettyName.split("\n")))
+
+            if self.bugTracker:
+                bugTracker = "XSBC-Bugtracker: %s" % self.bugTracker.strip()
+                specificParagraphFields.append("\n  ".join(bugTracker.split("\n")))
+
+            if self.upgradeDescription:
+                upgradeDescription = "XSBC-Maemo-Upgrade-Description: %s" % self.upgradeDescription.strip()
+                specificParagraphFields.append("\n  ".join(upgradeDescription.split("\n")))
+
+            if self.icon:
+                f = open(self.icon, "rb")
+                try:
+                    rawIcon = f.read()
+                finally:
+                    f.close()
+                uueIcon = base64.b64encode(rawIcon)
+                uueIconLines = []
+                for i, c in enumerate(uueIcon):
+                    if i % 60 == 0:
+                        uueIconLines.append("")
+                    uueIconLines[-1] += c
+                uueIconLines[0:0] = ("XSBC-Maemo-Icon-26:", )
+                specificParagraphFields.append("\n  ".join(uueIconLines))
+
+            generalParagraph = "\n".join(generalParagraphFields)
+            specificParagraph = "\n".join(specificParagraphFields)
+            controlContent = "\n\n".join((generalParagraph, specificParagraph)) % packageContents
+            open(os.path.join(DEBIAN, "control"), "w").write(controlContent)
+
+            #==========================================================================
+            # CREATE debian/copyright
+            #==========================================================================
+            packageContents["txtLicense"] = LICENSE_AGREEMENT[license]
+            packageContents["pv"] =__version__
+            txt="""This package was py2debianized(%(pv)s) by %(author)s <%(mail)s> on
+%(buildDate)s.
+
+It was downloaded from %(url)s
+
+Upstream Author: %(author)s <%(mail)s>
+
+Copyright: %(buildDateYear)s by %(author)s
+
+License:
+
+%(txtLicense)s
+
+The Debian packaging is (C) %(buildDateYear)s, %(author)s <%(mail)s> and
+is licensed under the GPL, see above.
+
+
+# Please also look if there are files or directories which have a
+# different copyright/license attached and list them here.
+""" % packageContents
+            open(os.path.join(DEBIAN, "copyright"), "w").write(txt)
+
+            #==========================================================================
+            # CREATE debian/rules
+            #==========================================================================
+            txt="""#!/usr/bin/make -f
+# -*- makefile -*-
+# Sample debian/rules that uses debhelper.
+# This file was originally written by Joey Hess and Craig Small.
+# As a special exception, when this file is copied by dh-make into a
+# dh-make output file, you may use that output file without restriction.
+# This special exception was added by Craig Small in version 0.37 of dh-make.
+
+# Uncomment this to turn on verbose mode.
+#export DH_VERBOSE=1
+
+
+
+
+CFLAGS = -Wall -g
+
+ifneq (,$(findstring noopt,$(DEB_BUILD_OPTIONS)))
+       CFLAGS += -O0
+else
+       CFLAGS += -O2
+endif
+
+configure: configure-stamp
+configure-stamp:
+       dh_testdir
+       # Add here commands to configure the package.
+
+       touch configure-stamp
+
+
+build: build-stamp
+
+build-stamp: configure-stamp
+       dh_testdir
+       touch build-stamp
+
+clean:
+       dh_testdir
+       dh_testroot
+       rm -f build-stamp configure-stamp
+       dh_clean
+
+install: build
+       dh_testdir
+       dh_testroot
+       dh_clean -k
+       dh_installdirs
+
+       # ======================================================
+       #$(MAKE) DESTDIR="$(CURDIR)/debian/%(name)s" install
+       mkdir -p "$(CURDIR)/debian/%(name)s"
+
+       %(rules)s
+       # ======================================================
+
+# Build architecture-independent files here.
+binary-indep: build install
+# We have nothing to do by default.
+
+# Build architecture-dependent files here.
+binary-arch: build install
+       dh_testdir
+       dh_testroot
+       dh_installchangelogs debian/changelog
+       dh_installdocs
+       dh_installexamples
+#      dh_install
+#      dh_installmenu
+#      dh_installdebconf
+#      dh_installlogrotate
+#      dh_installemacsen
+#      dh_installpam
+#      dh_installmime
+#      dh_python
+#      dh_installinit
+#      dh_installcron
+#      dh_installinfo
+       dh_installman
+       dh_link
+       dh_strip
+       dh_compress
+       dh_fixperms
+#      dh_perl
+#      dh_makeshlibs
+       dh_installdeb
+       dh_shlibdeps
+       dh_gencontrol
+       dh_md5sums
+       dh_builddeb
+
+binary: binary-indep binary-arch
+.PHONY: build clean binary-indep binary-arch binary install configure
+""" % packageContents
+            open(os.path.join(DEBIAN, "rules"), "w").write(txt)
+            os.chmod(os.path.join(DEBIAN, "rules"), 0755)
+
+            ###########################################################################
+            ###########################################################################
+            ###########################################################################
+
+            generatedFiles = []
+
+            if build:
+                #http://www.debian.org/doc/manuals/maint-guide/ch-build.fr.html
+                ret = os.system('cd "%(DEST)s"; dpkg-buildpackage -tc -rfakeroot -us -uc' % packageContents)
+                if ret != 0:
+                    raise Py2debException("buildpackage failed (see output)")
+
+                l=glob("%(TEMP)s/%(name)s*.deb" % packageContents)
+                if len(l) != 1:
+                    raise Py2debException("didn't find builded deb")
+
+                tdeb = l[0]
+                deb = os.path.basename(tdeb)
+                shutil.move(tdeb, deb)
+
+                generatedFiles = [deb, ]
+
+                if rpm:
+                    rpmFilename = deb2rpm(deb)
+                    generatedFiles.append(rpmFilename)
+
+                if src:
+                    tarFilename = py2src(TEMP, name)
+                    generatedFiles.append(tarFilename)
+
+            if tar:
+                tarFilename = py2tar(DEST, TEMP, name, version)
+                generatedFiles.append(tarFilename)
+
+            if dsc:
+                dscFilename = py2dsc(TEMP, name, version, depends, author, mail, arch)
+                generatedFiles.append(dscFilename)
+
+            if changes:
+                changesFilenames = py2changes(packageContents)
+                generatedFiles.extend(changesFilenames)
+
+            return generatedFiles
+
+        #~ except Exception,m:
+            #~ raise Py2debException("build error :"+str(m))
+
+        finally:
+            if Py2deb.clear:
+                shutil.rmtree(TEMP)
+
+
+if __name__ == "__main__":
+    try:
+        os.chdir(os.path.dirname(sys.argv[0]))
+    except:
+        pass
+
+    p=Py2deb("python-py2deb")
+    p.description="Generate simple deb(/rpm/tgz) from python (2.4, 2.5 and 2.6)"
+    p.url = "http://www.manatlan.com/page/py2deb"
+    p.author=__author__
+    p.mail=__mail__
+    p.depends = "dpkg-dev, fakeroot, alien, python"
+    p.section="python"
+    p["/usr/lib/python2.6/dist-packages"] = ["py2deb.py", ]
+    p["/usr/lib/python2.5/site-packages"] = ["py2deb.py", ]
+    p["/usr/lib/python2.4/site-packages"] = ["py2deb.py", ]
+    #~ p.postinstall = "s.py"
+    #~ p.preinstall = "s.py"
+    #~ p.postremove = "s.py"
+    #~ p.preremove = "s.py"
+    print p
+    print p.generate(__version__, changelog = __doc__, src=True)
diff --git a/support/todo.py b/support/todo.py
new file mode 100755 (executable)
index 0000000..90cbd04
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+from __future__ import with_statement
+import itertools
+
+
+verbose = False
+
+
+def tag_parser(file, tag):
+       """
+       >>> nothing = []
+       >>> for todo in tag_parser(nothing, "@todo"):
+       ...     print todo
+       ...
+       >>> one = ["@todo Help!"]
+       >>> for todo in tag_parser(one, "@todo"):
+       ...     print todo
+       ...
+       1: @todo Help!
+       >>> mixed = ["one", "@todo two", "three"]
+       >>> for todo in tag_parser(mixed, "@todo"):
+       ...     print todo
+       ...
+       2: @todo two
+       >>> embedded = ["one @todo two", "three"]
+       >>> for todo in tag_parser(embedded, "@todo"):
+       ...     print todo
+       ...
+       1: @todo two
+       >>> continuation = ["one", "@todo two", " three"]
+       >>> for todo in tag_parser(continuation, "@todo"):
+       ...     print todo
+       ...
+       2: @todo two three
+       >>> series = ["one", "@todo two", "@todo three"]
+       >>> for todo in tag_parser(series, "@todo"):
+       ...     print todo
+       ...
+       2: @todo two
+       3: @todo three
+       """
+       currentTodo = []
+       prefix = None
+       for lineNumber, line in enumerate(file):
+               column = line.find(tag)
+               if column != -1:
+                       if currentTodo:
+                               yield "\n".join (currentTodo)
+                       prefix = line[0:column]
+                       currentTodo = ["%d: %s" % (lineNumber+1, line[column:].strip())]
+               elif prefix is not None and len(prefix)+1 < len(line) and line.startswith(prefix) and line[len(prefix)].isspace():
+                       currentTodo.append (line[len(prefix):].rstrip())
+               elif currentTodo:
+                       yield "\n".join (currentTodo)
+                       currentTodo = []
+                       prefix = None
+       if currentTodo:
+               yield "\n".join (currentTodo)
+
+
+def tag_finder(filename, tag):
+       todoList = []
+
+       with open(filename) as file:
+               body = "\n".join (tag_parser(file, tag))
+       passed = not body
+       if passed:
+               output = "No %s's for %s" % (tag, filename) if verbose else ""
+       else:
+               header = "%s's for %s:\n" % (tag, filename) if verbose else ""
+               output = header + body
+               output += "\n" if verbose else ""
+
+       return (passed, output)
+
+
+if __name__ == "__main__":
+       import sys
+       import os
+       import optparse
+
+       opar = optparse.OptionParser()
+       opar.add_option("-v", "--verbose", dest="verbose", help="Toggle verbosity", action="store_true", default=False)
+       options, args = opar.parse_args(sys.argv[1:])
+       verbose = options.verbose
+
+       bugsAsError = True
+       todosAsError = False
+
+       completeOutput = []
+       allPassed = True
+       for filename in args:
+               bugPassed, bugOutput = tag_finder(filename, "@bug")
+               todoPassed, todoOutput = tag_finder(filename, "@todo")
+               output = "\n".join ([bugOutput, todoOutput])
+               if (not bugPassed and bugsAsError) or (not todoPassed and todosAsError):
+                       allPassed = False
+               output = output.strip()
+               if output:
+                       completeOutput.append(filename+":\n"+output+"\n\n")
+       print "\n".join(completeOutput)
+       
+       sys.exit(0 if allPassed else 1);