Setting up a trunk for branching
authorepage <eopage@byu.net>
Fri, 7 May 2010 03:07:01 +0000 (03:07 +0000)
committerepage <eopage@byu.net>
Fri, 7 May 2010 03:07:01 +0000 (03:07 +0000)
git-svn-id: file:///svnroot/nqaap/trunk@9 00ff6f12-f5ab-46b1-af0e-967c86d3154f

24 files changed:
build.sh [new file with mode: 0644]
build_nqaap.py [new file with mode: 0644]
build_nqaap_maemo5.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/.ropeproject/config.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/Audiobook.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/Browser.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/CallMonitor.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/FileStorage.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/Gui.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/Help/nqaap.html [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/NoCover.png [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/Player.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/SimpleGStreamer.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/__init__.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/constants.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/gtk_toolbox.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/hildonize.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/nqaap.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/nqaap_gtk.py [new file with mode: 0644]
src/opt/Nqa-Audiobook-player/settings.py [new file with mode: 0644]
src/usr/share/applications/hildon/nqaap.desktop [new file with mode: 0644]
src/usr/share/icons/hicolor/48x48/hildon/nqaap.png [new file with mode: 0644]
src/usr/share/icons/hicolor/scalable/hildon/nqaap.png [new file with mode: 0644]

diff --git a/build.sh b/build.sh
new file mode 100644 (file)
index 0000000..0a7610c
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,7 @@
+#!/bin/sh\r
+\r
+rm -r ~/nqaap;\r
+svn export http://www.pengworld.com/python/n900/Audiobook/nqaap/ ~/nqaap\r
+chmod +x ~/nqaap/src/opt/Nqa-Audiobook-player/nqaap.py\r
+python ~/nqaap/build_nqaap.py\r
+\r
diff --git a/build_nqaap.py b/build_nqaap.py
new file mode 100644 (file)
index 0000000..09e1de5
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/python2.5\r
+ # -*- coding: utf-8 -*-\r
+ ## This program is free software; you can redistribute it and/or modify\r
+ ## it under the terms of the GNU General Public License as published\r
+ ## by the Free Software Foundation; version 2 only.\r
+ ##\r
+ ## This program is distributed in the hope that it will be useful,\r
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ ## GNU General Public License for more details.\r
+ ##\r
+import py2deb\r
+import os\r
+if __name__ == "__main__":\r
+    try:\r
+        os.chdir(os.path.dirname(sys.argv[0]))\r
+    except:\r
+        pass\r
+    print\r
+    p=py2deb.Py2deb("nqaap") #This is the package name and MUST be in\r
+                               #lowercase! (using e.g. "mClock" fails\r
+                               #miserably...)\r
+    p.description="Very simple Audiobook player. \nSupports playing, pausing, seeking (sort of) and saving state when changing book/closing.\nPlays books arranged as dirs under myDocs/Audiobooks"\r
+    p.author="Soeren 'Pengman' Pedersen"\r
+    p.mail="pengmeister@gmail.com"\r
+    p.depends = "python2.5, python2.5-gtk2, python-gst0.10"\r
+    p.section="user/multimedia"\r
+    p.icon = "/usr/share/icons/hicolor/48x48/hildon/nqaap.png"\r
+    p.arch="all"                #should be all for python, any for all arch\r
+    p.urgency="low"             #not used in maemo onl for deb os\r
+    p.distribution="fremantle"\r
+    p.repository="extras-devel"\r
+    p.xsbc_bugtracker="http://talk.maemo.org/showthread.php?p=619738"\r
+    #  p.postinstall="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post install script\r
+    #  p.postremove="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post remove script\r
+    #  p.preinstall="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre install script\r
+    #  p.preremove="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre remove script\r
+    version = "0.8.0"           #Version of your software, e.g. "1.2.0" or "0.8.2"\r
+    build = "2" #Build number, e.g. "1" for the first build of this\r
+                                #version of your software. Increment\r
+                                #for later re-builds of the same\r
+                                #version of your software.  Text with\r
+                                #changelog information to be displayed\r
+                                #in the package "Details" tab of the\r
+                                #Maemo Application Manager\r
+    changeloginformation = "Merged changes from EPage (proper changelog later)\nNew Icon by Strutten."\r
+    # 0.7.2 : Seek bar now responds to clicks (rather than drags)\nFixed bug with wrong text showing on button after changed chapter.\r
+    # 0.7.1 : Fixed crash when current points to non existing book\r
+    # 0.7.0 : Now ignores pressed outside the chapter selection menu\nAdded help\r
+    # 0.6.1 : Fixed bug that prevented running on devices without Audiobook folder.\r
+    #         Added tip on where to place audiobooks.\r
+    # 0.6.0 : Now also plays .mp3 files\r
+    # 0.5.0 : Second release. Now shows which chapter is playing, and scrolls to it when changing.\r
+    # 0.4.9 : First release. Now it should work\r
+    #  \r
+    dir_name = "src" #Name of the subfolder containing your package\r
+                                #source files\r
+                                #(e.g. usr\share\icons\hicolor\scalable\myappicon.svg,\r
+                                #usr\lib\myapp\somelib.py). We suggest\r
+                                #to leave it named src in all projects\r
+                                #and will refer to that in the wiki\r
+                                #article on maemo.org\r
+                                \r
+    #Thanks to DareTheHair from talk.maemo.org for this snippet that\r
+    #recursively builds the file list\r
+    for root, dirs, files in os.walk(dir_name):\r
+        real_dir = root[len(dir_name):]\r
+        if '.' in real_dir:\r
+            continue # if some part of the dirname contains '.' we\r
+                                        # ignore all files (avoid .svn\r
+                                        # and others)\r
+        fake_file = []\r
+        for f in files:\r
+            fake_file.append(root + os.sep + f + "|" + f)\r
+        if len(fake_file) > 0:\r
+            p[real_dir] = fake_file\r
+    print p\r
+    r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True)\r
diff --git a/build_nqaap_maemo5.py b/build_nqaap_maemo5.py
new file mode 100644 (file)
index 0000000..5854d83
--- /dev/null
@@ -0,0 +1,82 @@
+#!/usr/bin/python2.5\r
+ # -*- coding: utf-8 -*-\r
+ ## This program is free software; you can redistribute it and/or modify\r
+ ## it under the terms of the GNU General Public License as published\r
+ ## by the Free Software Foundation; version 2 only.\r
+ ##\r
+ ## This program is distributed in the hope that it will be useful,\r
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ ## GNU General Public License for more details.\r
+ ##\r
+import py2deb\r
+import os\r
+if __name__ == "__main__":\r
+    try:\r
+        os.chdir(os.path.dirname(sys.argv[0]))\r
+    except:\r
+        pass\r
+    print\r
+    p=py2deb.Py2deb("nqaap") #This is the package name and MUST be in\r
+                               #lowercase! (using e.g. "mClock" fails\r
+                               #miserably...)\r
+    p.description="Very simple Audiobook player. \nSupports playing, pausing, seeking (sort of) and saving state when changing book/closing.\nPlays books arranged as dirs under myDocs/Audiobooks"\r
+    p.author="Soeren 'Pengman' Pedersen"\r
+    p.mail="pengmeister@gmail.com"\r
+    p.depends = "python2.5, python2.5-gtk2, python2.5-dbus, python2.5-telepathy, python2.5-gobject, python-gst0.10"\r
+    p.section="user/multimedia"\r
+    p.icon = "/usr/share/icons/hicolor/48x48/hildon/nqaap.png"\r
+    p.arch="all"                #should be all for python, any for all arch\r
+    p.urgency="low"             #not used in maemo onl for deb os\r
+    p.distribution="fremantle"\r
+    p.repository="extras-devel"\r
+    p.xsbc_bugtracker="http://talk.maemo.org/showthread.php?p=619738"\r
+    #  p.postinstall="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post install script\r
+    #  p.postremove="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post remove script\r
+    #  p.preinstall="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre install script\r
+    #  p.preremove="""#!/bin/sh\r
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre remove script\r
+    version = "0.8.0"           #Version of your software, e.g. "1.2.0" or "0.8.2"\r
+    build = "2" #Build number, e.g. "1" for the first build of this\r
+                                #version of your software. Increment\r
+                                #for later re-builds of the same\r
+                                #version of your software.  Text with\r
+                                #changelog information to be displayed\r
+                                #in the package "Details" tab of the\r
+                                #Maemo Application Manager\r
+    changeloginformation = "Merged changes from EPage (proper changelog later)\nNew Icon by Strutten."\r
+    # 0.7.2 : Seek bar now responds to clicks (rather than drags)\nFixed bug with wrong text showing on button after changed chapter.\r
+    # 0.7.1 : Fixed crash when current points to non existing book\r
+    # 0.7.0 : Now ignores pressed outside the chapter selection menu\nAdded help\r
+    # 0.6.1 : Fixed bug that prevented running on devices without Audiobook folder.\r
+    #         Added tip on where to place audiobooks.\r
+    # 0.6.0 : Now also plays .mp3 files\r
+    # 0.5.0 : Second release. Now shows which chapter is playing, and scrolls to it when changing.\r
+    # 0.4.9 : First release. Now it should work\r
+    #  \r
+    dir_name = "src" #Name of the subfolder containing your package\r
+                                #source files\r
+                                #(e.g. usr\share\icons\hicolor\scalable\myappicon.svg,\r
+                                #usr\lib\myapp\somelib.py). We suggest\r
+                                #to leave it named src in all projects\r
+                                #and will refer to that in the wiki\r
+                                #article on maemo.org\r
+                                \r
+    #Thanks to DareTheHair from talk.maemo.org for this snippet that\r
+    #recursively builds the file list\r
+    for root, dirs, files in os.walk(dir_name):\r
+        real_dir = root[len(dir_name):]\r
+        if '.' in real_dir:\r
+            continue # if some part of the dirname contains '.' we\r
+                                        # ignore all files (avoid .svn\r
+                                        # and others)\r
+        fake_file = []\r
+        for f in files:\r
+            fake_file.append(root + os.sep + f + "|" + f)\r
+        if len(fake_file) > 0:\r
+            p[real_dir] = fake_file\r
+    print p\r
+    r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True)\r
diff --git a/src/opt/Nqa-Audiobook-player/.ropeproject/config.py b/src/opt/Nqa-Audiobook-player/.ropeproject/config.py
new file mode 100644 (file)
index 0000000..ffebcd4
--- /dev/null
@@ -0,0 +1,85 @@
+# The default ``config.py``
+
+
+def set_prefs(prefs):
+    """This function is called before opening the project"""
+
+    # Specify which files and folders to ignore in the project.
+    # Changes to ignored resources are not added to the history and
+    # VCSs.  Also they are not returned in `Project.get_files()`.
+    # Note that ``?`` and ``*`` match all characters but slashes.
+    # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
+    # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
+    # '.svn': matches 'pkg/.svn' and all of its children
+    # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
+    # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
+    prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
+                                  '.hg', '.svn', '_svn', '.git']
+
+    # Specifies which files should be considered python files.  It is
+    # useful when you have scripts inside your project.  Only files
+    # ending with ``.py`` are considered to be python files by
+    # default.
+    #prefs['python_files'] = ['*.py']
+
+    # Custom source folders:  By default rope searches the project
+    # for finding source folders (folders that should be searched
+    # for finding modules).  You can add paths to that list.  Note
+    # that rope guesses project source folders correctly most of the
+    # time; use this if you have any problems.
+    # The folders should be relative to project root and use '/' for
+    # separating folders regardless of the platform rope is running on.
+    # 'src/my_source_folder' for instance.
+    #prefs.add('source_folders', 'src')
+
+    # You can extend python path for looking up modules
+    #prefs.add('python_path', '~/python/')
+
+    # Should rope save object information or not.
+    prefs['save_objectdb'] = True
+    prefs['compress_objectdb'] = False
+
+    # If `True`, rope analyzes each module when it is being saved.
+    prefs['automatic_soa'] = True
+    # The depth of calls to follow in static object analysis
+    prefs['soa_followed_calls'] = 0
+
+    # If `False` when running modules or unit tests "dynamic object
+    # analysis" is turned off.  This makes them much faster.
+    prefs['perform_doa'] = True
+
+    # Rope can check the validity of its object DB when running.
+    prefs['validate_objectdb'] = True
+
+    # How many undos to hold?
+    prefs['max_history_items'] = 32
+
+    # Shows whether to save history across sessions.
+    prefs['save_history'] = True
+    prefs['compress_history'] = False
+
+    # Set the number spaces used for indenting.  According to
+    # :PEP:`8`, it is best to use 4 spaces.  Since most of rope's
+    # unit-tests use 4 spaces it is more reliable, too.
+    prefs['indent_size'] = 4
+
+    # Builtin and c-extension modules that are allowed to be imported
+    # and inspected by rope.
+    prefs['extension_modules'] = []
+
+    # Add all standard c-extensions to extension_modules list.
+    prefs['import_dynload_stdmods'] = True
+
+    # If `True` modules with syntax errors are considered to be empty.
+    # The default value is `False`; When `False` syntax errors raise
+    # `rope.base.exceptions.ModuleSyntaxError` exception.
+    prefs['ignore_syntax_errors'] = False
+
+    # If `True`, rope ignores unresolvable imports.  Otherwise, they
+    # appear in the importing namespace.
+    prefs['ignore_bad_imports'] = False
+
+
+def project_opened(project):
+    """This function is called after opening the project"""
+    # Do whatever you like here!
diff --git a/src/opt/Nqa-Audiobook-player/Audiobook.py b/src/opt/Nqa-Audiobook-player/Audiobook.py
new file mode 100644 (file)
index 0000000..00fac87
--- /dev/null
@@ -0,0 +1,176 @@
+from __future__ import with_statement
+
+import os
+
+import logging
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class Audiobook(object):
+
+    def __init__(self, path, current_chapter = 0):
+        self.title = ""
+        self._coverPath = ""
+        self._chapterPaths = []
+        self.current_chapter = current_chapter
+
+        if is_playlist_book(path):
+            self._scan_index(path)
+        elif is_dir_book(path):
+            self._scan_dir(path)
+        elif is_single_chapter(path):
+            self._scan_chapter(path)
+        else:
+            _moduleLogger.info("Audiobook not found in path: " + path)
+            raise IOError("Audiobook directory not found")
+
+        if len(self._chapterPaths) <= self.current_chapter:
+                       _moduleLogger.warning(
+                               "Audiobook chapter out of range (%s/%s)" % (
+                                       self.current_chapter, len(self._chapterPaths)
+                               )
+                       )
+                       self.current_chapter = 0
+
+    @property
+    def chapters(self):
+        return self._chapterPaths
+
+    def get_current_chapter(self):
+        return self._chapterPaths[self.current_chapter]
+
+    def set_chapter(self, chapter):
+        if chapter in self.chapters:
+            self.current_chapter = self.chapters.index(chapter)
+        else:
+            raise Exception("Unknown chapter set")
+
+    def get_previous_chapter(self):
+        """
+        @returns the file name for the next chapter, without path
+        """
+        if 0 == self.current_chapter:
+            return False
+        else:
+            self.current_chapter -= 1
+            return self._chapterPaths[self.current_chapter]
+
+    def get_next_chapter(self):
+        """
+        @returns the file name for the next chapter, without path
+        """
+        if len(self._chapterPaths) == self.current_chapter:
+            return False
+        else:
+            self.current_chapter += 1
+            return self._chapterPaths[self.current_chapter]
+
+    def get_cover_img(self):
+        if self._coverPath:
+            return self._coverPath
+        else:
+            return "%s/NoCover.png" % os.path.dirname(__file__)
+
+    def _scan_dir(self, root):
+        self.title = os.path.split(root)[-1]
+        dirContent = (
+            os.path.join(root, f)
+            for f in os.listdir(root)
+            if not f.startswith(".")
+        )
+
+        files = [
+            path
+            for path in dirContent
+            if os.path.isfile(os.path.join(root, path))
+        ]
+
+        images = [
+            path
+            for path in files
+            if path.rsplit(".", 1)[-1] in ["png", "gif", "jpg", "jpeg"]
+        ]
+        if 0 < len(images):
+            self.cover = images[0]
+
+        self._chapterPaths = [
+            path
+            for path in files
+            if is_single_chapter(path)
+        ]
+        self._chapterPaths.sort()
+
+    def _scan_chapter(self, file):
+        self._chapterPaths = [file]
+
+    def _scan_playlist(self, file):
+        root = os.path.dirname(file)
+        self.title = os.path.basename(file).rsplit(".")[0]
+
+        with open(file, 'r') as f:
+            for line in f:
+                if line.startswith("#"):
+                    continue
+                path = line
+                if not os.path.isabs(path):
+                    path = os.path.normpath(os.path.join(root, path))
+                self._chapterPaths.append(path)
+        # Not sorting, assuming the file is in the desired order
+
+    def _scan_index(self, file):
+        import unicodedata
+
+        # Reading file
+        looking_for_title = False
+        looking_for_cover = False
+        looking_for_chapters = False
+
+        with open(file, 'r') as f:
+            for line in f:
+                # title
+                ascii = unicodedata.normalize('NFKD', unicode(line, "latin-1")).encode('ascii', 'ignore')
+                print line[:-1], "PIC\n" in line, line in "#PIC"
+                if "#BOOK" in line:
+                    looking_for_title = True
+                    continue
+                if looking_for_title:
+                    self.title = line[:-1]
+                    looking_for_title = False
+                if "#PIC" in line:
+                    looking_for_cover = True
+                    continue
+                if looking_for_cover:
+                    self.cover = line[:-1]
+                    looking_for_cover = False
+                if "#TRACKS" in line:
+                    looking_for_chapters = True
+                    continue
+                if looking_for_chapters:
+                    if "#CHAPTERS" in line:
+                        break           # no further information needed
+                    self.chapters.append(line.split(':')[0])
+
+
+def is_dir_book(path):
+    return os.path.isdir(path)
+
+
+def is_playlist_book(path):
+    return path.rsplit(".", 1)[-1] in ["m3u"]
+
+
+def is_single_chapter(path):
+    return path.rsplit(".", 1)[-1] in ["awb", "mp3", "spx", "ogg", "ac3", "wav"]
+
+
+def is_book(path):
+    if is_dir_book(path):
+        return True
+    elif is_playlist_book(path):
+        return True
+    elif is_single_chapter(path):
+        return True
+    else:
+        return False
diff --git a/src/opt/Nqa-Audiobook-player/Browser.py b/src/opt/Nqa-Audiobook-player/Browser.py
new file mode 100644 (file)
index 0000000..36d4e34
--- /dev/null
@@ -0,0 +1,5 @@
+import os\r
+\r
+\r
+def open(url):\r
+    os.system('dbus-send --system --type=method_call --dest="com.nokia.osso_browser" /com/nokia/osso_browser/request com.nokia.osso_browser.load_url string:"%s"' % url)\r
diff --git a/src/opt/Nqa-Audiobook-player/CallMonitor.py b/src/opt/Nqa-Audiobook-player/CallMonitor.py
new file mode 100644 (file)
index 0000000..97be5a7
--- /dev/null
@@ -0,0 +1,145 @@
+import logging
+
+import gobject
+import dbus
+import telepathy
+
+import gtk_toolbox
+
+
+_moduleLogger = logging.getLogger(__name__)
+DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties'
+
+
+class NewChannelSignaller(object):
+
+       def __init__(self, on_new_channel):
+               self._sessionBus = dbus.SessionBus()
+               self._on_user_new_channel = on_new_channel
+
+       def start(self):
+               self._sessionBus.add_signal_receiver(
+                       self._on_new_channel,
+                       "NewChannel",
+                       "org.freedesktop.Telepathy.Connection",
+                       None,
+                       None
+               )
+
+       def stop(self):
+               self._sessionBus.remove_signal_receiver(
+                       self._on_new_channel,
+                       "NewChannel",
+                       "org.freedesktop.Telepathy.Connection",
+                       None,
+                       None
+               )
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_new_channel(
+               self, channelObjectPath, channelType, handleType, handle, supressHandler
+       ):
+               connObjectPath = channel_path_to_conn_path(channelObjectPath)
+               serviceName = path_to_service_name(channelObjectPath)
+               try:
+                       self._on_user_new_channel(
+                               self._sessionBus, serviceName, connObjectPath, channelObjectPath, channelType
+                       )
+               except Exception:
+                       _moduleLogger.exception("Blocking exception from being passed up")
+
+
+class ChannelClosed(object):
+
+       def __init__(self, bus, conn, chan, on_closed):
+               self.__on_closed = on_closed
+
+               chan[telepathy.interfaces.CHANNEL].connect_to_signal(
+                       "Closed",
+                       self._on_closed,
+               )
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_closed(self):
+               self.__on_closed(self)
+
+
+class CallMonitor(gobject.GObject):
+
+       __gsignals__ = {
+               'call_start' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (),
+               ),
+               'call_end' : (
+                       gobject.SIGNAL_RUN_LAST,
+                       gobject.TYPE_NONE,
+                       (),
+               ),
+       }
+
+       def __init__(self):
+               gobject.GObject.__init__(self)
+               self._isActive = False
+               self._newChannelMonitor = NewChannelSignaller(self._on_new_channel)
+               self._channelClosedMonitors = []
+
+       def start(self):
+               self._isActive = True
+               self._newChannelMonitor.start()
+
+       def stop(self):
+               self._isActive = False
+               self._newChannelMonitor.stop()
+
+       def _on_new_channel(self, sessionBus, serviceName, connObjectPath, channelObjectPath, channelType):
+               if not self._isActive:
+                       return
+
+               if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA:
+                       return
+
+               cmName = cm_from_path(connObjectPath)
+               conn = telepathy.client.Connection(serviceName, connObjectPath)
+               try:
+                       chan = telepathy.client.Channel(serviceName, channelObjectPath)
+               except dbus.exceptions.UnknownMethodException:
+                       _moduleLogger.exception("Client might not have implemented a deprecated method")
+                       return
+
+               missDetection = ChannelClosed(
+                       sessionBus, conn, chan, self._on_close
+               )
+               self._outstandingRequests.append(missDetection)
+               if len(self._outstandingRequests) == 1:
+                       self.emit("call_start")
+
+       def _on_close(self, channelCloseMonitor):
+               self._outstandingRequests.remove(channelCloseMonitor)
+               if not self._outstandingRequests:
+                       self.emit("call_stop")
+
+
+def channel_path_to_conn_path(channelObjectPath):
+       """
+       >>> channel_path_to_conn_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       '/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME'
+       """
+       return channelObjectPath.rsplit("/", 1)[0]
+
+
+def path_to_service_name(path):
+       """
+       >>> path_to_service_name("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       'org.freedesktop.Telepathy.ConnectionManager.theonering.gv.USERNAME'
+       """
+       return ".".join(path[1:].split("/")[0:7])
+
+
+def cm_from_path(path):
+       """
+       >>> cm_from_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1")
+       'theonering'
+       """
+       return path[1:].split("/")[4]
diff --git a/src/opt/Nqa-Audiobook-player/FileStorage.py b/src/opt/Nqa-Audiobook-player/FileStorage.py
new file mode 100644 (file)
index 0000000..44cfc36
--- /dev/null
@@ -0,0 +1,68 @@
+from __future__ import with_statement   # enable with
+
+import os
+
+import logging
+
+
+log = logging.getLogger(__name__)
+
+
+class FileStorage(object):
+
+    def __init__(self, path="~/.SornPlayer/"):
+        # Setup dir
+        log.info("init filestorage")
+        self.path = path
+        self.books_path = os.path.join(self.path, "books/")
+        if not os.path.isdir(self.books_path):
+            os.makedirs(self.books_path)
+
+        # Read config file
+        self.conf = os.path.join(self.path, "current")
+        self.selected = None
+
+        if os.path.isfile(self.conf):
+            with open(self.conf) as f:
+                self.selected = f.readline()
+
+        # Read current book file
+
+    def get_selected(self):
+        """returns the currently selected book"""
+        return self.selected
+
+    def select_book(self, bookName):
+        """ Sets the book as the currently playing, and adds it to the
+        database if it is not already there"""
+        book_file = os.path.join(self.books_path, bookName)
+        if not os.path.isfile(book_file):
+            with open(book_file, 'w') as f:
+                f.write("0\n") #Current chapter
+                f.write("0\n") #Current position
+
+        self.selected = bookName
+        with open(self.conf, 'w') as f:
+            f.write(self.selected) #
+
+    def set_time(self, chapter, position):
+        """ Sets the current time for the book that is currently selected"""
+        try:
+            book_file = os.path.join(self.books_path, self.selected)
+            log.debug("writing time (%s, %s) to: %s"%( chapter, position, book_file ))
+            with open(book_file, 'w') as f:
+                f.write(str(int(chapter)) + "\n") #Current chapter
+                f.write(str(int(position)) + "\n") #Current position
+        except:
+            log.error("Unable to save to file: %s" % book_file)
+
+    def get_time(self):
+        """Returns the current saved time for the current selected book"""
+        chapter, position = 0 , 0
+        book_file = os.path.join(self.books_path, self.selected)
+        log.debug("getting time from: " + book_file)
+        with open(book_file, 'r') as f:
+            chapter = int(f.readline())
+            position = int(f.readline())
+
+        return chapter, position
diff --git a/src/opt/Nqa-Audiobook-player/Gui.py b/src/opt/Nqa-Audiobook-player/Gui.py
new file mode 100644 (file)
index 0000000..0f51f36
--- /dev/null
@@ -0,0 +1,594 @@
+from __future__ import with_statement
+
+import os
+import ConfigParser
+import logging
+
+import gobject
+import gtk
+
+import constants
+import hildonize
+import gtk_toolbox
+import Browser
+import CallMonitor
+import settings
+
+if hildonize.IS_FREMANTLE_SUPPORTED:
+    # I don't normally do this but I want to error as loudly as possibly when an issue arises
+    import hildon
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class Gui(object):
+
+    def __init__(self):
+        _moduleLogger.info("Starting GUI")
+        self._callMonitor = CallMonitor.CallMonitor()
+        self.__settingsWindow = None
+        self.__settingsManager = None
+        self._bookSelection = []
+        self._bookSelectionIndex = -1
+        self._chapterSelection = []
+        self._chapterSelectionIndex = -1
+        self._sleepSelection = ["0", "1", "10", "20", "30", "60"]
+        self._sleepSelectionIndex = 0
+
+        self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
+        self.__isPortrait = False
+
+        self.controller = None
+        self.sleep_timer = None
+        self.auto_chapter_selected = False # true if we are in the
+                                           # midle of an automatic
+                                           # chapter change
+
+        self.ignore_next_chapter_change = False
+        # set up gui
+        self.setup()
+        self._callMonitor.connect("call_start", self.__on_call_started)
+        self._callMonitor.start()
+
+    def setup(self):
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            gtk.set_application_name(constants.__pretty_app_name__) # window title
+        self._app = hildonize.get_app_class()()
+        self.win = gtk.Window()
+        self.win = hildonize.hildonize_window(self._app, self.win)
+
+        # Cover image
+        self.cover = gtk.Image()
+
+        # Controls:
+
+        # Label that hold the title of the book,and maybe the chapter
+        self.title = gtk.Label("Select a book to start listening")
+        self.title.set_justify(gtk.JUSTIFY_CENTER)
+
+        # Seekbar 
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.seek = hildon.Seekbar()
+            self.seek.set_range(0.0, 100)
+            self.seek.set_draw_value(False)
+            self.seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
+            self.seek.connect('change-value', self.seek_changed) # event
+            # self.seek.connect('value-changed',self.seek_changed) # event
+        else:
+            adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
+            self.seek = gtk.HScale(adjustment)
+            self.seek.connect('change-value', self.seek_changed) # event
+
+        # Pause button
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.backButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            image = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.HILDON_SIZE_FINGER_HEIGHT)
+            self.backButton.set_image(image)
+
+            self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+
+            self.forwardButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.HILDON_SIZE_FINGER_HEIGHT)
+            self.forwardButton.set_image(image)
+        else:
+            self.backButton = gtk.Button(stock=gtk.STOCK_GO_BACK)
+            self.button = gtk.Button()
+            self.forwardButton = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
+        self.set_button_text("Play", "Start playing the audiobook")
+        self.backButton.connect('clicked', self._on_previous_chapter)
+        self.button.connect('clicked', self.play_pressed) # event
+        self.forwardButton.connect('clicked', self._on_next_chapter)
+
+        self._toolbar = gtk.HBox()
+        self._toolbar.pack_start(self.backButton, False, False, 0)
+        self._toolbar.pack_start(self.button, True, True, 0)
+        self._toolbar.pack_start(self.forwardButton, False, False, 0)
+
+        # Box to hold the controls:
+        self._controlLayout = gtk.VBox()
+        self._controlLayout.pack_start(self.title, True, True, 0)
+        self._controlLayout.pack_start(self.seek, True, True, 0)
+        self._controlLayout.pack_start(self._toolbar, False, True, 0)
+
+        #Box that divides the layout in two: cover on the lefta
+        #and controls on the right
+        self._viewLayout = gtk.HBox()
+        self._viewLayout.pack_start(self.cover, True, True, 0)
+        self._viewLayout.add(self._controlLayout)
+
+        self._menuBar = gtk.MenuBar()
+        self._menuBar.show()
+
+        self._mainLayout = gtk.VBox()
+        self._mainLayout.pack_start(self._menuBar, False, False, 0)
+        self._mainLayout.pack_start(self._viewLayout)
+
+        # Add hbox to the window
+        self.win.add(self._mainLayout)
+
+        #Menu:
+        # Create menu
+        self._populate_menu()
+
+        self.win.connect("delete_event", self.quit) # Add shutdown event
+        self.win.connect("key-press-event", self.on_key_press)
+        self.win.connect("window-state-event", self._on_window_state_change)
+
+        self.win.show_all()
+
+        # Run update timer
+        self.setup_timers()
+
+    def _populate_menu(self):
+        self._menuBar = hildonize.hildonize_menu(
+            self.win,
+            self._menuBar,
+        )
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            # Create a picker button 
+            self.book_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
+                                              hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            self.book_button.set_title("Audiobook") # Set a title to the button 
+            self.book_button.connect("clicked", self._on_select_audiobook)
+
+            # Create a picker button 
+            self.chapter_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
+                                              hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            self.chapter_button.set_title("Chapter") # Set a title to the button 
+            self.chapter_button.connect("clicked", self._on_select_chapter)
+
+            # Create a picker button 
+            self.sleeptime_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
+                                              hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            self.sleeptime_button.set_title("Sleeptimer") # Set a title to the button 
+            self.sleeptime_button.connect("clicked", self._on_select_sleep)
+
+            settings_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            settings_button.set_label("Settings")
+            settings_button.connect("clicked", self._on_settings)
+
+            help_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            help_button.set_label("Help")
+            help_button.connect("clicked", self.get_help)
+
+            self._menuBar.append(self.book_button)        # Add the button to menu
+            self._menuBar.append(self.chapter_button)        # Add the button to menu
+            self._menuBar.append(self.sleeptime_button)        # Add the button to menu
+            self._menuBar.append(settings_button)
+            self._menuBar.append(help_button)
+            self._menuBar.show_all()
+        else:
+            self._audiobookMenuItem = gtk.MenuItem("Audiobook: ")
+            self._audiobookMenuItem.connect("activate", self._on_select_audiobook)
+
+            self._chapterMenuItem = gtk.MenuItem("Chapter: ")
+            self._chapterMenuItem.connect("activate", self._on_select_chapter)
+
+            self._sleepMenuItem = gtk.MenuItem("Sleeptimer: 0")
+            self._sleepMenuItem.connect("activate", self._on_select_sleep)
+
+            settingsMenuItem = gtk.MenuItem("Settings")
+            settingsMenuItem.connect("activate", self._on_settings)
+
+            helpMenuItem = gtk.MenuItem("Help")
+            helpMenuItem.connect("activate", self.get_help)
+
+            booksMenu = gtk.Menu()
+            booksMenu.append(self._audiobookMenuItem)
+            booksMenu.append(self._chapterMenuItem)
+            booksMenu.append(self._sleepMenuItem)
+            booksMenu.append(settingsMenuItem)
+            booksMenu.append(helpMenuItem)
+
+            booksMenuItem = gtk.MenuItem("Books")
+            booksMenuItem.show()
+            booksMenuItem.set_submenu(booksMenu)
+            self._menuBar.append(booksMenuItem)
+            self._menuBar.show_all()
+
+    def setup_timers(self):
+        self.seek_timer = timeout_add_seconds(3, self.update_seek)
+
+    def save_settings(self):
+        config = ConfigParser.SafeConfigParser()
+        self._save_settings(config)
+        with open(constants._user_settings_, "wb") as configFile:
+            config.write(configFile)
+
+    def _save_settings(self, config):
+        config.add_section(constants.__pretty_app_name__)
+        config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
+        config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen))
+        config.set(constants.__pretty_app_name__, "audiopath", self.controller.get_books_path())
+
+    def load_settings(self):
+        config = ConfigParser.SafeConfigParser()
+        config.read(constants._user_settings_)
+        self._load_settings(config)
+
+    def _load_settings(self, config):
+        isPortrait = False
+        window_in_fullscreen = False
+        booksPath = constants._default_book_path_
+        try:
+            isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
+            window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
+            booksPath = config.get(constants.__pretty_app_name__, "audiopath")
+        except ConfigParser.NoSectionError, e:
+            _moduleLogger.info(
+                "Settings file %s is missing section %s" % (
+                    constants._user_settings_,
+                    e.section,
+                )
+            )
+
+        if isPortrait ^ self.__isPortrait:
+            if isPortrait:
+                orientation = gtk.ORIENTATION_VERTICAL
+            else:
+                orientation = gtk.ORIENTATION_HORIZONTAL
+            self.set_orientation(orientation)
+
+        self.__window_in_fullscreen = window_in_fullscreen
+        if self.__window_in_fullscreen:
+            self.win.fullscreen()
+        else:
+            self.win.unfullscreen()
+
+        self.controller.load_books_path(booksPath)
+
+    @staticmethod
+    def __format_name(path):
+        if os.path.isfile(path):
+            return os.path.basename(path).rsplit(".", 1)[0]
+        else:
+            return os.path.basename(path)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_select_audiobook(self, *args):
+        if not self._bookSelection:
+            return
+        index = hildonize.touch_selector(
+            self.win,
+            "Audiobook",
+            (self.__format_name(bookPath) for bookPath in self._bookSelection),
+            self._bookSelectionIndex if 0 <= self._bookSelectionIndex else 0,
+        )
+        self._bookSelectionIndex = index
+        bookName = self._bookSelection[index]
+        self.controller.set_book(bookName)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_select_chapter(self, *args):
+        if not self._chapterSelection:
+            return
+        index = hildonize.touch_selector(
+            self.win,
+            "Chapter",
+            (self.__format_name(chapterPath) for chapterPath in self._chapterSelection),
+            self._chapterSelectionIndex if 0 <= self._chapterSelectionIndex else 0,
+        )
+        self._chapterSelectionIndex = index
+        chapterName = self._chapterSelection[index]
+        self.controller.set_chapter(chapterName)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_select_sleep(self, *args):
+        if self.sleep_timer is not None:
+            gobject.source_remove(self.sleep_timer)
+
+        try:
+            index = hildonize.touch_selector(
+                self.win,
+                "Sleeptimer",
+                self._sleepSelection,
+                self._sleepSelectionIndex if 0 <= self._sleepSelectionIndex else 0,
+            )
+        except RuntimeError:
+            _moduleLogger.exception("Handling as if user cancelled")
+            hildonize.show_information_banner(self.win, "Sleep timer canceled")
+            index = 0
+
+        self._sleepSelectionIndex = index
+        sleepName = self._sleepSelection[index]
+
+        time_out = int(sleepName)
+        if 0 < time_out:
+            timeout_add_seconds(time_out * 60, self.sleep)
+
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.sleeptime_button.set_text("Sleeptimer", sleepName)
+        else:
+            self._sleepMenuItem.get_child().set_text("Sleeptimer: %s" % (sleepName, ))
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def __on_call_started(self, callMonitor):
+        self.pause()
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_settings(self, *args):
+        if self.__settingsWindow is None:
+            vbox = gtk.VBox()
+            self.__settingsManager = settings.SettingsDialog(vbox)
+
+            self.__settingsWindow = gtk.Window()
+            self.__settingsWindow.add(vbox)
+            self.__settingsWindow = hildonize.hildonize_window(self._app, self.__settingsWindow)
+            self.__settingsManager.window = self.__settingsWindow
+
+            self.__settingsWindow.set_title("Settings")
+            self.__settingsWindow.set_transient_for(self.win)
+            self.__settingsWindow.set_default_size(*self.win.get_size())
+            self.__settingsWindow.connect("delete-event", self._on_settings_delete)
+        self.__settingsManager.set_portrait_state(self.__isPortrait)
+        self.__settingsManager.set_audiobook_path(self.controller.get_books_path())
+        self.__settingsWindow.set_modal(True)
+        self.__settingsWindow.show_all()
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_settings_delete(self, *args):
+        self.__settingsWindow.emit_stop_by_name("delete-event")
+        self.__settingsWindow.hide()
+        self.__settingsWindow.set_modal(False)
+
+        isPortrait = self.__settingsManager.is_portrait()
+        if isPortrait ^ self.__isPortrait:
+            if isPortrait:
+                orientation = gtk.ORIENTATION_VERTICAL
+            else:
+                orientation = gtk.ORIENTATION_HORIZONTAL
+            self.set_orientation(orientation)
+        if self.__settingsManager.get_audiobook_path() != self.controller.get_books_path():
+            self.controller.load_books_path(self.__settingsManager.get_audiobook_path())
+
+        return True
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def update_seek(self):
+        #print self.controller.get_percentage()
+        if self.controller.is_playing():
+            gtk.gdk.threads_enter()
+            self.seek.set_value(self.controller.get_percentage() * 100)
+            gtk.gdk.threads_leave()
+        #self.controller.get_percentage() 
+        return True                     # run again
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def sleep(self):
+        _moduleLogger.info("sleep time timeout")
+        hildonize.show_information_banner(self.win, "Sleep timer")
+        self.controller.stop()
+        self.set_button_text("Resume", "Resume playing the audiobook")
+        return False                    # do not repeat
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def get_help(self, button):
+        Browser.open("file:///opt/Nqa-Audiobook-player/Help/nqaap.html")
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def seek_changed(self, seek, scroll , value):
+        # print "sok", scroll
+        self.controller.seek_percent(seek.get_value() / 100.0)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_next_chapter(self, *args):
+        self.controller.next_chapter()
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_previous_chapter(self, *args):
+        self.controller.previous_chapter()
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def play_pressed(self, button):
+        if self.controller.is_playing():
+            self.pause()
+        else:
+            self.play()
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def on_key_press(self, widget, event, *args):
+        RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
+        isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
+        if (
+            event.keyval == gtk.keysyms.F6 or
+            event.keyval in RETURN_TYPES and isCtrl
+        ):
+            # The "Full screen" hardware key has been pressed 
+            if self.__window_in_fullscreen:
+                self.win.unfullscreen ()
+            else:
+                self.win.fullscreen ()
+            return True
+        elif event.keyval == gtk.keysyms.o and isCtrl:
+            self._toggle_rotate()
+            return True
+        elif (
+            event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
+            event.get_state() & gtk.gdk.CONTROL_MASK
+        ):
+            self.quit()
+        elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
+            with open(constants._user_logpath_, "r") as f:
+                logLines = f.xreadlines()
+                log = "".join(logLines)
+                self._clipboard.set_text(str(log))
+            return True
+        elif event.keyval in RETURN_TYPES:
+            if self.controller.is_playing():
+                self.pause()
+            else:
+                self.play()
+            return True
+        elif event.keyval == gtk.keysyms.Left:
+            self.controller.previous_chapter()
+            return True
+        elif event.keyval == gtk.keysyms.Right:
+            self.controller.next_chapter()
+            return True
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_window_state_change(self, widget, event, *args):
+        if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
+            self.__window_in_fullscreen = True
+        else:
+            self.__window_in_fullscreen = False
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def quit(self, *args):             # what are the arguments?
+        _moduleLogger.info("Shutting down")
+        try:
+            self.save_settings()
+            self.controller.stop()          # to save the state
+        finally:
+            gtk.main_quit()
+
+    # Actions:  
+
+    def play(self):
+        self.set_button_text("Stop", "Stop playing the audiobook")
+        self.controller.play()
+
+    def pause(self):
+        self.set_button_text("Resume", "Resume playing the audiobook")
+        self.controller.stop()
+
+    def set_orientation(self, orientation):
+        if orientation == gtk.ORIENTATION_VERTICAL:
+            if self.__isPortrait:
+                return
+            hildonize.window_to_portrait(self.win)
+            self.__isPortrait = True
+
+            self._viewLayout.remove(self._controlLayout)
+            self._mainLayout.add(self._controlLayout)
+        elif orientation == gtk.ORIENTATION_HORIZONTAL:
+            if not self.__isPortrait:
+                return
+            hildonize.window_to_landscape(self.win)
+            self.__isPortrait = False
+
+            self._mainLayout.remove(self._controlLayout)
+            self._viewLayout.add(self._controlLayout)
+        else:
+            raise NotImplementedError(orientation)
+
+    def get_orientation(self):
+        return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
+
+    def _toggle_rotate(self):
+        if self.__isPortrait:
+            self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
+        else:
+            self.set_orientation(gtk.ORIENTATION_VERTICAL)
+
+    def change_chapter(self, chapterName):
+        if chapterName is None:
+            _moduleLogger.debug("chapter selection canceled.")
+            #import pdb; pdb.set_trace()     # start debugger
+            self.ignore_next_chapter_change = True
+            return True                   # this should end the function and indicate it has been handled
+
+        if self.ignore_next_chapter_change:
+            self.ignore_next_chapter_change = False
+            _moduleLogger.debug("followup chapter selection canceled.")
+            #import pdb; pdb.set_trace()     # start debugger
+            return True                   # this should end the function and indicate it has been handled
+
+        if self.auto_chapter_selected:
+            _moduleLogger.debug("chapter changed (by controller) to: %s" % chapterName)
+            self.auto_chapter_selected = False
+            # do nothing
+        else:
+            _moduleLogger.debug("chapter selection sendt to controller: %s" % chapterName)
+            self.controller.set_chapter(chapterName) # signal controller
+            self.set_button_text("Play", "Start playing the audiobook") # reset button
+
+    def set_button_text(self, title, text):
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.button.set_text("Resume", "Resume playing the audiobook")
+        else:
+            self.button.set_label("%s - %s" % (title, text))
+
+    def set_books(self, books):
+        _moduleLogger.debug("new books")
+        del self._bookSelection[:]
+        self._bookSelection.extend(books)
+        if len(books) == 0 and self.controller is not None:
+            hildonize.show_information_banner(self.win, "No audiobooks found. \nPlease place your audiobooks in the directory %s" % self.controller.get_books_path())
+
+    def set_book(self, bookPath, cover):
+        bookName = self.__format_name(bookPath)
+
+        self.set_button_text("Play", "Start playing the audiobook") # reset button
+        self.title.set_text(bookName)
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.book_button.set_text("Audiobook", bookName)
+        else:
+            self._audiobookMenuItem.get_child().set_text("Audiobook: %s" % (bookName, ))
+        if cover != "":
+            self.cover.set_from_file(cover)
+
+    def set_chapter(self, chapterIndex):
+        '''
+        Called from controller whenever a new chapter is started
+
+        chapter parameter is supposed to be the index for the chapter, not the name
+        '''
+        self.auto_chapter_selected = True
+        if hildonize.IS_FREMANTLE_SUPPORTED:
+            self.chapter_button.set_text("Chapter", str(chapterIndex))
+        else:
+            self._chapterMenuItem.get_child().set_text("Chapter: %s" % (chapterIndex, ))
+
+    def set_chapters(self, chapters):
+        _moduleLogger.debug("setting chapters" )
+        del self._chapterSelection[:]
+        self._chapterSelection.extend(chapters)
+
+    def set_sleep_timer(self, mins):
+        pass
+
+    # Utils
+    def set_selected_value(self, button, value):
+        i = button.get_selector().get_model(0).index[value] # get index of value from list
+        button.set_active(i)                                # set active index to that index
+
+
+def _old_timeout_add_seconds(timeout, callback):
+    return gobject.timeout_add(timeout * 1000, callback)
+
+
+def _timeout_add_seconds(timeout, callback):
+    return gobject.timeout_add_seconds(timeout, callback)
+
+
+try:
+    gobject.timeout_add_seconds
+    timeout_add_seconds = _timeout_add_seconds
+except AttributeError:
+    timeout_add_seconds = _old_timeout_add_seconds
+
+
+if __name__ == "__main__":
+    g = Gui(None)
diff --git a/src/opt/Nqa-Audiobook-player/Help/nqaap.html b/src/opt/Nqa-Audiobook-player/Help/nqaap.html
new file mode 100644 (file)
index 0000000..4bd4598
--- /dev/null
@@ -0,0 +1,111 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\r
+          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml"\r
+      lang="en" xml:lang="en">\r
+  <head>\r
+    <title>nQa Audiobook Player</title>\r
+    <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1"/>\r
+    <meta name="generator" content="Org-mode"/>\r
+    <meta name="generated" content="2010-04-17 21:23:57 "/>\r
+    <meta name="author" content="Søren Pedersen"/>\r
+    <style type="text/css">\r
+      html { font-family: Verdana, serif; font-size: 10pt; }\r
+      .title  { text-align: center; }\r
+      .todo   { color: red; }\r
+      .done   { color: green; }\r
+      .tag    { background-color:lightblue; font-weight:normal }\r
+      .target { }\r
+      .timestamp { color: red }\r
+      .timestamp-kwd { color: CadetBlue }\r
+      p.verse { margin-left: 3% }\r
+      pre {\r
+         border: 1pt solid #AEBDCC;\r
+         background-color: #F3F5F7;\r
+         padding: 5pt;\r
+         font-family: courier, monospace;\r
+          font-size: 90%;\r
+          overflow:auto;\r
+      }\r
+      table { border-collapse: collapse; }\r
+      td, th { vertical-align: top; }\r
+      dt { font-weight: bold; }\r
+      \r
+      .org-info-js_info-navigation { border-style:none; }\r
+      #org-info-js_console-label { font-size:10px; font-weight:bold;\r
+                                  white-space:nowrap; }\r
+      .org-info-js_search-highlight {background-color:#ffff00; color:#000000;\r
+                                     font-weight:bold; }\r
+      #postamble { color: grey }\r
+      #postamble a { color: CornflowerBlue  }\r
+    </style>\r
+  </head><body>\r
+    <h1 class="title">nQa Audiobook Player</h1>\r
+    \r
+    \r
+    <div id="table-of-contents">\r
+      <h2>Table of Contents</h2>\r
+      <div id="text-table-of-contents">\r
+       <ul>\r
+         <li><a href="#sec-1">1 Create book</a></li>\r
+         <li><a href="#sec-2">2 Play</a></li>\r
+       </ul>\r
+      </div>\r
+    </div>\r
+    \r
+    <div id="outline-container-1" class="outline-3">\r
+      <h3 id="sec-1">1 Create book</h3>\r
+      <div id="text-1">\r
+       \r
+       <ul>\r
+         <li>\r
+           Find audiofiles for the book in either mp3 or awb format (encoding\r
+           instructions later)\r
+         </li>\r
+         <li>\r
+           Ensure that files are named in such a way that a lexical sort places\r
+           them in playing order.\r
+         </li>\r
+         <li>\r
+           Create a directory in ~/MyDocs/Audiobooks (create this directory too if it\r
+           does not exist) with the name of the book.\r
+         </li>\r
+         <li>\r
+           Place audiofiles in this dir, all files must be in this\r
+           directory. It cant find files in further subdirectories.\r
+         </li>\r
+         <li>\r
+           (Optional) Place an imagefile in the directory. If prsent this file\r
+           will be used as the cover of the book.\r
+         </li>\r
+       </ul>\r
+      </div>\r
+      \r
+    </div>\r
+    \r
+    <div id="outline-container-2" class="outline-3">\r
+      <h3 id="sec-2">2 Play</h3>\r
+      <div id="text-2">\r
+       \r
+       <ul>\r
+         <li>\r
+           Open nqaap \r
+         </li>\r
+         <li>\r
+           Open menu \r
+         </li>\r
+         <li>\r
+           Select book \r
+         </li>\r
+         <li>\r
+           Press play \r
+         </li>\r
+       </ul>\r
+      </div>\r
+    </div>\r
+    <div id="postamble"><p class="author"> Author: S&oslash;ren 'Pengman' Pedersen\r
+       <a href="mailto:pengmeister@hotmail.com">&lt;pengmeister@hotmail.com&gt;</a>\r
+      </p>\r
+      <p class="date"> Date: 2010-04-17 21:23:57 </p>\r
+      <p>HTML generated by org-mode 6.09a in emacs 23</p>\r
+  </div></body>\r
+</html>\r
diff --git a/src/opt/Nqa-Audiobook-player/NoCover.png b/src/opt/Nqa-Audiobook-player/NoCover.png
new file mode 100644 (file)
index 0000000..80e142b
Binary files /dev/null and b/src/opt/Nqa-Audiobook-player/NoCover.png differ
diff --git a/src/opt/Nqa-Audiobook-player/Player.py b/src/opt/Nqa-Audiobook-player/Player.py
new file mode 100644 (file)
index 0000000..522b2fa
--- /dev/null
@@ -0,0 +1,185 @@
+import os
+import threading
+import time
+import logging
+
+import constants
+import hildonize
+import Audiobook
+import FileStorage
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class Player(object):
+
+    def __init__(self, ui):
+        self.storage = FileStorage.FileStorage(path = constants._data_path_)
+        if hildonize.IS_HILDON_SUPPORTED and not hildonize.IS_FREMANTLE_SUPPORTED:
+            import SimpleOSSOPlayer as _SimplePlayer
+            SimpleGStreamer = _SimplePlayer # silence PyFlakes
+        else:
+            import SimpleGStreamer as SimplePlayer
+        self.player = SimplePlayer.SimplePlayer(self.next_chapter)
+        self.ui = ui
+        self.audiobook = None
+        self._bookDir = None
+        self._bookPaths = {}
+        self.load_books_path(constants._default_book_path_)
+
+    def get_books_path(self):
+        return self._bookDir
+
+    def load_books_path(self, booksPath):
+        if self.audiobook is not None:
+            position = self.player.elapsed()
+            self.storage.set_time(self.audiobook.current_chapter, position)
+
+        self._bookDir = booksPath
+
+        self._bookPaths = dict(
+            (self.__format_name(bookPath), bookPath)
+            for bookPath in self._find_books()
+        )
+        if self.ui is not None:
+            bookPaths = self._bookPaths.values()
+            bookPaths.sort()
+            self.ui.set_books(bookPaths)
+
+        lastBookName = self.storage.get_selected()
+        if lastBookName is not None:
+            _moduleLogger.info("continuing book: %s" % lastBookName)
+            try:
+                bookPath = self._bookPaths[lastBookName]
+                self.set_book(bookPath)
+            except KeyError:
+                _moduleLogger.info("Audiobook was not found")
+            except IOError:
+                _moduleLogger.info("Audiobook could not be loaded")
+
+    @staticmethod
+    def __format_name(path):
+        if os.path.isfile(path):
+            return os.path.basename(path).rsplit(".", 1)[0]
+        else:
+            return os.path.basename(path)
+
+    def set_book(self, bookPath):
+        oldBookName = self.storage.get_selected()
+        try:
+            bookName = self.__format_name(bookPath)
+            self.storage.select_book(bookName)
+            chapter_num, _ = self.storage.get_time()
+            self.audiobook = Audiobook.Audiobook(
+                bookPath,
+                chapter_num
+            )
+        except:
+            self.storage.select_book(oldBookName)
+            raise
+
+        # self.player.set_file(self.audiobook.get_current_chapter())
+        # self.player.seek_time(time) 
+
+        if self.ui is not None:
+            self.ui.set_book(bookPath, self.audiobook.get_cover_img())
+            self.ui.set_chapters(self.audiobook.chapters)
+
+        chapter_title = self.audiobook.chapters[chapter_num]
+        self.set_chapter(chapter_title, True)
+
+    def set_chapter(self, chapter, continuing = False):
+        _moduleLogger.info("set chapter:" + chapter + " : Continuing: " + str(continuing))
+        self.audiobook.set_chapter(chapter)
+        self.player.set_file(self.audiobook.get_current_chapter())
+        if not continuing:
+            self.storage.set_time(self.audiobook.current_chapter, 0)
+
+        if self.ui is not None:
+            self.ui.set_chapter(self.audiobook.current_chapter)
+
+    def previous_chapter(self, *args):
+        _moduleLogger.info("Going back a chapter")
+        self.player.stop()
+        next_file = self.audiobook.get_previous_chapter()
+        if next_file is not False:
+            self.set_chapter(next_file)
+            self.player.play()
+        else:                           # the book is over
+            self.storage.set_time(0, 0)
+
+    def next_chapter(self, *args):
+        _moduleLogger.info("Advancing a chapter")
+        self.player.stop()
+        next_file = self.audiobook.get_next_chapter()
+        if next_file is not False:
+            self.set_chapter(next_file)
+            self.player.play()
+        else:                           # the book is over
+            self.storage.set_time(0, 0)
+
+    def play(self):
+        if self.audiobook is not None:
+            self.player.play()
+            _, target_time = self.storage.get_time()
+            if 0 < target_time:
+                time.sleep(1)
+                self.player.seek_time(target_time)
+            #print self.player.elapsed()
+        else:
+            print "No book selected, find one in ", self._bookDir
+
+    def stop(self):
+        position = self.player.elapsed()
+        self.player.stop()
+
+        if self.audiobook is not None:
+            self.storage.set_time(self.audiobook.current_chapter, position)
+
+    def is_playing(self):
+        return self.player.playing
+
+    def sleeptimer(self, secs):
+        #print "sleeper", secs
+        time.sleep(secs)
+        #print "now its time to sleep"
+        self.stop()
+
+    def start_sleeptimer(self, secs):
+        #print "startin sleep"
+        sleep_thread = threading.Thread(target=self.sleeptimer, args=(secs, ))
+        sleep_thread.start()
+        #print "started sleep"
+
+    def get_percentage(self):
+        try:
+            return float(self.player.elapsed()) / float(self.player.duration())
+        except ZeroDivisionError:
+            return 0.0
+
+    def seek_percent(self, ratio):
+        try:
+            target = self.player.duration() * ratio # Calculate where to seek
+            self.player.seek_time(int(target))      # seek
+
+            position = self.player.elapsed()
+            self.storage.set_time(self.audiobook.current_chapter, target) # save position
+            return True
+        except:
+            _moduleLogger.excetion("Seek failed")
+            return False
+
+    def _find_books(self):
+        try:
+            paths = (
+                os.path.join(self._bookDir, f)
+                for f in os.listdir(self._bookDir)
+            )
+            return (
+                path
+                for path in paths
+                if Audiobook.is_book(path)
+            )
+        except OSError:
+            return ()
diff --git a/src/opt/Nqa-Audiobook-player/SimpleGStreamer.py b/src/opt/Nqa-Audiobook-player/SimpleGStreamer.py
new file mode 100644 (file)
index 0000000..75fce6b
--- /dev/null
@@ -0,0 +1,93 @@
+import os
+import logging
+
+import gst
+
+import gtk_toolbox
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class SimplePlayer(object):
+
+    def __init__(self, on_playing_done = None):
+        #Fields
+        self.has_file = False
+        self.playing = False
+        self.__elapsed = 0
+
+        #Event callbacks
+        self.on_playing_done = on_playing_done
+
+        #Set up GStreamer
+        self.player = gst.element_factory_make("playbin2", "player")
+        fakesink = gst.element_factory_make("fakesink", "fakesink")
+        self.player.set_property("video-sink", fakesink)
+        bus = self.player.get_bus()
+        bus.add_signal_watch()
+        bus.connect("message", self.on_message)
+
+        #Constants
+        self.time_format = gst.Format(gst.FORMAT_TIME)
+        self.seek_flag = gst.SEEK_FLAG_FLUSH
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def on_message(self, bus, message):
+        t = message.type
+        if t == gst.MESSAGE_EOS:        # End-Of-Stream
+            self.player.set_state(gst.STATE_NULL)
+            self.playing = False
+            if self.on_playing_done is not None: # event callback
+                self.on_playing_done(self)
+        elif t == gst.MESSAGE_ERROR:
+            self.player.set_state(gst.STATE_NULL)
+            err, debug = message.parse_error()
+            #print "Error: %s" % err, debug
+            _moduleLogger.error("Error: %s, (%s)" % (err, debug))
+            self.playing = False
+
+    def set_file(self, file):
+        _moduleLogger.info("set file: %s", file)
+        if os.path.isfile(file):
+            if self.playing:
+                self.stop()
+
+            file = os.path.abspath(file) # ensure absolute path
+            _moduleLogger.debug("set file (absolute path): %s "%file)
+            self.player.set_property("uri", "file://" + file)
+            self.has_file = True
+        else:
+            _moduleLogger.error("File: %s not found" % file)
+
+    def play(self):
+        _moduleLogger.info("Started playing")
+        self.player.set_state(gst.STATE_PLAYING)
+        self.playing = True
+
+    def stop(self):
+        self.player.set_state(gst.STATE_NULL)
+        self.playing = False
+        _moduleLogger.info("Stopped playing")
+
+    def elapsed(self):
+        try:
+            self.__elapsed = self.player.query_position(self.time_format, None)[0]
+        except:
+            pass
+        return self.__elapsed
+
+    def duration(self):
+        try:
+            return self.player.query_duration(self.time_format, None)[0]
+        except:
+            _moduleLogger.exception("Query failed")
+            return 0
+
+    def seek_time(self, ns):
+        _moduleLogger.debug("Seeking to: %s", ns)
+        self.player.seek_simple(self.time_format, self.seek_flag, ns)
+
+    def __seek_percent(self, percent):
+        format = gst.Format(gst.FORMAT_PERCENT)
+        self.player.seek_simple(format, self.seek_flag, percent)
diff --git a/src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py b/src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py
new file mode 100644 (file)
index 0000000..9129f1a
--- /dev/null
@@ -0,0 +1,115 @@
+import os
+import logging
+
+import dbus
+
+import gtk_toolbox
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class SimplePlayer(object):
+
+    SERVICE_NAME         = "com.nokia.osso_media_server"
+    OBJECT_PATH          = "/com/nokia/osso_media_server"
+    AUDIO_INTERFACE_NAME = "com.nokia.osso_media_server.music"
+
+    def __init__(self, on_playing_done = None):
+        #Fields
+        self.has_file = False
+        self.playing = False
+        self.__elapsed = 0
+
+        #Event callbacks
+        self.on_playing_done = on_playing_done
+
+        session_bus = dbus.SessionBus()
+
+        # Get the osso-media-player proxy object
+        oms_object = session_bus.get_object(
+            self.SERVICE_NAME,
+            self.OBJECT_PATH,
+            introspect=False,
+            follow_name_owner_changes=True,
+        )
+        # Use the audio interface
+        oms_audio_interface = dbus.Interface(
+            oms_object,
+            self.AUDIO_INTERFACE_NAME,
+        )
+        self._audioProxy = oms_audio_interface
+
+        self._audioProxy.connect_to_signal("state_changed", self._on_state_changed)
+        self._audioProxy.connect_to_signal("end_of_stream", self._on_end_of_stream)
+
+        error_signals = [
+            "no_media_selected",
+            "file_not_found",
+            "type_not_found",
+            "unsupported_type",
+            "gstreamer",
+            "dsp",
+            "device_unavailable",
+            "corrupted_file",
+            "out_of_memory",
+            "audio_codec_not_supported",
+        ]
+        for error in error_signals:
+            self._audioProxy.connect_to_signal(error, self._on_error)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_error(self, *args):
+        self.playing = False
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_end_of_stream(self, *args):
+        self.playing = False
+        if self.on_playing_done is not None: # event callback
+            self.on_playing_done(self)
+
+    @gtk_toolbox.log_exception(_moduleLogger)
+    def _on_state_changed(self, state):
+        _moduleLogger.info("State: %s", state)
+
+    def set_file(self, file):
+        _moduleLogger.info("set file: %s", file)
+        if os.path.isfile(file):
+            if self.playing:
+                self.stop()
+
+            uri = "file://" + file
+            self._audioProxy.set_media_location(uri)
+            self.has_file = True
+        else:
+            _moduleLogger.error("File: %s not found" % file)
+
+    def play(self):
+        _moduleLogger.info("Started playing")
+        self._audioProxy.play()
+        self.playing = True
+
+    def stop(self):
+        self._audioProxy.stop()
+        self.playing = False
+        _moduleLogger.info("Stopped playing")
+
+    def elapsed(self):
+        pos_info = self._audioProxy.get_position()
+        if isinstance(pos_info, tuple):
+            pos, _ = pos_info
+            return pos
+        else:
+            return 0
+
+    def duration(self):
+        pos_info = self._audioProxy.get_position()
+        if isinstance(pos_info, tuple):
+            _, dur = pos_info
+            return dur
+        else:
+            return 0
+
+    def seek_time(self, ns):
+        _moduleLogger.debug("Seeking to: %s", ns)
+        self._audioProxy.seek( dbus.Int32(1), dbus.Int32(ns) )
diff --git a/src/opt/Nqa-Audiobook-player/__init__.py b/src/opt/Nqa-Audiobook-player/__init__.py
new file mode 100644 (file)
index 0000000..4265cc3
--- /dev/null
@@ -0,0 +1 @@
+#!/usr/bin/env python
diff --git a/src/opt/Nqa-Audiobook-player/constants.py b/src/opt/Nqa-Audiobook-player/constants.py
new file mode 100644 (file)
index 0000000..9f7bea9
--- /dev/null
@@ -0,0 +1,11 @@
+import os
+
+__pretty_app_name__ = "nQa Audiobook Player"
+__app_name__ = "nqaap"
+__version__ = "0.7.2-1"
+__build__ = 0
+__app_magic__ = 0xdeadbeef
+_data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__)
+_user_settings_ = "%s/settings.ini" % _data_path_
+_user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__)
+_default_book_path_ = os.path.join(os.path.expanduser("~"), "MyDocs/Audiobooks/")
diff --git a/src/opt/Nqa-Audiobook-player/gtk_toolbox.py b/src/opt/Nqa-Audiobook-player/gtk_toolbox.py
new file mode 100644 (file)
index 0000000..784c871
--- /dev/null
@@ -0,0 +1,577 @@
+#!/usr/bin/python
+
+from __future__ import with_statement
+
+import os
+import errno
+import sys
+import time
+import itertools
+import functools
+import contextlib
+import logging
+import threading
+import Queue
+
+import gobject
+import gtk
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+def get_screen_orientation():
+       width, height = gtk.gdk.get_default_root_window().get_size()
+       if width < height:
+               return gtk.ORIENTATION_VERTICAL
+       else:
+               return gtk.ORIENTATION_HORIZONTAL
+
+
+def orientation_change_connect(handler, *args):
+       """
+       @param handler(orientation, *args) -> None(?)
+       """
+       initialScreenOrientation = get_screen_orientation()
+       orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args))
+
+       def _on_screen_size_changed(screen):
+               newScreenOrientation = get_screen_orientation()
+               if newScreenOrientation != orientationAndArgs[0]:
+                       orientationAndArgs[0] = newScreenOrientation
+                       handler(*orientationAndArgs)
+
+       rootScreen = gtk.gdk.get_default_root_window()
+       return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed)
+
+
+@contextlib.contextmanager
+def flock(path, timeout=-1):
+       WAIT_FOREVER = -1
+       DELAY = 0.1
+       timeSpent = 0
+
+       acquired = False
+
+       while timeSpent <= timeout or timeout == WAIT_FOREVER:
+               try:
+                       fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR)
+                       acquired = True
+                       break
+               except OSError, e:
+                       if e.errno != errno.EEXIST:
+                               raise
+               time.sleep(DELAY)
+               timeSpent += DELAY
+
+       assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout)
+
+       try:
+               yield fd
+       finally:
+               os.unlink(path)
+
+
+@contextlib.contextmanager
+def gtk_lock():
+       gtk.gdk.threads_enter()
+       try:
+               yield
+       finally:
+               gtk.gdk.threads_leave()
+
+
+def find_parent_window(widget):
+       while True:
+               parent = widget.get_parent()
+               if isinstance(parent, gtk.Window):
+                       return parent
+               widget = parent
+
+
+def make_idler(func):
+       """
+       Decorator that makes a generator-function into a function that will continue execution on next call
+       """
+       a = []
+
+       @functools.wraps(func)
+       def decorated_func(*args, **kwds):
+               if not a:
+                       a.append(func(*args, **kwds))
+               try:
+                       a[0].next()
+                       return True
+               except StopIteration:
+                       del a[:]
+                       return False
+
+       return decorated_func
+
+
+def asynchronous_gtk_message(original_func):
+       """
+       @note Idea came from http://www.aclevername.com/articles/python-webgui/
+       """
+
+       def execute(allArgs):
+               args, kwargs = allArgs
+               with gtk_lock():
+                       original_func(*args, **kwargs)
+               return False
+
+       @functools.wraps(original_func)
+       def delayed_func(*args, **kwargs):
+               gobject.idle_add(execute, (args, kwargs))
+
+       return delayed_func
+
+
+def synchronous_gtk_message(original_func):
+       """
+       @note Idea came from http://www.aclevername.com/articles/python-webgui/
+       """
+
+       @functools.wraps(original_func)
+       def immediate_func(*args, **kwargs):
+               with gtk_lock():
+                       return original_func(*args, **kwargs)
+
+       return immediate_func
+
+
+def autostart(func):
+       """
+       >>> @autostart
+       ... def grep_sink(pattern):
+       ...     print "Looking for %s" % pattern
+       ...     while True:
+       ...             line = yield
+       ...             if pattern in line:
+       ...                     print line,
+       >>> g = grep_sink("python")
+       Looking for python
+       >>> g.send("Yeah but no but yeah but no")
+       >>> g.send("A series of tubes")
+       >>> g.send("python generators rock!")
+       python generators rock!
+       >>> g.close()
+       """
+
+       @functools.wraps(func)
+       def start(*args, **kwargs):
+               cr = func(*args, **kwargs)
+               cr.next()
+               return cr
+
+       return start
+
+
+@autostart
+def printer_sink(format = "%s"):
+       """
+       >>> pr = printer_sink("%r")
+       >>> pr.send("Hello")
+       'Hello'
+       >>> pr.send("5")
+       '5'
+       >>> pr.send(5)
+       5
+       >>> p = printer_sink()
+       >>> p.send("Hello")
+       Hello
+       >>> p.send("World")
+       World
+       >>> # p.throw(RuntimeError, "Goodbye")
+       >>> # p.send("Meh")
+       >>> # p.close()
+       """
+       while True:
+               item = yield
+               print format % (item, )
+
+
+@autostart
+def null_sink():
+       """
+       Good for uses like with cochain to pick up any slack
+       """
+       while True:
+               item = yield
+
+
+@autostart
+def comap(function, target):
+       """
+       >>> p = printer_sink()
+       >>> cm = comap(lambda x: x+1, p)
+       >>> cm.send((0, ))
+       1
+       >>> cm.send((1.0, ))
+       2.0
+       >>> cm.send((-2, ))
+       -1
+       """
+       while True:
+               try:
+                       item = yield
+                       mappedItem = function(*item)
+                       target.send(mappedItem)
+               except Exception, e:
+                       _moduleLogger.exception("Forwarding exception!")
+                       target.throw(e.__class__, str(e))
+
+
+def _flush_queue(queue):
+       while not queue.empty():
+               yield queue.get()
+
+
+@autostart
+def queue_sink(queue):
+       """
+       >>> q = Queue.Queue()
+       >>> qs = queue_sink(q)
+       >>> qs.send("Hello")
+       >>> qs.send("World")
+       >>> qs.throw(RuntimeError, "Goodbye")
+       >>> qs.send("Meh")
+       >>> qs.close()
+       >>> print [i for i in _flush_queue(q)]
+       [(None, 'Hello'), (None, 'World'), (<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)
+
+
+def log_exception(logger):
+
+       def log_exception_decorator(func):
+
+               @functools.wraps(func)
+               def wrapper(*args, **kwds):
+                       try:
+                               return func(*args, **kwds)
+                       except Exception:
+                               logger.exception(func.__name__)
+
+               return wrapper
+
+       return log_exception_decorator
+
+
+class LoginWindow(object):
+
+       def __init__(self, widgetTree):
+               """
+               @note Thread agnostic
+               """
+               self._dialog = widgetTree.get_widget("loginDialog")
+               self._parentWindow = widgetTree.get_widget("mainWindow")
+               self._serviceCombo = widgetTree.get_widget("serviceCombo")
+               self._usernameEntry = widgetTree.get_widget("usernameentry")
+               self._passwordEntry = widgetTree.get_widget("passwordentry")
+
+               self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING)
+               self._serviceCombo.set_model(self._serviceList)
+               cell = gtk.CellRendererText()
+               self._serviceCombo.pack_start(cell, True)
+               self._serviceCombo.add_attribute(cell, 'text', 1)
+               self._serviceCombo.set_active(0)
+
+               widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked)
+               widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked)
+
+       def request_credentials(self,
+               parentWindow = None,
+               defaultCredentials = ("", "")
+       ):
+               """
+               @note UI Thread
+               """
+               if parentWindow is None:
+                       parentWindow = self._parentWindow
+
+               self._serviceCombo.hide()
+               self._serviceList.clear()
+
+               self._usernameEntry.set_text(defaultCredentials[0])
+               self._passwordEntry.set_text(defaultCredentials[1])
+
+               try:
+                       self._dialog.set_transient_for(parentWindow)
+                       self._dialog.set_default_response(gtk.RESPONSE_OK)
+                       response = self._dialog.run()
+                       if response != gtk.RESPONSE_OK:
+                               raise RuntimeError("Login Cancelled")
+
+                       username = self._usernameEntry.get_text()
+                       password = self._passwordEntry.get_text()
+                       self._passwordEntry.set_text("")
+               finally:
+                       self._dialog.hide()
+
+               return username, password
+
+       def request_credentials_from(self,
+               services,
+               parentWindow = None,
+               defaultCredentials = ("", "")
+       ):
+               """
+               @note UI Thread
+               """
+               if parentWindow is None:
+                       parentWindow = self._parentWindow
+
+               self._serviceList.clear()
+               for serviceIdserviceName in services:
+                       self._serviceList.append(serviceIdserviceName)
+               self._serviceCombo.set_active(0)
+               self._serviceCombo.show()
+
+               self._usernameEntry.set_text(defaultCredentials[0])
+               self._passwordEntry.set_text(defaultCredentials[1])
+
+               try:
+                       self._dialog.set_transient_for(parentWindow)
+                       self._dialog.set_default_response(gtk.RESPONSE_OK)
+                       response = self._dialog.run()
+                       if response != gtk.RESPONSE_OK:
+                               raise RuntimeError("Login Cancelled")
+
+                       username = self._usernameEntry.get_text()
+                       password = self._passwordEntry.get_text()
+               finally:
+                       self._dialog.hide()
+
+               itr = self._serviceCombo.get_active_iter()
+               serviceId = int(self._serviceList.get_value(itr, 0))
+               self._serviceList.clear()
+               return serviceId, username, password
+
+       def _on_loginbutton_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_OK)
+
+       def _on_loginclose_clicked(self, *args):
+               self._dialog.response(gtk.RESPONSE_CANCEL)
+
+
+def safecall(f, errorDisplay=None, default=None, exception=Exception):
+       '''
+       Returns modified f. When the modified f is called and throws an
+       exception, the default value is returned
+       '''
+       def _safecall(*args, **argv):
+               try:
+                       return f(*args,**argv)
+               except exception, e:
+                       if errorDisplay is not None:
+                               errorDisplay.push_exception(e)
+                       return default
+       return _safecall
+
+
+class ErrorDisplay(object):
+
+       def __init__(self, widgetTree):
+               super(ErrorDisplay, self).__init__()
+               self.__errorBox = widgetTree.get_widget("errorEventBox")
+               self.__errorDescription = widgetTree.get_widget("errorDescription")
+               self.__errorClose = widgetTree.get_widget("errorClose")
+               self.__parentBox = self.__errorBox.get_parent()
+
+               self.__errorBox.connect("button_release_event", self._on_close)
+
+               self.__messages = []
+               self.__parentBox.remove(self.__errorBox)
+
+       def push_message_with_lock(self, message):
+               with gtk_lock():
+                       self.push_message(message)
+
+       def push_message(self, message):
+               self.__messages.append(message)
+               if 1 == len(self.__messages):
+                       self.__show_message(message)
+
+       def push_exception_with_lock(self):
+               with gtk_lock():
+                       self.push_exception()
+
+       def push_exception(self):
+               userMessage = str(sys.exc_info()[1])
+               self.push_message(userMessage)
+               _moduleLogger.exception(userMessage)
+
+       def pop_message(self):
+               del self.__messages[0]
+               if 0 == len(self.__messages):
+                       self.__hide_message()
+               else:
+                       self.__errorDescription.set_text(self.__messages[0])
+
+       def _on_close(self, *args):
+               self.pop_message()
+
+       def __show_message(self, message):
+               self.__errorDescription.set_text(message)
+               self.__parentBox.pack_start(self.__errorBox, False, False)
+               self.__parentBox.reorder_child(self.__errorBox, 1)
+
+       def __hide_message(self):
+               self.__errorDescription.set_text("")
+               self.__parentBox.remove(self.__errorBox)
+
+
+class DummyErrorDisplay(object):
+
+       def __init__(self):
+               super(DummyErrorDisplay, self).__init__()
+
+               self.__messages = []
+
+       def push_message_with_lock(self, message):
+               self.push_message(message)
+
+       def push_message(self, message):
+               if 0 < len(self.__messages):
+                       self.__messages.append(message)
+               else:
+                       self.__show_message(message)
+
+       def push_exception(self, exception = None):
+               userMessage = str(sys.exc_value)
+               _moduleLogger.exception(userMessage)
+
+       def pop_message(self):
+               if 0 < len(self.__messages):
+                       self.__show_message(self.__messages[0])
+                       del self.__messages[0]
+
+       def __show_message(self, message):
+               _moduleLogger.debug(message)
+
+
+class MessageBox(gtk.MessageDialog):
+
+       def __init__(self, message):
+               parent = None
+               gtk.MessageDialog.__init__(
+                       self,
+                       parent,
+                       gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+                       gtk.MESSAGE_ERROR,
+                       gtk.BUTTONS_OK,
+                       message,
+               )
+               self.set_default_response(gtk.RESPONSE_OK)
+               self.connect('response', self._handle_clicked)
+
+       def _handle_clicked(self, *args):
+               self.destroy()
+
+
+class MessageBox2(gtk.MessageDialog):
+
+       def __init__(self, message):
+               parent = None
+               gtk.MessageDialog.__init__(
+                       self,
+                       parent,
+                       gtk.DIALOG_DESTROY_WITH_PARENT,
+                       gtk.MESSAGE_ERROR,
+                       gtk.BUTTONS_OK,
+                       message,
+               )
+               self.set_default_response(gtk.RESPONSE_OK)
+               self.connect('response', self._handle_clicked)
+
+       def _handle_clicked(self, *args):
+               self.destroy()
+
+
+class PopupCalendar(object):
+
+       def __init__(self, parent, displayDate, title = ""):
+               self._displayDate = displayDate
+
+               self._calendar = gtk.Calendar()
+               self._calendar.select_month(self._displayDate.month, self._displayDate.year)
+               self._calendar.select_day(self._displayDate.day)
+               self._calendar.set_display_options(
+                       gtk.CALENDAR_SHOW_HEADING |
+                       gtk.CALENDAR_SHOW_DAY_NAMES |
+                       gtk.CALENDAR_NO_MONTH_CHANGE |
+                       0
+               )
+               self._calendar.connect("day-selected", self._on_day_selected)
+
+               self._popupWindow = gtk.Window()
+               self._popupWindow.set_title(title)
+               self._popupWindow.add(self._calendar)
+               self._popupWindow.set_transient_for(parent)
+               self._popupWindow.set_modal(True)
+               self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
+               self._popupWindow.set_skip_pager_hint(True)
+               self._popupWindow.set_skip_taskbar_hint(True)
+
+       def run(self):
+               self._popupWindow.show_all()
+
+       def _on_day_selected(self, *args):
+               try:
+                       self._calendar.select_month(self._displayDate.month, self._displayDate.year)
+                       self._calendar.select_day(self._displayDate.day)
+               except Exception, e:
+                       _moduleLogger.exception(e)
+
+
+if __name__ == "__main__":
+       if False:
+               import datetime
+               cal = PopupCalendar(None, datetime.datetime.now())
+               cal._popupWindow.connect("destroy", lambda w: gtk.main_quit())
+               cal.run()
+
+       gtk.main()
diff --git a/src/opt/Nqa-Audiobook-player/hildonize.py b/src/opt/Nqa-Audiobook-player/hildonize.py
new file mode 100644 (file)
index 0000000..08049ba
--- /dev/null
@@ -0,0 +1,743 @@
+#!/usr/bin/env python
+
+"""
+Open Issues
+       @bug not all of a message is shown
+       @bug Buttons are too small
+"""
+
+
+import gobject
+import gtk
+import dbus
+
+
+class _NullHildonModule(object):
+       pass
+
+
+try:
+       import hildon as _hildon
+       hildon  = _hildon # Dumb but gets around pyflakiness
+except (ImportError, OSError):
+       hildon = _NullHildonModule
+
+
+IS_HILDON_SUPPORTED = hildon is not _NullHildonModule
+
+
+class _NullHildonProgram(object):
+
+       def add_window(self, window):
+               pass
+
+
+def _hildon_get_app_class():
+       return hildon.Program
+
+
+def _null_get_app_class():
+       return _NullHildonProgram
+
+
+try:
+       hildon.Program
+       get_app_class = _hildon_get_app_class
+except AttributeError:
+       get_app_class = _null_get_app_class
+
+
+def _hildon_set_application_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()
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
+       app.add_window(newWindow)
+       return newWindow
+
+
+def _hildon_hildonize_window(app, window):
+       oldWindow = window
+       newWindow = hildon.Window()
+       if oldWindow.get_child() is not None:
+               oldWindow.get_child().reparent(newWindow)
+       app.add_window(newWindow)
+       return newWindow
+
+
+def _null_hildonize_window(app, window):
+       return window
+
+
+try:
+       hildon.StackableWindow
+       hildonize_window = _fremantle_hildonize_window
+except AttributeError:
+       try:
+               hildon.Window
+               hildonize_window = _hildon_hildonize_window
+       except AttributeError:
+               hildonize_window = _null_hildonize_window
+
+
+def _fremantle_hildonize_menu(window, gtkMenu):
+       appMenu = hildon.AppMenu()
+       window.set_app_menu(appMenu)
+       gtkMenu.get_parent().remove(gtkMenu)
+       return appMenu
+
+
+def _hildon_hildonize_menu(window, gtkMenu):
+       hildonMenu = gtk.Menu()
+       for child in gtkMenu.get_children():
+               child.reparent(hildonMenu)
+       window.set_menu(hildonMenu)
+       gtkMenu.destroy()
+       return hildonMenu
+
+
+def _null_hildonize_menu(window, gtkMenu):
+       return gtkMenu
+
+
+try:
+       hildon.AppMenu
+       GTK_MENU_USED = False
+       IS_FREMANTLE_SUPPORTED = True
+       hildonize_menu = _fremantle_hildonize_menu
+except AttributeError:
+       GTK_MENU_USED = True
+       IS_FREMANTLE_SUPPORTED = False
+       if IS_HILDON_SUPPORTED:
+               hildonize_menu = _hildon_hildonize_menu
+       else:
+               hildonize_menu = _null_hildonize_menu
+
+
+def _hildon_set_button_auto_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT)
+
+
+def _null_set_button_auto_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_AUTO_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_auto_selectable = _hildon_set_button_auto_selectable
+except AttributeError:
+       set_button_auto_selectable = _null_set_button_auto_selectable
+
+
+def _hildon_set_button_finger_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT)
+
+
+def _null_set_button_finger_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_FINGER_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_finger_selectable = _hildon_set_button_finger_selectable
+except AttributeError:
+       set_button_finger_selectable = _null_set_button_finger_selectable
+
+
+def _hildon_set_button_thumb_selectable(button):
+       button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT)
+
+
+def _null_set_button_thumb_selectable(button):
+       pass
+
+
+try:
+       hildon.HILDON_SIZE_THUMB_HEIGHT
+       gtk.Button.set_theme_size
+       set_button_thumb_selectable = _hildon_set_button_thumb_selectable
+except AttributeError:
+       set_button_thumb_selectable = _null_set_button_thumb_selectable
+
+
+def _hildon_set_cell_thumb_selectable(renderer):
+       renderer.set_property("scale", 1.5)
+
+
+def _null_set_cell_thumb_selectable(renderer):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable
+else:
+       set_cell_thumb_selectable = _null_set_cell_thumb_selectable
+
+
+def _hildon_set_pix_cell_thumb_selectable(renderer):
+       renderer.set_property("stock-size", 48)
+
+
+def _null_set_pix_cell_thumb_selectable(renderer):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable
+else:
+       set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable
+
+
+def _fremantle_show_information_banner(parent, message):
+       hildon.hildon_banner_show_information(parent, "", message)
+
+
+def _hildon_show_information_banner(parent, message):
+       hildon.hildon_banner_show_information(parent, None, message)
+
+
+def _null_show_information_banner(parent, message):
+       pass
+
+
+if IS_FREMANTLE_SUPPORTED:
+       show_information_banner = _fremantle_show_information_banner
+else:
+       try:
+               hildon.hildon_banner_show_information
+               show_information_banner = _hildon_show_information_banner
+       except AttributeError:
+               show_information_banner = _null_show_information_banner
+
+
+def _fremantle_show_busy_banner_start(parent, message):
+       hildon.hildon_gtk_window_set_progress_indicator(parent, True)
+       return parent
+
+
+def _fremantle_show_busy_banner_end(banner):
+       hildon.hildon_gtk_window_set_progress_indicator(banner, False)
+
+
+def _hildon_show_busy_banner_start(parent, message):
+       return hildon.hildon_banner_show_animation(parent, None, message)
+
+
+def _hildon_show_busy_banner_end(banner):
+       banner.destroy()
+
+
+def _null_show_busy_banner_start(parent, message):
+       return None
+
+
+def _null_show_busy_banner_end(banner):
+       assert banner is None
+
+
+try:
+       hildon.hildon_gtk_window_set_progress_indicator
+       show_busy_banner_start = _fremantle_show_busy_banner_start
+       show_busy_banner_end = _fremantle_show_busy_banner_end
+except AttributeError:
+       try:
+               hildon.hildon_banner_show_animation
+               show_busy_banner_start = _hildon_show_busy_banner_start
+               show_busy_banner_end = _hildon_show_busy_banner_end
+       except AttributeError:
+               show_busy_banner_start = _null_show_busy_banner_start
+               show_busy_banner_end = _null_show_busy_banner_end
+
+
+def _hildon_hildonize_text_entry(textEntry):
+       textEntry.set_property('hildon-input-mode', 7)
+
+
+def _null_hildonize_text_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_text_entry = _hildon_hildonize_text_entry
+else:
+       hildonize_text_entry = _null_hildonize_text_entry
+
+
+def _hildon_window_to_portrait(window):
+       # gtk documentation is unclear whether this does a "=" or a "|="
+       flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST
+       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+
+def _hildon_window_to_landscape(window):
+       # gtk documentation is unclear whether this does a "=" or a "&= ~"
+       flags = hildon.PORTRAIT_MODE_SUPPORT
+       hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+
+def _null_window_to_portrait(window):
+       pass
+
+
+def _null_window_to_landscape(window):
+       pass
+
+
+try:
+       hildon.PORTRAIT_MODE_SUPPORT
+       hildon.PORTRAIT_MODE_REQUEST
+       hildon.hildon_gtk_window_set_portrait_flags
+
+       window_to_portrait = _hildon_window_to_portrait
+       window_to_landscape = _hildon_window_to_landscape
+except AttributeError:
+       window_to_portrait = _null_window_to_portrait
+       window_to_landscape = _null_window_to_landscape
+
+
+def get_device_orientation():
+       bus = dbus.SystemBus()
+       try:
+               rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request")
+               mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request")
+               orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation()
+       except dbus.exception.DBusException:
+               # catching for documentation purposes that when a system doesn't
+               # support this, this is what to expect
+               raise
+
+       if orientation == "":
+               return gtk.ORIENTATION_HORIZONTAL
+       elif orientation == "":
+               return gtk.ORIENTATION_VERTICAL
+       else:
+               raise RuntimeError("Unknown orientation: %s" % orientation)
+
+
+def _hildon_hildonize_password_entry(textEntry):
+       textEntry.set_property('hildon-input-mode', 7 | (1 << 29))
+
+
+def _null_hildonize_password_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_password_entry = _hildon_hildonize_password_entry
+else:
+       hildonize_password_entry = _null_hildonize_password_entry
+
+
+def _hildon_hildonize_combo_entry(comboEntry):
+       comboEntry.set_property('hildon-input-mode', 1 << 4)
+
+
+def _null_hildonize_combo_entry(textEntry):
+       pass
+
+
+if IS_HILDON_SUPPORTED:
+       hildonize_combo_entry = _hildon_hildonize_combo_entry
+else:
+       hildonize_combo_entry = _null_hildonize_combo_entry
+
+
+def _fremantle_hildonize_scrollwindow(scrolledWindow):
+       pannableWindow = hildon.PannableArea()
+
+       child = scrolledWindow.get_child()
+       scrolledWindow.remove(child)
+       pannableWindow.add(child)
+
+       parent = scrolledWindow.get_parent()
+       if parent is not None:
+               parent.remove(scrolledWindow)
+               parent.add(pannableWindow)
+
+       return pannableWindow
+
+
+def _hildon_hildonize_scrollwindow(scrolledWindow):
+       hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True)
+       return scrolledWindow
+
+
+def _null_hildonize_scrollwindow(scrolledWindow):
+       return scrolledWindow
+
+
+try:
+       hildon.PannableArea
+       hildonize_scrollwindow = _fremantle_hildonize_scrollwindow
+       hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
+except AttributeError:
+       try:
+               hildon.hildon_helper_set_thumb_scrollbar
+               hildonize_scrollwindow = _hildon_hildonize_scrollwindow
+               hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow
+       except AttributeError:
+               hildonize_scrollwindow = _null_hildonize_scrollwindow
+               hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow
+
+
+def _hildon_request_number(parent, title, range, default):
+       spinner = hildon.NumberEditor(*range)
+       spinner.set_value(default)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(spinner)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return spinner.get_value()
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+def _null_request_number(parent, title, range, default):
+       adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0)
+       spinner = gtk.SpinButton(adjustment, 0, 0)
+       spinner.set_wrap(False)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(spinner)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return spinner.get_value_as_int()
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.NumberEditor # TODO deprecated in fremantle
+       request_number = _hildon_request_number
+except AttributeError:
+       request_number = _null_request_number
+
+
+def _hildon_touch_selector(parent, title, items, defaultIndex):
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       for item in items:
+               model.append((item, ))
+
+       selector = hildon.TouchSelector()
+       selector.append_text_column(model, True)
+       selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
+       selector.set_active(0, defaultIndex)
+
+       dialog = hildon.PickerDialog(parent)
+       dialog.set_selector(selector)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       return selector.get_active(0)
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData):
+       dialog.response(gtk.RESPONSE_OK)
+       pathData[0] = path
+
+
+def _null_touch_selector(parent, title, items, defaultIndex = -1):
+       parentSize = parent.get_size()
+
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       for item in items:
+               model.append((item, ))
+
+       cell = gtk.CellRendererText()
+       set_cell_thumb_selectable(cell)
+       column = gtk.TreeViewColumn(title)
+       column.pack_start(cell, expand=True)
+       column.add_attribute(cell, "text", 0)
+
+       treeView = gtk.TreeView()
+       treeView.set_model(model)
+       treeView.append_column(column)
+       selection = treeView.get_selection()
+       selection.set_mode(gtk.SELECTION_SINGLE)
+       if 0 < defaultIndex:
+               selection.select_path((defaultIndex, ))
+
+       scrolledWin = gtk.ScrolledWindow()
+       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+       scrolledWin.add(treeView)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(scrolledWin)
+       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
+
+       scrolledWin = hildonize_scrollwindow(scrolledWin)
+       pathData = [None]
+       treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       if pathData[0] is None:
+                               raise RuntimeError("No selection made")
+                       return pathData[0][0]
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.PickerDialog
+       hildon.TouchSelector
+       touch_selector = _hildon_touch_selector
+except AttributeError:
+       touch_selector = _null_touch_selector
+
+
+def _hildon_touch_selector_entry(parent, title, items, defaultItem):
+       # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way
+       try:
+               selector = hildon.TouchSelectorEntry(text=True)
+       except TypeError:
+               selector = hildon.hildon_touch_selector_entry_new_text()
+       defaultIndex = -1
+       for i, item in enumerate(items):
+               selector.append_text(item)
+               if item == defaultItem:
+                       defaultIndex = i
+
+       dialog = hildon.PickerDialog(parent)
+       dialog.set_selector(selector)
+
+       if 0 < defaultIndex:
+               selector.set_active(0, defaultIndex)
+       else:
+               selector.get_entry().set_text(defaultItem)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+       finally:
+               dialog.hide()
+
+       if response == gtk.RESPONSE_OK:
+               return selector.get_entry().get_text()
+       elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+               raise RuntimeError("User cancelled request")
+       else:
+               raise RuntimeError("Unrecognized response %r", response)
+
+
+def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex):
+       custom = entry.get_text().strip()
+       if custom:
+               result[0] = custom
+               selection.unselect_all()
+       else:
+               result[0] = None
+               selection.select_path((defaultIndex, ))
+
+
+def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result):
+       dialog.response(gtk.RESPONSE_OK)
+       result[0] = customEntry.get_text()
+
+
+def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result):
+       dialog.response(gtk.RESPONSE_OK)
+       model = treeView.get_model()
+       itr = model.get_iter(path)
+       if itr is not None:
+               result[0] = model.get_value(itr, 0)
+
+
+def _null_touch_selector_entry(parent, title, items, defaultItem):
+       parentSize = parent.get_size()
+
+       model = gtk.ListStore(gobject.TYPE_STRING)
+       defaultIndex = -1
+       for i, item in enumerate(items):
+               model.append((item, ))
+               if item == defaultItem:
+                       defaultIndex = i
+
+       cell = gtk.CellRendererText()
+       set_cell_thumb_selectable(cell)
+       column = gtk.TreeViewColumn(title)
+       column.pack_start(cell, expand=True)
+       column.add_attribute(cell, "text", 0)
+
+       treeView = gtk.TreeView()
+       treeView.set_model(model)
+       treeView.append_column(column)
+       selection = treeView.get_selection()
+       selection.set_mode(gtk.SELECTION_SINGLE)
+
+       scrolledWin = gtk.ScrolledWindow()
+       scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+       scrolledWin.add(treeView)
+
+       customEntry = gtk.Entry()
+
+       layout = gtk.VBox()
+       layout.pack_start(customEntry, expand=False)
+       layout.pack_start(scrolledWin)
+
+       dialog = gtk.Dialog(
+               title,
+               parent,
+               gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
+               (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL),
+       )
+       dialog.set_default_response(gtk.RESPONSE_CANCEL)
+       dialog.get_child().add(layout)
+       dialog.resize(parentSize[0], max(parentSize[1]-100, 100))
+
+       scrolledWin = hildonize_scrollwindow(scrolledWin)
+
+       result = [None]
+       if 0 < defaultIndex:
+               selection.select_path((defaultIndex, ))
+               result[0] = defaultItem
+       else:
+               customEntry.set_text(defaultItem)
+
+       customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result)
+       customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex)
+       treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result)
+
+       try:
+               dialog.show_all()
+               response = dialog.run()
+
+               if response == gtk.RESPONSE_OK:
+                       _, itr = selection.get_selected()
+                       if itr is not None:
+                               return model.get_value(itr, 0)
+                       else:
+                               enteredText = customEntry.get_text().strip()
+                               if enteredText:
+                                       return enteredText
+                               elif result[0] is not None:
+                                       return result[0]
+                               else:
+                                       raise RuntimeError("No selection made")
+               elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT:
+                       raise RuntimeError("User cancelled request")
+               else:
+                       raise RuntimeError("Unrecognized response %r", response)
+       finally:
+               dialog.hide()
+               dialog.destroy()
+
+
+try:
+       hildon.PickerDialog
+       hildon.TouchSelectorEntry
+       touch_selector_entry = _hildon_touch_selector_entry
+except AttributeError:
+       touch_selector_entry = _null_touch_selector_entry
+
+
+if __name__ == "__main__":
+       app = get_app_class()()
+
+       label = gtk.Label("Hello World from a Label!")
+
+       win = gtk.Window()
+       win.add(label)
+       win = hildonize_window(app, win)
+       if False and IS_FREMANTLE_SUPPORTED:
+               appMenu = hildon.AppMenu()
+               for i in xrange(5):
+                       b = gtk.Button(str(i))
+                       appMenu.append(b)
+               win.set_app_menu(appMenu)
+               win.show_all()
+               appMenu.show_all()
+               gtk.main()
+       elif False:
+               print touch_selector(win, "Test", ["A", "B", "C", "D"], 2)
+       elif False:
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C")
+               print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah")
+       elif False:
+               import pprint
+               name, value = "", ""
+               goodLocals = [
+                       (name, value) for (name, value) in locals().iteritems()
+                       if not name.startswith("_")
+               ]
+               pprint.pprint(goodLocals)
+       elif False:
+               import time
+               show_information_banner(win, "Hello World")
+               time.sleep(5)
+       elif False:
+               import time
+               banner = show_busy_banner_start(win, "Hello World")
+               time.sleep(5)
+               show_busy_banner_end(banner)
diff --git a/src/opt/Nqa-Audiobook-player/nqaap.py b/src/opt/Nqa-Audiobook-player/nqaap.py
new file mode 100644 (file)
index 0000000..ab32ba2
--- /dev/null
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+import os
+import logging
+
+import constants
+import nqaap_gtk
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+try:
+       os.makedirs(constants._data_path_)
+except OSError, e:
+       if e.errno != 17:
+               raise
+
+logging.basicConfig(level=logging.DEBUG, filename=constants._user_logpath_)
+_moduleLogger.info("%s %s-%s" % (constants.__pretty_app_name__, constants.__version__, constants.__build__))
+_moduleLogger.info("OS: %s" % (os.uname()[0], ))
+_moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:])
+_moduleLogger.info("Hostname: %s" % os.uname()[1])
+
+try:
+       nqaap_gtk.run()
+finally:
+       logging.shutdown()
diff --git a/src/opt/Nqa-Audiobook-player/nqaap_gtk.py b/src/opt/Nqa-Audiobook-player/nqaap_gtk.py
new file mode 100644 (file)
index 0000000..eb2ed8f
--- /dev/null
@@ -0,0 +1,32 @@
+#! /usr/bin/env python  
+
+import logging
+
+import dbus
+import dbus.mainloop.glib
+import gobject
+import gtk
+
+from Player import Player
+from Gui import Gui
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+def run():
+    l = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+    gobject.threads_init()
+    gtk.gdk.threads_init()
+
+    gui = Gui()
+    controller = Player(ui = gui)
+    gui.controller = controller
+    gui.load_settings()
+
+    gtk.main()
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.DEBUG)
+    run()
diff --git a/src/opt/Nqa-Audiobook-player/settings.py b/src/opt/Nqa-Audiobook-player/settings.py
new file mode 100644 (file)
index 0000000..afb50a9
--- /dev/null
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+This file is part of Multilist.
+
+Multilist 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 3 of the License, or
+(at your option) any later version.
+
+Multilist 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 Multilist.  If not, see <http://www.gnu.org/licenses/>.
+
+Copyright (C) 2008 Christoph Würstle
+"""
+
+import logging
+
+import gtk
+
+import hildonize
+import gtk_toolbox
+
+try:
+       _
+except NameError:
+       _ = lambda x: x
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class SettingsDialog(object):
+
+       def __init__(self, parent):
+               self.window = None
+
+               self.__isPortraitCheckbutton = gtk.CheckButton("Portrait Mode")
+
+               self.__rotationSection = gtk.VBox()
+               self.__rotationSection.pack_start(self.__isPortraitCheckbutton, False, True)
+
+               rotationFrame = gtk.Frame("Rotation")
+               rotationFrame.add(self.__rotationSection)
+
+               self.__audioBooksPath = gtk.Entry()
+               self.__audioBooksPathButton = gtk.Button("Choose")
+               self.__audioBooksPathButton.connect("clicked", self._on_path_choose)
+
+               self.__audiobookPathSection = gtk.HBox()
+               self.__audiobookPathSection.pack_start(self.__audioBooksPath, True, True)
+               self.__audiobookPathSection.pack_start(self.__audioBooksPathButton, False, True)
+
+               self.__audiobookSection = gtk.VBox()
+               self.__audiobookSection.pack_start(self.__audiobookPathSection)
+
+               audiobookFrame = gtk.Frame("Audiobooks")
+               audiobookFrame.add(self.__audiobookSection)
+
+               settingsBox = gtk.VBox()
+               settingsBox.pack_start(rotationFrame, False, True)
+               settingsBox.pack_start(audiobookFrame, False, True)
+               settingsView = gtk.Viewport()
+               settingsView.add(settingsBox)
+               settingsScrollView = gtk.ScrolledWindow()
+               settingsScrollView.add(settingsView)
+               settingsScrollView.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+               parent.pack_start(settingsScrollView, True, True)
+
+               settingsScrollView = hildonize.hildonize_scrollwindow(settingsScrollView)
+
+       def set_portrait_state(self, isPortrait):
+               self.__isPortraitCheckbutton.set_active(isPortrait)
+
+       def is_portrait(self):
+               return self.__isPortraitCheckbutton.get_active()
+
+       def set_audiobook_path(self, path):
+               self.__audioBooksPath.set_text(path)
+
+       def get_audiobook_path(self):
+               return self.__audioBooksPath.get_text()
+
+       @gtk_toolbox.log_exception(_moduleLogger)
+       def _on_path_choose(self, *args):
+               fileChooser = gtk.FileChooserDialog(
+                       title="Audiobooks",
+                       action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+                       parent=self.window,
+               )
+               fileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+               fileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+               fileChooser.set_filename(self.__audioBooksPath.get_text())
+               userResponse = fileChooser.run()
+               fileChooser.hide()
+               if userResponse == gtk.RESPONSE_OK:
+                       filename = fileChooser.get_filename()
+                       self.__audioBooksPath.set_text(filename)
diff --git a/src/usr/share/applications/hildon/nqaap.desktop b/src/usr/share/applications/hildon/nqaap.desktop
new file mode 100644 (file)
index 0000000..4054f62
--- /dev/null
@@ -0,0 +1,10 @@
+[Desktop Entry]
+Version=1.0.0  
+Encoding=UTF-8
+Name=nQaap
+Exec=python /opt/Nqa-Audiobook-player/nqaap.py
+Icon=nqaap
+X-Icon-path=/usr/share/icons
+X-Window-Icon=nqaap
+Type=Application
+X-Osso-Type=application/x-executable
\ No newline at end of file
diff --git a/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png b/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png
new file mode 100644 (file)
index 0000000..9e38229
Binary files /dev/null and b/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png differ
diff --git a/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png b/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png
new file mode 100644 (file)
index 0000000..8538783
Binary files /dev/null and b/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png differ