--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+# 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!
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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]
--- /dev/null
+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
--- /dev/null
+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)
--- /dev/null
+<!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øren 'Pengman' Pedersen\r
+ <a href="mailto:pengmeister@hotmail.com"><pengmeister@hotmail.com></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
--- /dev/null
+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 ()
--- /dev/null
+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)
--- /dev/null
+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) )
--- /dev/null
+#!/usr/bin/env python
--- /dev/null
+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/")
--- /dev/null
+#!/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()
--- /dev/null
+#!/usr/bin/env python
+
+"""
+Open Issues
+ @bug not all of a message is shown
+ @bug Buttons are too small
+"""
+
+
+import gobject
+import gtk
+import dbus
+
+
+class _NullHildonModule(object):
+ pass
+
+
+try:
+ import hildon as _hildon
+ hildon = _hildon # Dumb but gets around pyflakiness
+except (ImportError, OSError):
+ hildon = _NullHildonModule
+
+
+IS_HILDON_SUPPORTED = hildon is not _NullHildonModule
+
+
+class _NullHildonProgram(object):
+
+ def add_window(self, window):
+ pass
+
+
+def _hildon_get_app_class():
+ return hildon.Program
+
+
+def _null_get_app_class():
+ return _NullHildonProgram
+
+
+try:
+ hildon.Program
+ get_app_class = _hildon_get_app_class
+except AttributeError:
+ get_app_class = _null_get_app_class
+
+
+def _hildon_set_application_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)
--- /dev/null
+#!/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()
--- /dev/null
+#! /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()
--- /dev/null
+#!/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)
--- /dev/null
+[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