From: epage Date: Fri, 7 May 2010 03:07:01 +0000 (+0000) Subject: Setting up a trunk for branching X-Git-Url: http://git.maemo.org/git/?p=nqaap;a=commitdiff_plain;h=f3924cb75e5fbb24f55b503e8bf3fd6fd26ab1ed Setting up a trunk for branching git-svn-id: file:///svnroot/nqaap/trunk@9 00ff6f12-f5ab-46b1-af0e-967c86d3154f --- f3924cb75e5fbb24f55b503e8bf3fd6fd26ab1ed diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..0a7610c --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +rm -r ~/nqaap; +svn export http://www.pengworld.com/python/n900/Audiobook/nqaap/ ~/nqaap +chmod +x ~/nqaap/src/opt/Nqa-Audiobook-player/nqaap.py +python ~/nqaap/build_nqaap.py + diff --git a/build_nqaap.py b/build_nqaap.py new file mode 100644 index 0000000..09e1de5 --- /dev/null +++ b/build_nqaap.py @@ -0,0 +1,82 @@ +#!/usr/bin/python2.5 + # -*- coding: utf-8 -*- + ## This program is free software; you can redistribute it and/or modify + ## it under the terms of the GNU General Public License as published + ## by the Free Software Foundation; version 2 only. + ## + ## This program is distributed in the hope that it will be useful, + ## but WITHOUT ANY WARRANTY; without even the implied warranty of + ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ## GNU General Public License for more details. + ## +import py2deb +import os +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + print + p=py2deb.Py2deb("nqaap") #This is the package name and MUST be in + #lowercase! (using e.g. "mClock" fails + #miserably...) + 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" + p.author="Soeren 'Pengman' Pedersen" + p.mail="pengmeister@gmail.com" + p.depends = "python2.5, python2.5-gtk2, python-gst0.10" + p.section="user/multimedia" + p.icon = "/usr/share/icons/hicolor/48x48/hildon/nqaap.png" + p.arch="all" #should be all for python, any for all arch + p.urgency="low" #not used in maemo onl for deb os + p.distribution="fremantle" + p.repository="extras-devel" + p.xsbc_bugtracker="http://talk.maemo.org/showthread.php?p=619738" + # p.postinstall="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your post install script + # p.postremove="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your post remove script + # p.preinstall="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your pre install script + # p.preremove="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your pre remove script + version = "0.8.0" #Version of your software, e.g. "1.2.0" or "0.8.2" + build = "2" #Build number, e.g. "1" for the first build of this + #version of your software. Increment + #for later re-builds of the same + #version of your software. Text with + #changelog information to be displayed + #in the package "Details" tab of the + #Maemo Application Manager + changeloginformation = "Merged changes from EPage (proper changelog later)\nNew Icon by Strutten." + # 0.7.2 : Seek bar now responds to clicks (rather than drags)\nFixed bug with wrong text showing on button after changed chapter. + # 0.7.1 : Fixed crash when current points to non existing book + # 0.7.0 : Now ignores pressed outside the chapter selection menu\nAdded help + # 0.6.1 : Fixed bug that prevented running on devices without Audiobook folder. + # Added tip on where to place audiobooks. + # 0.6.0 : Now also plays .mp3 files + # 0.5.0 : Second release. Now shows which chapter is playing, and scrolls to it when changing. + # 0.4.9 : First release. Now it should work + # + dir_name = "src" #Name of the subfolder containing your package + #source files + #(e.g. usr\share\icons\hicolor\scalable\myappicon.svg, + #usr\lib\myapp\somelib.py). We suggest + #to leave it named src in all projects + #and will refer to that in the wiki + #article on maemo.org + + #Thanks to DareTheHair from talk.maemo.org for this snippet that + #recursively builds the file list + for root, dirs, files in os.walk(dir_name): + real_dir = root[len(dir_name):] + if '.' in real_dir: + continue # if some part of the dirname contains '.' we + # ignore all files (avoid .svn + # and others) + fake_file = [] + for f in files: + fake_file.append(root + os.sep + f + "|" + f) + if len(fake_file) > 0: + p[real_dir] = fake_file + print p + r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True) diff --git a/build_nqaap_maemo5.py b/build_nqaap_maemo5.py new file mode 100644 index 0000000..5854d83 --- /dev/null +++ b/build_nqaap_maemo5.py @@ -0,0 +1,82 @@ +#!/usr/bin/python2.5 + # -*- coding: utf-8 -*- + ## This program is free software; you can redistribute it and/or modify + ## it under the terms of the GNU General Public License as published + ## by the Free Software Foundation; version 2 only. + ## + ## This program is distributed in the hope that it will be useful, + ## but WITHOUT ANY WARRANTY; without even the implied warranty of + ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ## GNU General Public License for more details. + ## +import py2deb +import os +if __name__ == "__main__": + try: + os.chdir(os.path.dirname(sys.argv[0])) + except: + pass + print + p=py2deb.Py2deb("nqaap") #This is the package name and MUST be in + #lowercase! (using e.g. "mClock" fails + #miserably...) + 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" + p.author="Soeren 'Pengman' Pedersen" + p.mail="pengmeister@gmail.com" + p.depends = "python2.5, python2.5-gtk2, python2.5-dbus, python2.5-telepathy, python2.5-gobject, python-gst0.10" + p.section="user/multimedia" + p.icon = "/usr/share/icons/hicolor/48x48/hildon/nqaap.png" + p.arch="all" #should be all for python, any for all arch + p.urgency="low" #not used in maemo onl for deb os + p.distribution="fremantle" + p.repository="extras-devel" + p.xsbc_bugtracker="http://talk.maemo.org/showthread.php?p=619738" + # p.postinstall="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your post install script + # p.postremove="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your post remove script + # p.preinstall="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your pre install script + # p.preremove="""#!/bin/sh + # chmod +x /usr/bin/mclock.py""" #Set here your pre remove script + version = "0.8.0" #Version of your software, e.g. "1.2.0" or "0.8.2" + build = "2" #Build number, e.g. "1" for the first build of this + #version of your software. Increment + #for later re-builds of the same + #version of your software. Text with + #changelog information to be displayed + #in the package "Details" tab of the + #Maemo Application Manager + changeloginformation = "Merged changes from EPage (proper changelog later)\nNew Icon by Strutten." + # 0.7.2 : Seek bar now responds to clicks (rather than drags)\nFixed bug with wrong text showing on button after changed chapter. + # 0.7.1 : Fixed crash when current points to non existing book + # 0.7.0 : Now ignores pressed outside the chapter selection menu\nAdded help + # 0.6.1 : Fixed bug that prevented running on devices without Audiobook folder. + # Added tip on where to place audiobooks. + # 0.6.0 : Now also plays .mp3 files + # 0.5.0 : Second release. Now shows which chapter is playing, and scrolls to it when changing. + # 0.4.9 : First release. Now it should work + # + dir_name = "src" #Name of the subfolder containing your package + #source files + #(e.g. usr\share\icons\hicolor\scalable\myappicon.svg, + #usr\lib\myapp\somelib.py). We suggest + #to leave it named src in all projects + #and will refer to that in the wiki + #article on maemo.org + + #Thanks to DareTheHair from talk.maemo.org for this snippet that + #recursively builds the file list + for root, dirs, files in os.walk(dir_name): + real_dir = root[len(dir_name):] + if '.' in real_dir: + continue # if some part of the dirname contains '.' we + # ignore all files (avoid .svn + # and others) + fake_file = [] + for f in files: + fake_file.append(root + os.sep + f + "|" + f) + if len(fake_file) > 0: + p[real_dir] = fake_file + print p + r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True) diff --git a/src/opt/Nqa-Audiobook-player/.ropeproject/config.py b/src/opt/Nqa-Audiobook-player/.ropeproject/config.py new file mode 100644 index 0000000..ffebcd4 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/.ropeproject/config.py @@ -0,0 +1,85 @@ +# The default ``config.py`` + + +def set_prefs(prefs): + """This function is called before opening the project""" + + # Specify which files and folders to ignore in the project. + # Changes to ignored resources are not added to the history and + # VCSs. Also they are not returned in `Project.get_files()`. + # Note that ``?`` and ``*`` match all characters but slashes. + # '*.pyc': matches 'test.pyc' and 'pkg/test.pyc' + # 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc' + # '.svn': matches 'pkg/.svn' and all of its children + # 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o' + # 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o' + prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject', + '.hg', '.svn', '_svn', '.git'] + + # Specifies which files should be considered python files. It is + # useful when you have scripts inside your project. Only files + # ending with ``.py`` are considered to be python files by + # default. + #prefs['python_files'] = ['*.py'] + + # Custom source folders: By default rope searches the project + # for finding source folders (folders that should be searched + # for finding modules). You can add paths to that list. Note + # that rope guesses project source folders correctly most of the + # time; use this if you have any problems. + # The folders should be relative to project root and use '/' for + # separating folders regardless of the platform rope is running on. + # 'src/my_source_folder' for instance. + #prefs.add('source_folders', 'src') + + # You can extend python path for looking up modules + #prefs.add('python_path', '~/python/') + + # Should rope save object information or not. + prefs['save_objectdb'] = True + prefs['compress_objectdb'] = False + + # If `True`, rope analyzes each module when it is being saved. + prefs['automatic_soa'] = True + # The depth of calls to follow in static object analysis + prefs['soa_followed_calls'] = 0 + + # If `False` when running modules or unit tests "dynamic object + # analysis" is turned off. This makes them much faster. + prefs['perform_doa'] = True + + # Rope can check the validity of its object DB when running. + prefs['validate_objectdb'] = True + + # How many undos to hold? + prefs['max_history_items'] = 32 + + # Shows whether to save history across sessions. + prefs['save_history'] = True + prefs['compress_history'] = False + + # Set the number spaces used for indenting. According to + # :PEP:`8`, it is best to use 4 spaces. Since most of rope's + # unit-tests use 4 spaces it is more reliable, too. + prefs['indent_size'] = 4 + + # Builtin and c-extension modules that are allowed to be imported + # and inspected by rope. + prefs['extension_modules'] = [] + + # Add all standard c-extensions to extension_modules list. + prefs['import_dynload_stdmods'] = True + + # If `True` modules with syntax errors are considered to be empty. + # The default value is `False`; When `False` syntax errors raise + # `rope.base.exceptions.ModuleSyntaxError` exception. + prefs['ignore_syntax_errors'] = False + + # If `True`, rope ignores unresolvable imports. Otherwise, they + # appear in the importing namespace. + prefs['ignore_bad_imports'] = False + + +def project_opened(project): + """This function is called after opening the project""" + # Do whatever you like here! diff --git a/src/opt/Nqa-Audiobook-player/Audiobook.py b/src/opt/Nqa-Audiobook-player/Audiobook.py new file mode 100644 index 0000000..00fac87 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/Audiobook.py @@ -0,0 +1,176 @@ +from __future__ import with_statement + +import os + +import logging + + +_moduleLogger = logging.getLogger(__name__) + + +class Audiobook(object): + + def __init__(self, path, current_chapter = 0): + self.title = "" + self._coverPath = "" + self._chapterPaths = [] + self.current_chapter = current_chapter + + if is_playlist_book(path): + self._scan_index(path) + elif is_dir_book(path): + self._scan_dir(path) + elif is_single_chapter(path): + self._scan_chapter(path) + else: + _moduleLogger.info("Audiobook not found in path: " + path) + raise IOError("Audiobook directory not found") + + if len(self._chapterPaths) <= self.current_chapter: + _moduleLogger.warning( + "Audiobook chapter out of range (%s/%s)" % ( + self.current_chapter, len(self._chapterPaths) + ) + ) + self.current_chapter = 0 + + @property + def chapters(self): + return self._chapterPaths + + def get_current_chapter(self): + return self._chapterPaths[self.current_chapter] + + def set_chapter(self, chapter): + if chapter in self.chapters: + self.current_chapter = self.chapters.index(chapter) + else: + raise Exception("Unknown chapter set") + + def get_previous_chapter(self): + """ + @returns the file name for the next chapter, without path + """ + if 0 == self.current_chapter: + return False + else: + self.current_chapter -= 1 + return self._chapterPaths[self.current_chapter] + + def get_next_chapter(self): + """ + @returns the file name for the next chapter, without path + """ + if len(self._chapterPaths) == self.current_chapter: + return False + else: + self.current_chapter += 1 + return self._chapterPaths[self.current_chapter] + + def get_cover_img(self): + if self._coverPath: + return self._coverPath + else: + return "%s/NoCover.png" % os.path.dirname(__file__) + + def _scan_dir(self, root): + self.title = os.path.split(root)[-1] + dirContent = ( + os.path.join(root, f) + for f in os.listdir(root) + if not f.startswith(".") + ) + + files = [ + path + for path in dirContent + if os.path.isfile(os.path.join(root, path)) + ] + + images = [ + path + for path in files + if path.rsplit(".", 1)[-1] in ["png", "gif", "jpg", "jpeg"] + ] + if 0 < len(images): + self.cover = images[0] + + self._chapterPaths = [ + path + for path in files + if is_single_chapter(path) + ] + self._chapterPaths.sort() + + def _scan_chapter(self, file): + self._chapterPaths = [file] + + def _scan_playlist(self, file): + root = os.path.dirname(file) + self.title = os.path.basename(file).rsplit(".")[0] + + with open(file, 'r') as f: + for line in f: + if line.startswith("#"): + continue + path = line + if not os.path.isabs(path): + path = os.path.normpath(os.path.join(root, path)) + self._chapterPaths.append(path) + # Not sorting, assuming the file is in the desired order + + def _scan_index(self, file): + import unicodedata + + # Reading file + looking_for_title = False + looking_for_cover = False + looking_for_chapters = False + + with open(file, 'r') as f: + for line in f: + # title + ascii = unicodedata.normalize('NFKD', unicode(line, "latin-1")).encode('ascii', 'ignore') + print line[:-1], "PIC\n" in line, line in "#PIC" + if "#BOOK" in line: + looking_for_title = True + continue + if looking_for_title: + self.title = line[:-1] + looking_for_title = False + if "#PIC" in line: + looking_for_cover = True + continue + if looking_for_cover: + self.cover = line[:-1] + looking_for_cover = False + if "#TRACKS" in line: + looking_for_chapters = True + continue + if looking_for_chapters: + if "#CHAPTERS" in line: + break # no further information needed + self.chapters.append(line.split(':')[0]) + + +def is_dir_book(path): + return os.path.isdir(path) + + +def is_playlist_book(path): + return path.rsplit(".", 1)[-1] in ["m3u"] + + +def is_single_chapter(path): + return path.rsplit(".", 1)[-1] in ["awb", "mp3", "spx", "ogg", "ac3", "wav"] + + +def is_book(path): + if is_dir_book(path): + return True + elif is_playlist_book(path): + return True + elif is_single_chapter(path): + return True + else: + return False diff --git a/src/opt/Nqa-Audiobook-player/Browser.py b/src/opt/Nqa-Audiobook-player/Browser.py new file mode 100644 index 0000000..36d4e34 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/Browser.py @@ -0,0 +1,5 @@ +import os + + +def open(url): + 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) diff --git a/src/opt/Nqa-Audiobook-player/CallMonitor.py b/src/opt/Nqa-Audiobook-player/CallMonitor.py new file mode 100644 index 0000000..97be5a7 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/CallMonitor.py @@ -0,0 +1,145 @@ +import logging + +import gobject +import dbus +import telepathy + +import gtk_toolbox + + +_moduleLogger = logging.getLogger(__name__) +DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' + + +class NewChannelSignaller(object): + + def __init__(self, on_new_channel): + self._sessionBus = dbus.SessionBus() + self._on_user_new_channel = on_new_channel + + def start(self): + self._sessionBus.add_signal_receiver( + self._on_new_channel, + "NewChannel", + "org.freedesktop.Telepathy.Connection", + None, + None + ) + + def stop(self): + self._sessionBus.remove_signal_receiver( + self._on_new_channel, + "NewChannel", + "org.freedesktop.Telepathy.Connection", + None, + None + ) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_new_channel( + self, channelObjectPath, channelType, handleType, handle, supressHandler + ): + connObjectPath = channel_path_to_conn_path(channelObjectPath) + serviceName = path_to_service_name(channelObjectPath) + try: + self._on_user_new_channel( + self._sessionBus, serviceName, connObjectPath, channelObjectPath, channelType + ) + except Exception: + _moduleLogger.exception("Blocking exception from being passed up") + + +class ChannelClosed(object): + + def __init__(self, bus, conn, chan, on_closed): + self.__on_closed = on_closed + + chan[telepathy.interfaces.CHANNEL].connect_to_signal( + "Closed", + self._on_closed, + ) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_closed(self): + self.__on_closed(self) + + +class CallMonitor(gobject.GObject): + + __gsignals__ = { + 'call_start' : ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (), + ), + 'call_end' : ( + gobject.SIGNAL_RUN_LAST, + gobject.TYPE_NONE, + (), + ), + } + + def __init__(self): + gobject.GObject.__init__(self) + self._isActive = False + self._newChannelMonitor = NewChannelSignaller(self._on_new_channel) + self._channelClosedMonitors = [] + + def start(self): + self._isActive = True + self._newChannelMonitor.start() + + def stop(self): + self._isActive = False + self._newChannelMonitor.stop() + + def _on_new_channel(self, sessionBus, serviceName, connObjectPath, channelObjectPath, channelType): + if not self._isActive: + return + + if channelType != telepathy.interfaces.CHANNEL_TYPE_STREAMED_MEDIA: + return + + cmName = cm_from_path(connObjectPath) + conn = telepathy.client.Connection(serviceName, connObjectPath) + try: + chan = telepathy.client.Channel(serviceName, channelObjectPath) + except dbus.exceptions.UnknownMethodException: + _moduleLogger.exception("Client might not have implemented a deprecated method") + return + + missDetection = ChannelClosed( + sessionBus, conn, chan, self._on_close + ) + self._outstandingRequests.append(missDetection) + if len(self._outstandingRequests) == 1: + self.emit("call_start") + + def _on_close(self, channelCloseMonitor): + self._outstandingRequests.remove(channelCloseMonitor) + if not self._outstandingRequests: + self.emit("call_stop") + + +def channel_path_to_conn_path(channelObjectPath): + """ + >>> channel_path_to_conn_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") + '/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME' + """ + return channelObjectPath.rsplit("/", 1)[0] + + +def path_to_service_name(path): + """ + >>> path_to_service_name("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") + 'org.freedesktop.Telepathy.ConnectionManager.theonering.gv.USERNAME' + """ + return ".".join(path[1:].split("/")[0:7]) + + +def cm_from_path(path): + """ + >>> cm_from_path("/org/freedesktop/Telepathy/ConnectionManager/theonering/gv/USERNAME/Channel1") + 'theonering' + """ + return path[1:].split("/")[4] diff --git a/src/opt/Nqa-Audiobook-player/FileStorage.py b/src/opt/Nqa-Audiobook-player/FileStorage.py new file mode 100644 index 0000000..44cfc36 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/FileStorage.py @@ -0,0 +1,68 @@ +from __future__ import with_statement # enable with + +import os + +import logging + + +log = logging.getLogger(__name__) + + +class FileStorage(object): + + def __init__(self, path="~/.SornPlayer/"): + # Setup dir + log.info("init filestorage") + self.path = path + self.books_path = os.path.join(self.path, "books/") + if not os.path.isdir(self.books_path): + os.makedirs(self.books_path) + + # Read config file + self.conf = os.path.join(self.path, "current") + self.selected = None + + if os.path.isfile(self.conf): + with open(self.conf) as f: + self.selected = f.readline() + + # Read current book file + + def get_selected(self): + """returns the currently selected book""" + return self.selected + + def select_book(self, bookName): + """ Sets the book as the currently playing, and adds it to the + database if it is not already there""" + book_file = os.path.join(self.books_path, bookName) + if not os.path.isfile(book_file): + with open(book_file, 'w') as f: + f.write("0\n") #Current chapter + f.write("0\n") #Current position + + self.selected = bookName + with open(self.conf, 'w') as f: + f.write(self.selected) # + + def set_time(self, chapter, position): + """ Sets the current time for the book that is currently selected""" + try: + book_file = os.path.join(self.books_path, self.selected) + log.debug("writing time (%s, %s) to: %s"%( chapter, position, book_file )) + with open(book_file, 'w') as f: + f.write(str(int(chapter)) + "\n") #Current chapter + f.write(str(int(position)) + "\n") #Current position + except: + log.error("Unable to save to file: %s" % book_file) + + def get_time(self): + """Returns the current saved time for the current selected book""" + chapter, position = 0 , 0 + book_file = os.path.join(self.books_path, self.selected) + log.debug("getting time from: " + book_file) + with open(book_file, 'r') as f: + chapter = int(f.readline()) + position = int(f.readline()) + + return chapter, position diff --git a/src/opt/Nqa-Audiobook-player/Gui.py b/src/opt/Nqa-Audiobook-player/Gui.py new file mode 100644 index 0000000..0f51f36 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/Gui.py @@ -0,0 +1,594 @@ +from __future__ import with_statement + +import os +import ConfigParser +import logging + +import gobject +import gtk + +import constants +import hildonize +import gtk_toolbox +import Browser +import CallMonitor +import settings + +if hildonize.IS_FREMANTLE_SUPPORTED: + # I don't normally do this but I want to error as loudly as possibly when an issue arises + import hildon + + +_moduleLogger = logging.getLogger(__name__) + + +class Gui(object): + + def __init__(self): + _moduleLogger.info("Starting GUI") + self._callMonitor = CallMonitor.CallMonitor() + self.__settingsWindow = None + self.__settingsManager = None + self._bookSelection = [] + self._bookSelectionIndex = -1 + self._chapterSelection = [] + self._chapterSelectionIndex = -1 + self._sleepSelection = ["0", "1", "10", "20", "30", "60"] + self._sleepSelectionIndex = 0 + + self.__window_in_fullscreen = False #The window isn't in full screen mode initially. + self.__isPortrait = False + + self.controller = None + self.sleep_timer = None + self.auto_chapter_selected = False # true if we are in the + # midle of an automatic + # chapter change + + self.ignore_next_chapter_change = False + # set up gui + self.setup() + self._callMonitor.connect("call_start", self.__on_call_started) + self._callMonitor.start() + + def setup(self): + if hildonize.IS_FREMANTLE_SUPPORTED: + gtk.set_application_name(constants.__pretty_app_name__) # window title + self._app = hildonize.get_app_class()() + self.win = gtk.Window() + self.win = hildonize.hildonize_window(self._app, self.win) + + # Cover image + self.cover = gtk.Image() + + # Controls: + + # Label that hold the title of the book,and maybe the chapter + self.title = gtk.Label("Select a book to start listening") + self.title.set_justify(gtk.JUSTIFY_CENTER) + + # Seekbar + if hildonize.IS_FREMANTLE_SUPPORTED: + self.seek = hildon.Seekbar() + self.seek.set_range(0.0, 100) + self.seek.set_draw_value(False) + self.seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS) + self.seek.connect('change-value', self.seek_changed) # event + # self.seek.connect('value-changed',self.seek_changed) # event + else: + adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1) + self.seek = gtk.HScale(adjustment) + self.seek.connect('change-value', self.seek_changed) # event + + # Pause button + if hildonize.IS_FREMANTLE_SUPPORTED: + self.backButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL) + image = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.HILDON_SIZE_FINGER_HEIGHT) + self.backButton.set_image(image) + + self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL) + + self.forwardButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL) + image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.HILDON_SIZE_FINGER_HEIGHT) + self.forwardButton.set_image(image) + else: + self.backButton = gtk.Button(stock=gtk.STOCK_GO_BACK) + self.button = gtk.Button() + self.forwardButton = gtk.Button(stock=gtk.STOCK_GO_FORWARD) + self.set_button_text("Play", "Start playing the audiobook") + self.backButton.connect('clicked', self._on_previous_chapter) + self.button.connect('clicked', self.play_pressed) # event + self.forwardButton.connect('clicked', self._on_next_chapter) + + self._toolbar = gtk.HBox() + self._toolbar.pack_start(self.backButton, False, False, 0) + self._toolbar.pack_start(self.button, True, True, 0) + self._toolbar.pack_start(self.forwardButton, False, False, 0) + + # Box to hold the controls: + self._controlLayout = gtk.VBox() + self._controlLayout.pack_start(self.title, True, True, 0) + self._controlLayout.pack_start(self.seek, True, True, 0) + self._controlLayout.pack_start(self._toolbar, False, True, 0) + + #Box that divides the layout in two: cover on the lefta + #and controls on the right + self._viewLayout = gtk.HBox() + self._viewLayout.pack_start(self.cover, True, True, 0) + self._viewLayout.add(self._controlLayout) + + self._menuBar = gtk.MenuBar() + self._menuBar.show() + + self._mainLayout = gtk.VBox() + self._mainLayout.pack_start(self._menuBar, False, False, 0) + self._mainLayout.pack_start(self._viewLayout) + + # Add hbox to the window + self.win.add(self._mainLayout) + + #Menu: + # Create menu + self._populate_menu() + + self.win.connect("delete_event", self.quit) # Add shutdown event + self.win.connect("key-press-event", self.on_key_press) + self.win.connect("window-state-event", self._on_window_state_change) + + self.win.show_all() + + # Run update timer + self.setup_timers() + + def _populate_menu(self): + self._menuBar = hildonize.hildonize_menu( + self.win, + self._menuBar, + ) + if hildonize.IS_FREMANTLE_SUPPORTED: + # Create a picker button + self.book_button = hildon.Button(gtk.HILDON_SIZE_AUTO, + hildon.BUTTON_ARRANGEMENT_VERTICAL) + self.book_button.set_title("Audiobook") # Set a title to the button + self.book_button.connect("clicked", self._on_select_audiobook) + + # Create a picker button + self.chapter_button = hildon.Button(gtk.HILDON_SIZE_AUTO, + hildon.BUTTON_ARRANGEMENT_VERTICAL) + self.chapter_button.set_title("Chapter") # Set a title to the button + self.chapter_button.connect("clicked", self._on_select_chapter) + + # Create a picker button + self.sleeptime_button = hildon.Button(gtk.HILDON_SIZE_AUTO, + hildon.BUTTON_ARRANGEMENT_VERTICAL) + self.sleeptime_button.set_title("Sleeptimer") # Set a title to the button + self.sleeptime_button.connect("clicked", self._on_select_sleep) + + settings_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL) + settings_button.set_label("Settings") + settings_button.connect("clicked", self._on_settings) + + help_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL) + help_button.set_label("Help") + help_button.connect("clicked", self.get_help) + + self._menuBar.append(self.book_button) # Add the button to menu + self._menuBar.append(self.chapter_button) # Add the button to menu + self._menuBar.append(self.sleeptime_button) # Add the button to menu + self._menuBar.append(settings_button) + self._menuBar.append(help_button) + self._menuBar.show_all() + else: + self._audiobookMenuItem = gtk.MenuItem("Audiobook: ") + self._audiobookMenuItem.connect("activate", self._on_select_audiobook) + + self._chapterMenuItem = gtk.MenuItem("Chapter: ") + self._chapterMenuItem.connect("activate", self._on_select_chapter) + + self._sleepMenuItem = gtk.MenuItem("Sleeptimer: 0") + self._sleepMenuItem.connect("activate", self._on_select_sleep) + + settingsMenuItem = gtk.MenuItem("Settings") + settingsMenuItem.connect("activate", self._on_settings) + + helpMenuItem = gtk.MenuItem("Help") + helpMenuItem.connect("activate", self.get_help) + + booksMenu = gtk.Menu() + booksMenu.append(self._audiobookMenuItem) + booksMenu.append(self._chapterMenuItem) + booksMenu.append(self._sleepMenuItem) + booksMenu.append(settingsMenuItem) + booksMenu.append(helpMenuItem) + + booksMenuItem = gtk.MenuItem("Books") + booksMenuItem.show() + booksMenuItem.set_submenu(booksMenu) + self._menuBar.append(booksMenuItem) + self._menuBar.show_all() + + def setup_timers(self): + self.seek_timer = timeout_add_seconds(3, self.update_seek) + + def save_settings(self): + config = ConfigParser.SafeConfigParser() + self._save_settings(config) + with open(constants._user_settings_, "wb") as configFile: + config.write(configFile) + + def _save_settings(self, config): + config.add_section(constants.__pretty_app_name__) + config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait)) + config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen)) + config.set(constants.__pretty_app_name__, "audiopath", self.controller.get_books_path()) + + def load_settings(self): + config = ConfigParser.SafeConfigParser() + config.read(constants._user_settings_) + self._load_settings(config) + + def _load_settings(self, config): + isPortrait = False + window_in_fullscreen = False + booksPath = constants._default_book_path_ + try: + isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait") + window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen") + booksPath = config.get(constants.__pretty_app_name__, "audiopath") + except ConfigParser.NoSectionError, e: + _moduleLogger.info( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ) + ) + + if isPortrait ^ self.__isPortrait: + if isPortrait: + orientation = gtk.ORIENTATION_VERTICAL + else: + orientation = gtk.ORIENTATION_HORIZONTAL + self.set_orientation(orientation) + + self.__window_in_fullscreen = window_in_fullscreen + if self.__window_in_fullscreen: + self.win.fullscreen() + else: + self.win.unfullscreen() + + self.controller.load_books_path(booksPath) + + @staticmethod + def __format_name(path): + if os.path.isfile(path): + return os.path.basename(path).rsplit(".", 1)[0] + else: + return os.path.basename(path) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_select_audiobook(self, *args): + if not self._bookSelection: + return + index = hildonize.touch_selector( + self.win, + "Audiobook", + (self.__format_name(bookPath) for bookPath in self._bookSelection), + self._bookSelectionIndex if 0 <= self._bookSelectionIndex else 0, + ) + self._bookSelectionIndex = index + bookName = self._bookSelection[index] + self.controller.set_book(bookName) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_select_chapter(self, *args): + if not self._chapterSelection: + return + index = hildonize.touch_selector( + self.win, + "Chapter", + (self.__format_name(chapterPath) for chapterPath in self._chapterSelection), + self._chapterSelectionIndex if 0 <= self._chapterSelectionIndex else 0, + ) + self._chapterSelectionIndex = index + chapterName = self._chapterSelection[index] + self.controller.set_chapter(chapterName) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_select_sleep(self, *args): + if self.sleep_timer is not None: + gobject.source_remove(self.sleep_timer) + + try: + index = hildonize.touch_selector( + self.win, + "Sleeptimer", + self._sleepSelection, + self._sleepSelectionIndex if 0 <= self._sleepSelectionIndex else 0, + ) + except RuntimeError: + _moduleLogger.exception("Handling as if user cancelled") + hildonize.show_information_banner(self.win, "Sleep timer canceled") + index = 0 + + self._sleepSelectionIndex = index + sleepName = self._sleepSelection[index] + + time_out = int(sleepName) + if 0 < time_out: + timeout_add_seconds(time_out * 60, self.sleep) + + if hildonize.IS_FREMANTLE_SUPPORTED: + self.sleeptime_button.set_text("Sleeptimer", sleepName) + else: + self._sleepMenuItem.get_child().set_text("Sleeptimer: %s" % (sleepName, )) + + @gtk_toolbox.log_exception(_moduleLogger) + def __on_call_started(self, callMonitor): + self.pause() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_settings(self, *args): + if self.__settingsWindow is None: + vbox = gtk.VBox() + self.__settingsManager = settings.SettingsDialog(vbox) + + self.__settingsWindow = gtk.Window() + self.__settingsWindow.add(vbox) + self.__settingsWindow = hildonize.hildonize_window(self._app, self.__settingsWindow) + self.__settingsManager.window = self.__settingsWindow + + self.__settingsWindow.set_title("Settings") + self.__settingsWindow.set_transient_for(self.win) + self.__settingsWindow.set_default_size(*self.win.get_size()) + self.__settingsWindow.connect("delete-event", self._on_settings_delete) + self.__settingsManager.set_portrait_state(self.__isPortrait) + self.__settingsManager.set_audiobook_path(self.controller.get_books_path()) + self.__settingsWindow.set_modal(True) + self.__settingsWindow.show_all() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_settings_delete(self, *args): + self.__settingsWindow.emit_stop_by_name("delete-event") + self.__settingsWindow.hide() + self.__settingsWindow.set_modal(False) + + isPortrait = self.__settingsManager.is_portrait() + if isPortrait ^ self.__isPortrait: + if isPortrait: + orientation = gtk.ORIENTATION_VERTICAL + else: + orientation = gtk.ORIENTATION_HORIZONTAL + self.set_orientation(orientation) + if self.__settingsManager.get_audiobook_path() != self.controller.get_books_path(): + self.controller.load_books_path(self.__settingsManager.get_audiobook_path()) + + return True + + @gtk_toolbox.log_exception(_moduleLogger) + def update_seek(self): + #print self.controller.get_percentage() + if self.controller.is_playing(): + gtk.gdk.threads_enter() + self.seek.set_value(self.controller.get_percentage() * 100) + gtk.gdk.threads_leave() + #self.controller.get_percentage() + return True # run again + + @gtk_toolbox.log_exception(_moduleLogger) + def sleep(self): + _moduleLogger.info("sleep time timeout") + hildonize.show_information_banner(self.win, "Sleep timer") + self.controller.stop() + self.set_button_text("Resume", "Resume playing the audiobook") + return False # do not repeat + + @gtk_toolbox.log_exception(_moduleLogger) + def get_help(self, button): + Browser.open("file:///opt/Nqa-Audiobook-player/Help/nqaap.html") + + @gtk_toolbox.log_exception(_moduleLogger) + def seek_changed(self, seek, scroll , value): + # print "sok", scroll + self.controller.seek_percent(seek.get_value() / 100.0) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_next_chapter(self, *args): + self.controller.next_chapter() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_previous_chapter(self, *args): + self.controller.previous_chapter() + + @gtk_toolbox.log_exception(_moduleLogger) + def play_pressed(self, button): + if self.controller.is_playing(): + self.pause() + else: + self.play() + + @gtk_toolbox.log_exception(_moduleLogger) + def on_key_press(self, widget, event, *args): + RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter) + isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK) + if ( + event.keyval == gtk.keysyms.F6 or + event.keyval in RETURN_TYPES and isCtrl + ): + # The "Full screen" hardware key has been pressed + if self.__window_in_fullscreen: + self.win.unfullscreen () + else: + self.win.fullscreen () + return True + elif event.keyval == gtk.keysyms.o and isCtrl: + self._toggle_rotate() + return True + elif ( + event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and + event.get_state() & gtk.gdk.CONTROL_MASK + ): + self.quit() + elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK: + with open(constants._user_logpath_, "r") as f: + logLines = f.xreadlines() + log = "".join(logLines) + self._clipboard.set_text(str(log)) + return True + elif event.keyval in RETURN_TYPES: + if self.controller.is_playing(): + self.pause() + else: + self.play() + return True + elif event.keyval == gtk.keysyms.Left: + self.controller.previous_chapter() + return True + elif event.keyval == gtk.keysyms.Right: + self.controller.next_chapter() + return True + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_window_state_change(self, widget, event, *args): + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: + self.__window_in_fullscreen = True + else: + self.__window_in_fullscreen = False + + @gtk_toolbox.log_exception(_moduleLogger) + def quit(self, *args): # what are the arguments? + _moduleLogger.info("Shutting down") + try: + self.save_settings() + self.controller.stop() # to save the state + finally: + gtk.main_quit() + + # Actions: + + def play(self): + self.set_button_text("Stop", "Stop playing the audiobook") + self.controller.play() + + def pause(self): + self.set_button_text("Resume", "Resume playing the audiobook") + self.controller.stop() + + def set_orientation(self, orientation): + if orientation == gtk.ORIENTATION_VERTICAL: + if self.__isPortrait: + return + hildonize.window_to_portrait(self.win) + self.__isPortrait = True + + self._viewLayout.remove(self._controlLayout) + self._mainLayout.add(self._controlLayout) + elif orientation == gtk.ORIENTATION_HORIZONTAL: + if not self.__isPortrait: + return + hildonize.window_to_landscape(self.win) + self.__isPortrait = False + + self._mainLayout.remove(self._controlLayout) + self._viewLayout.add(self._controlLayout) + else: + raise NotImplementedError(orientation) + + def get_orientation(self): + return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL + + def _toggle_rotate(self): + if self.__isPortrait: + self.set_orientation(gtk.ORIENTATION_HORIZONTAL) + else: + self.set_orientation(gtk.ORIENTATION_VERTICAL) + + def change_chapter(self, chapterName): + if chapterName is None: + _moduleLogger.debug("chapter selection canceled.") + #import pdb; pdb.set_trace() # start debugger + self.ignore_next_chapter_change = True + return True # this should end the function and indicate it has been handled + + if self.ignore_next_chapter_change: + self.ignore_next_chapter_change = False + _moduleLogger.debug("followup chapter selection canceled.") + #import pdb; pdb.set_trace() # start debugger + return True # this should end the function and indicate it has been handled + + if self.auto_chapter_selected: + _moduleLogger.debug("chapter changed (by controller) to: %s" % chapterName) + self.auto_chapter_selected = False + # do nothing + else: + _moduleLogger.debug("chapter selection sendt to controller: %s" % chapterName) + self.controller.set_chapter(chapterName) # signal controller + self.set_button_text("Play", "Start playing the audiobook") # reset button + + def set_button_text(self, title, text): + if hildonize.IS_FREMANTLE_SUPPORTED: + self.button.set_text("Resume", "Resume playing the audiobook") + else: + self.button.set_label("%s - %s" % (title, text)) + + def set_books(self, books): + _moduleLogger.debug("new books") + del self._bookSelection[:] + self._bookSelection.extend(books) + if len(books) == 0 and self.controller is not None: + hildonize.show_information_banner(self.win, "No audiobooks found. \nPlease place your audiobooks in the directory %s" % self.controller.get_books_path()) + + def set_book(self, bookPath, cover): + bookName = self.__format_name(bookPath) + + self.set_button_text("Play", "Start playing the audiobook") # reset button + self.title.set_text(bookName) + if hildonize.IS_FREMANTLE_SUPPORTED: + self.book_button.set_text("Audiobook", bookName) + else: + self._audiobookMenuItem.get_child().set_text("Audiobook: %s" % (bookName, )) + if cover != "": + self.cover.set_from_file(cover) + + def set_chapter(self, chapterIndex): + ''' + Called from controller whenever a new chapter is started + + chapter parameter is supposed to be the index for the chapter, not the name + ''' + self.auto_chapter_selected = True + if hildonize.IS_FREMANTLE_SUPPORTED: + self.chapter_button.set_text("Chapter", str(chapterIndex)) + else: + self._chapterMenuItem.get_child().set_text("Chapter: %s" % (chapterIndex, )) + + def set_chapters(self, chapters): + _moduleLogger.debug("setting chapters" ) + del self._chapterSelection[:] + self._chapterSelection.extend(chapters) + + def set_sleep_timer(self, mins): + pass + + # Utils + def set_selected_value(self, button, value): + i = button.get_selector().get_model(0).index[value] # get index of value from list + button.set_active(i) # set active index to that index + + +def _old_timeout_add_seconds(timeout, callback): + return gobject.timeout_add(timeout * 1000, callback) + + +def _timeout_add_seconds(timeout, callback): + return gobject.timeout_add_seconds(timeout, callback) + + +try: + gobject.timeout_add_seconds + timeout_add_seconds = _timeout_add_seconds +except AttributeError: + timeout_add_seconds = _old_timeout_add_seconds + + +if __name__ == "__main__": + g = Gui(None) diff --git a/src/opt/Nqa-Audiobook-player/Help/nqaap.html b/src/opt/Nqa-Audiobook-player/Help/nqaap.html new file mode 100644 index 0000000..4bd4598 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/Help/nqaap.html @@ -0,0 +1,111 @@ + + + + nQa Audiobook Player + + + + + + +

nQa Audiobook Player

+ + +
+

Table of Contents

+
+ +
+
+ +
+

1 Create book

+
+ +
    +
  • + Find audiofiles for the book in either mp3 or awb format (encoding + instructions later) +
  • +
  • + Ensure that files are named in such a way that a lexical sort places + them in playing order. +
  • +
  • + Create a directory in ~/MyDocs/Audiobooks (create this directory too if it + does not exist) with the name of the book. +
  • +
  • + Place audiofiles in this dir, all files must be in this + directory. It cant find files in further subdirectories. +
  • +
  • + (Optional) Place an imagefile in the directory. If prsent this file + will be used as the cover of the book. +
  • +
+
+ +
+ +
+

2 Play

+
+ +
    +
  • + Open nqaap +
  • +
  • + Open menu +
  • +
  • + Select book +
  • +
  • + Press play +
  • +
+
+
+

Author: Søren 'Pengman' Pedersen + <pengmeister@hotmail.com> +

+

Date: 2010-04-17 21:23:57

+

HTML generated by org-mode 6.09a in emacs 23

+
+ diff --git a/src/opt/Nqa-Audiobook-player/NoCover.png b/src/opt/Nqa-Audiobook-player/NoCover.png new file mode 100644 index 0000000..80e142b Binary files /dev/null and b/src/opt/Nqa-Audiobook-player/NoCover.png differ diff --git a/src/opt/Nqa-Audiobook-player/Player.py b/src/opt/Nqa-Audiobook-player/Player.py new file mode 100644 index 0000000..522b2fa --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/Player.py @@ -0,0 +1,185 @@ +import os +import threading +import time +import logging + +import constants +import hildonize +import Audiobook +import FileStorage + + +_moduleLogger = logging.getLogger(__name__) + + +class Player(object): + + def __init__(self, ui): + self.storage = FileStorage.FileStorage(path = constants._data_path_) + if hildonize.IS_HILDON_SUPPORTED and not hildonize.IS_FREMANTLE_SUPPORTED: + import SimpleOSSOPlayer as _SimplePlayer + SimpleGStreamer = _SimplePlayer # silence PyFlakes + else: + import SimpleGStreamer as SimplePlayer + self.player = SimplePlayer.SimplePlayer(self.next_chapter) + self.ui = ui + self.audiobook = None + self._bookDir = None + self._bookPaths = {} + self.load_books_path(constants._default_book_path_) + + def get_books_path(self): + return self._bookDir + + def load_books_path(self, booksPath): + if self.audiobook is not None: + position = self.player.elapsed() + self.storage.set_time(self.audiobook.current_chapter, position) + + self._bookDir = booksPath + + self._bookPaths = dict( + (self.__format_name(bookPath), bookPath) + for bookPath in self._find_books() + ) + if self.ui is not None: + bookPaths = self._bookPaths.values() + bookPaths.sort() + self.ui.set_books(bookPaths) + + lastBookName = self.storage.get_selected() + if lastBookName is not None: + _moduleLogger.info("continuing book: %s" % lastBookName) + try: + bookPath = self._bookPaths[lastBookName] + self.set_book(bookPath) + except KeyError: + _moduleLogger.info("Audiobook was not found") + except IOError: + _moduleLogger.info("Audiobook could not be loaded") + + @staticmethod + def __format_name(path): + if os.path.isfile(path): + return os.path.basename(path).rsplit(".", 1)[0] + else: + return os.path.basename(path) + + def set_book(self, bookPath): + oldBookName = self.storage.get_selected() + try: + bookName = self.__format_name(bookPath) + self.storage.select_book(bookName) + chapter_num, _ = self.storage.get_time() + self.audiobook = Audiobook.Audiobook( + bookPath, + chapter_num + ) + except: + self.storage.select_book(oldBookName) + raise + + # self.player.set_file(self.audiobook.get_current_chapter()) + # self.player.seek_time(time) + + if self.ui is not None: + self.ui.set_book(bookPath, self.audiobook.get_cover_img()) + self.ui.set_chapters(self.audiobook.chapters) + + chapter_title = self.audiobook.chapters[chapter_num] + self.set_chapter(chapter_title, True) + + def set_chapter(self, chapter, continuing = False): + _moduleLogger.info("set chapter:" + chapter + " : Continuing: " + str(continuing)) + self.audiobook.set_chapter(chapter) + self.player.set_file(self.audiobook.get_current_chapter()) + if not continuing: + self.storage.set_time(self.audiobook.current_chapter, 0) + + if self.ui is not None: + self.ui.set_chapter(self.audiobook.current_chapter) + + def previous_chapter(self, *args): + _moduleLogger.info("Going back a chapter") + self.player.stop() + next_file = self.audiobook.get_previous_chapter() + if next_file is not False: + self.set_chapter(next_file) + self.player.play() + else: # the book is over + self.storage.set_time(0, 0) + + def next_chapter(self, *args): + _moduleLogger.info("Advancing a chapter") + self.player.stop() + next_file = self.audiobook.get_next_chapter() + if next_file is not False: + self.set_chapter(next_file) + self.player.play() + else: # the book is over + self.storage.set_time(0, 0) + + def play(self): + if self.audiobook is not None: + self.player.play() + _, target_time = self.storage.get_time() + if 0 < target_time: + time.sleep(1) + self.player.seek_time(target_time) + #print self.player.elapsed() + else: + print "No book selected, find one in ", self._bookDir + + def stop(self): + position = self.player.elapsed() + self.player.stop() + + if self.audiobook is not None: + self.storage.set_time(self.audiobook.current_chapter, position) + + def is_playing(self): + return self.player.playing + + def sleeptimer(self, secs): + #print "sleeper", secs + time.sleep(secs) + #print "now its time to sleep" + self.stop() + + def start_sleeptimer(self, secs): + #print "startin sleep" + sleep_thread = threading.Thread(target=self.sleeptimer, args=(secs, )) + sleep_thread.start() + #print "started sleep" + + def get_percentage(self): + try: + return float(self.player.elapsed()) / float(self.player.duration()) + except ZeroDivisionError: + return 0.0 + + def seek_percent(self, ratio): + try: + target = self.player.duration() * ratio # Calculate where to seek + self.player.seek_time(int(target)) # seek + + position = self.player.elapsed() + self.storage.set_time(self.audiobook.current_chapter, target) # save position + return True + except: + _moduleLogger.excetion("Seek failed") + return False + + def _find_books(self): + try: + paths = ( + os.path.join(self._bookDir, f) + for f in os.listdir(self._bookDir) + ) + return ( + path + for path in paths + if Audiobook.is_book(path) + ) + except OSError: + return () diff --git a/src/opt/Nqa-Audiobook-player/SimpleGStreamer.py b/src/opt/Nqa-Audiobook-player/SimpleGStreamer.py new file mode 100644 index 0000000..75fce6b --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/SimpleGStreamer.py @@ -0,0 +1,93 @@ +import os +import logging + +import gst + +import gtk_toolbox + + +_moduleLogger = logging.getLogger(__name__) + + +class SimplePlayer(object): + + def __init__(self, on_playing_done = None): + #Fields + self.has_file = False + self.playing = False + self.__elapsed = 0 + + #Event callbacks + self.on_playing_done = on_playing_done + + #Set up GStreamer + self.player = gst.element_factory_make("playbin2", "player") + fakesink = gst.element_factory_make("fakesink", "fakesink") + self.player.set_property("video-sink", fakesink) + bus = self.player.get_bus() + bus.add_signal_watch() + bus.connect("message", self.on_message) + + #Constants + self.time_format = gst.Format(gst.FORMAT_TIME) + self.seek_flag = gst.SEEK_FLAG_FLUSH + + @gtk_toolbox.log_exception(_moduleLogger) + def on_message(self, bus, message): + t = message.type + if t == gst.MESSAGE_EOS: # End-Of-Stream + self.player.set_state(gst.STATE_NULL) + self.playing = False + if self.on_playing_done is not None: # event callback + self.on_playing_done(self) + elif t == gst.MESSAGE_ERROR: + self.player.set_state(gst.STATE_NULL) + err, debug = message.parse_error() + #print "Error: %s" % err, debug + _moduleLogger.error("Error: %s, (%s)" % (err, debug)) + self.playing = False + + def set_file(self, file): + _moduleLogger.info("set file: %s", file) + if os.path.isfile(file): + if self.playing: + self.stop() + + file = os.path.abspath(file) # ensure absolute path + _moduleLogger.debug("set file (absolute path): %s "%file) + self.player.set_property("uri", "file://" + file) + self.has_file = True + else: + _moduleLogger.error("File: %s not found" % file) + + def play(self): + _moduleLogger.info("Started playing") + self.player.set_state(gst.STATE_PLAYING) + self.playing = True + + def stop(self): + self.player.set_state(gst.STATE_NULL) + self.playing = False + _moduleLogger.info("Stopped playing") + + def elapsed(self): + try: + self.__elapsed = self.player.query_position(self.time_format, None)[0] + except: + pass + return self.__elapsed + + def duration(self): + try: + return self.player.query_duration(self.time_format, None)[0] + except: + _moduleLogger.exception("Query failed") + return 0 + + def seek_time(self, ns): + _moduleLogger.debug("Seeking to: %s", ns) + self.player.seek_simple(self.time_format, self.seek_flag, ns) + + def __seek_percent(self, percent): + format = gst.Format(gst.FORMAT_PERCENT) + self.player.seek_simple(format, self.seek_flag, percent) diff --git a/src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py b/src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py new file mode 100644 index 0000000..9129f1a --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/SimpleOSSOPlayer.py @@ -0,0 +1,115 @@ +import os +import logging + +import dbus + +import gtk_toolbox + + +_moduleLogger = logging.getLogger(__name__) + + +class SimplePlayer(object): + + SERVICE_NAME = "com.nokia.osso_media_server" + OBJECT_PATH = "/com/nokia/osso_media_server" + AUDIO_INTERFACE_NAME = "com.nokia.osso_media_server.music" + + def __init__(self, on_playing_done = None): + #Fields + self.has_file = False + self.playing = False + self.__elapsed = 0 + + #Event callbacks + self.on_playing_done = on_playing_done + + session_bus = dbus.SessionBus() + + # Get the osso-media-player proxy object + oms_object = session_bus.get_object( + self.SERVICE_NAME, + self.OBJECT_PATH, + introspect=False, + follow_name_owner_changes=True, + ) + # Use the audio interface + oms_audio_interface = dbus.Interface( + oms_object, + self.AUDIO_INTERFACE_NAME, + ) + self._audioProxy = oms_audio_interface + + self._audioProxy.connect_to_signal("state_changed", self._on_state_changed) + self._audioProxy.connect_to_signal("end_of_stream", self._on_end_of_stream) + + error_signals = [ + "no_media_selected", + "file_not_found", + "type_not_found", + "unsupported_type", + "gstreamer", + "dsp", + "device_unavailable", + "corrupted_file", + "out_of_memory", + "audio_codec_not_supported", + ] + for error in error_signals: + self._audioProxy.connect_to_signal(error, self._on_error) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_error(self, *args): + self.playing = False + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_end_of_stream(self, *args): + self.playing = False + if self.on_playing_done is not None: # event callback + self.on_playing_done(self) + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_state_changed(self, state): + _moduleLogger.info("State: %s", state) + + def set_file(self, file): + _moduleLogger.info("set file: %s", file) + if os.path.isfile(file): + if self.playing: + self.stop() + + uri = "file://" + file + self._audioProxy.set_media_location(uri) + self.has_file = True + else: + _moduleLogger.error("File: %s not found" % file) + + def play(self): + _moduleLogger.info("Started playing") + self._audioProxy.play() + self.playing = True + + def stop(self): + self._audioProxy.stop() + self.playing = False + _moduleLogger.info("Stopped playing") + + def elapsed(self): + pos_info = self._audioProxy.get_position() + if isinstance(pos_info, tuple): + pos, _ = pos_info + return pos + else: + return 0 + + def duration(self): + pos_info = self._audioProxy.get_position() + if isinstance(pos_info, tuple): + _, dur = pos_info + return dur + else: + return 0 + + def seek_time(self, ns): + _moduleLogger.debug("Seeking to: %s", ns) + self._audioProxy.seek( dbus.Int32(1), dbus.Int32(ns) ) diff --git a/src/opt/Nqa-Audiobook-player/__init__.py b/src/opt/Nqa-Audiobook-player/__init__.py new file mode 100644 index 0000000..4265cc3 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python diff --git a/src/opt/Nqa-Audiobook-player/constants.py b/src/opt/Nqa-Audiobook-player/constants.py new file mode 100644 index 0000000..9f7bea9 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/constants.py @@ -0,0 +1,11 @@ +import os + +__pretty_app_name__ = "nQa Audiobook Player" +__app_name__ = "nqaap" +__version__ = "0.7.2-1" +__build__ = 0 +__app_magic__ = 0xdeadbeef +_data_path_ = os.path.join(os.path.expanduser("~"), ".%s" % __app_name__) +_user_settings_ = "%s/settings.ini" % _data_path_ +_user_logpath_ = "%s/%s.log" % (_data_path_, __app_name__) +_default_book_path_ = os.path.join(os.path.expanduser("~"), "MyDocs/Audiobooks/") diff --git a/src/opt/Nqa-Audiobook-player/gtk_toolbox.py b/src/opt/Nqa-Audiobook-player/gtk_toolbox.py new file mode 100644 index 0000000..784c871 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/gtk_toolbox.py @@ -0,0 +1,577 @@ +#!/usr/bin/python + +from __future__ import with_statement + +import os +import errno +import sys +import time +import itertools +import functools +import contextlib +import logging +import threading +import Queue + +import gobject +import gtk + + +_moduleLogger = logging.getLogger(__name__) + + +def get_screen_orientation(): + width, height = gtk.gdk.get_default_root_window().get_size() + if width < height: + return gtk.ORIENTATION_VERTICAL + else: + return gtk.ORIENTATION_HORIZONTAL + + +def orientation_change_connect(handler, *args): + """ + @param handler(orientation, *args) -> None(?) + """ + initialScreenOrientation = get_screen_orientation() + orientationAndArgs = list(itertools.chain((initialScreenOrientation, ), args)) + + def _on_screen_size_changed(screen): + newScreenOrientation = get_screen_orientation() + if newScreenOrientation != orientationAndArgs[0]: + orientationAndArgs[0] = newScreenOrientation + handler(*orientationAndArgs) + + rootScreen = gtk.gdk.get_default_root_window() + return gtk.connect(rootScreen, "size-changed", _on_screen_size_changed) + + +@contextlib.contextmanager +def flock(path, timeout=-1): + WAIT_FOREVER = -1 + DELAY = 0.1 + timeSpent = 0 + + acquired = False + + while timeSpent <= timeout or timeout == WAIT_FOREVER: + try: + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR) + acquired = True + break + except OSError, e: + if e.errno != errno.EEXIST: + raise + time.sleep(DELAY) + timeSpent += DELAY + + assert acquired, "Failed to grab file-lock %s within timeout %d" % (path, timeout) + + try: + yield fd + finally: + os.unlink(path) + + +@contextlib.contextmanager +def gtk_lock(): + gtk.gdk.threads_enter() + try: + yield + finally: + gtk.gdk.threads_leave() + + +def find_parent_window(widget): + while True: + parent = widget.get_parent() + if isinstance(parent, gtk.Window): + return parent + widget = parent + + +def make_idler(func): + """ + Decorator that makes a generator-function into a function that will continue execution on next call + """ + a = [] + + @functools.wraps(func) + def decorated_func(*args, **kwds): + if not a: + a.append(func(*args, **kwds)) + try: + a[0].next() + return True + except StopIteration: + del a[:] + return False + + return decorated_func + + +def asynchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + def execute(allArgs): + args, kwargs = allArgs + with gtk_lock(): + original_func(*args, **kwargs) + return False + + @functools.wraps(original_func) + def delayed_func(*args, **kwargs): + gobject.idle_add(execute, (args, kwargs)) + + return delayed_func + + +def synchronous_gtk_message(original_func): + """ + @note Idea came from http://www.aclevername.com/articles/python-webgui/ + """ + + @functools.wraps(original_func) + def immediate_func(*args, **kwargs): + with gtk_lock(): + return original_func(*args, **kwargs) + + return immediate_func + + +def autostart(func): + """ + >>> @autostart + ... def grep_sink(pattern): + ... print "Looking for %s" % pattern + ... while True: + ... line = yield + ... if pattern in line: + ... print line, + >>> g = grep_sink("python") + Looking for python + >>> g.send("Yeah but no but yeah but no") + >>> g.send("A series of tubes") + >>> g.send("python generators rock!") + python generators rock! + >>> g.close() + """ + + @functools.wraps(func) + def start(*args, **kwargs): + cr = func(*args, **kwargs) + cr.next() + return cr + + return start + + +@autostart +def printer_sink(format = "%s"): + """ + >>> pr = printer_sink("%r") + >>> pr.send("Hello") + 'Hello' + >>> pr.send("5") + '5' + >>> pr.send(5) + 5 + >>> p = printer_sink() + >>> p.send("Hello") + Hello + >>> p.send("World") + World + >>> # p.throw(RuntimeError, "Goodbye") + >>> # p.send("Meh") + >>> # p.close() + """ + while True: + item = yield + print format % (item, ) + + +@autostart +def null_sink(): + """ + Good for uses like with cochain to pick up any slack + """ + while True: + item = yield + + +@autostart +def comap(function, target): + """ + >>> p = printer_sink() + >>> cm = comap(lambda x: x+1, p) + >>> cm.send((0, )) + 1 + >>> cm.send((1.0, )) + 2.0 + >>> cm.send((-2, )) + -1 + """ + while True: + try: + item = yield + mappedItem = function(*item) + target.send(mappedItem) + except Exception, e: + _moduleLogger.exception("Forwarding exception!") + target.throw(e.__class__, str(e)) + + +def _flush_queue(queue): + while not queue.empty(): + yield queue.get() + + +@autostart +def queue_sink(queue): + """ + >>> q = Queue.Queue() + >>> qs = queue_sink(q) + >>> qs.send("Hello") + >>> qs.send("World") + >>> qs.throw(RuntimeError, "Goodbye") + >>> qs.send("Meh") + >>> qs.close() + >>> print [i for i in _flush_queue(q)] + [(None, 'Hello'), (None, 'World'), (, 'Goodbye'), (None, 'Meh'), (, None)] + """ + while True: + try: + item = yield + queue.put((None, item)) + except Exception, e: + queue.put((e.__class__, str(e))) + except GeneratorExit: + queue.put((GeneratorExit, None)) + raise + + +def decode_item(item, target): + if item[0] is None: + target.send(item[1]) + return False + elif item[0] is GeneratorExit: + target.close() + return True + else: + target.throw(item[0], item[1]) + return False + + +def nonqueue_source(queue, target): + isDone = False + while not isDone: + item = queue.get() + isDone = decode_item(item, target) + while not queue.empty(): + queue.get_nowait() + + +def threaded_stage(target, thread_factory = threading.Thread): + messages = Queue.Queue() + + run_source = functools.partial(nonqueue_source, messages, target) + thread = thread_factory(target=run_source) + thread.setDaemon(True) + thread.start() + + # Sink running in current thread + return queue_sink(messages) + + +def log_exception(logger): + + def log_exception_decorator(func): + + @functools.wraps(func) + def wrapper(*args, **kwds): + try: + return func(*args, **kwds) + except Exception: + logger.exception(func.__name__) + + return wrapper + + return log_exception_decorator + + +class LoginWindow(object): + + def __init__(self, widgetTree): + """ + @note Thread agnostic + """ + self._dialog = widgetTree.get_widget("loginDialog") + self._parentWindow = widgetTree.get_widget("mainWindow") + self._serviceCombo = widgetTree.get_widget("serviceCombo") + self._usernameEntry = widgetTree.get_widget("usernameentry") + self._passwordEntry = widgetTree.get_widget("passwordentry") + + self._serviceList = gtk.ListStore(gobject.TYPE_INT, gobject.TYPE_STRING) + self._serviceCombo.set_model(self._serviceList) + cell = gtk.CellRendererText() + self._serviceCombo.pack_start(cell, True) + self._serviceCombo.add_attribute(cell, 'text', 1) + self._serviceCombo.set_active(0) + + widgetTree.get_widget("loginbutton").connect("clicked", self._on_loginbutton_clicked) + widgetTree.get_widget("logins_close_button").connect("clicked", self._on_loginclose_clicked) + + def request_credentials(self, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceCombo.hide() + self._serviceList.clear() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + self._passwordEntry.set_text("") + finally: + self._dialog.hide() + + return username, password + + def request_credentials_from(self, + services, + parentWindow = None, + defaultCredentials = ("", "") + ): + """ + @note UI Thread + """ + if parentWindow is None: + parentWindow = self._parentWindow + + self._serviceList.clear() + for serviceIdserviceName in services: + self._serviceList.append(serviceIdserviceName) + self._serviceCombo.set_active(0) + self._serviceCombo.show() + + self._usernameEntry.set_text(defaultCredentials[0]) + self._passwordEntry.set_text(defaultCredentials[1]) + + try: + self._dialog.set_transient_for(parentWindow) + self._dialog.set_default_response(gtk.RESPONSE_OK) + response = self._dialog.run() + if response != gtk.RESPONSE_OK: + raise RuntimeError("Login Cancelled") + + username = self._usernameEntry.get_text() + password = self._passwordEntry.get_text() + finally: + self._dialog.hide() + + itr = self._serviceCombo.get_active_iter() + serviceId = int(self._serviceList.get_value(itr, 0)) + self._serviceList.clear() + return serviceId, username, password + + def _on_loginbutton_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_OK) + + def _on_loginclose_clicked(self, *args): + self._dialog.response(gtk.RESPONSE_CANCEL) + + +def safecall(f, errorDisplay=None, default=None, exception=Exception): + ''' + Returns modified f. When the modified f is called and throws an + exception, the default value is returned + ''' + def _safecall(*args, **argv): + try: + return f(*args,**argv) + except exception, e: + if errorDisplay is not None: + errorDisplay.push_exception(e) + return default + return _safecall + + +class ErrorDisplay(object): + + def __init__(self, widgetTree): + super(ErrorDisplay, self).__init__() + self.__errorBox = widgetTree.get_widget("errorEventBox") + self.__errorDescription = widgetTree.get_widget("errorDescription") + self.__errorClose = widgetTree.get_widget("errorClose") + self.__parentBox = self.__errorBox.get_parent() + + self.__errorBox.connect("button_release_event", self._on_close) + + self.__messages = [] + self.__parentBox.remove(self.__errorBox) + + def push_message_with_lock(self, message): + with gtk_lock(): + self.push_message(message) + + def push_message(self, message): + self.__messages.append(message) + if 1 == len(self.__messages): + self.__show_message(message) + + def push_exception_with_lock(self): + with gtk_lock(): + self.push_exception() + + def push_exception(self): + userMessage = str(sys.exc_info()[1]) + self.push_message(userMessage) + _moduleLogger.exception(userMessage) + + def pop_message(self): + del self.__messages[0] + if 0 == len(self.__messages): + self.__hide_message() + else: + self.__errorDescription.set_text(self.__messages[0]) + + def _on_close(self, *args): + self.pop_message() + + def __show_message(self, message): + self.__errorDescription.set_text(message) + self.__parentBox.pack_start(self.__errorBox, False, False) + self.__parentBox.reorder_child(self.__errorBox, 1) + + def __hide_message(self): + self.__errorDescription.set_text("") + self.__parentBox.remove(self.__errorBox) + + +class DummyErrorDisplay(object): + + def __init__(self): + super(DummyErrorDisplay, self).__init__() + + self.__messages = [] + + def push_message_with_lock(self, message): + self.push_message(message) + + def push_message(self, message): + if 0 < len(self.__messages): + self.__messages.append(message) + else: + self.__show_message(message) + + def push_exception(self, exception = None): + userMessage = str(sys.exc_value) + _moduleLogger.exception(userMessage) + + def pop_message(self): + if 0 < len(self.__messages): + self.__show_message(self.__messages[0]) + del self.__messages[0] + + def __show_message(self, message): + _moduleLogger.debug(message) + + +class MessageBox(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class MessageBox2(gtk.MessageDialog): + + def __init__(self, message): + parent = None + gtk.MessageDialog.__init__( + self, + parent, + gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_OK, + message, + ) + self.set_default_response(gtk.RESPONSE_OK) + self.connect('response', self._handle_clicked) + + def _handle_clicked(self, *args): + self.destroy() + + +class PopupCalendar(object): + + def __init__(self, parent, displayDate, title = ""): + self._displayDate = displayDate + + self._calendar = gtk.Calendar() + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + self._calendar.set_display_options( + gtk.CALENDAR_SHOW_HEADING | + gtk.CALENDAR_SHOW_DAY_NAMES | + gtk.CALENDAR_NO_MONTH_CHANGE | + 0 + ) + self._calendar.connect("day-selected", self._on_day_selected) + + self._popupWindow = gtk.Window() + self._popupWindow.set_title(title) + self._popupWindow.add(self._calendar) + self._popupWindow.set_transient_for(parent) + self._popupWindow.set_modal(True) + self._popupWindow.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self._popupWindow.set_skip_pager_hint(True) + self._popupWindow.set_skip_taskbar_hint(True) + + def run(self): + self._popupWindow.show_all() + + def _on_day_selected(self, *args): + try: + self._calendar.select_month(self._displayDate.month, self._displayDate.year) + self._calendar.select_day(self._displayDate.day) + except Exception, e: + _moduleLogger.exception(e) + + +if __name__ == "__main__": + if False: + import datetime + cal = PopupCalendar(None, datetime.datetime.now()) + cal._popupWindow.connect("destroy", lambda w: gtk.main_quit()) + cal.run() + + gtk.main() diff --git a/src/opt/Nqa-Audiobook-player/hildonize.py b/src/opt/Nqa-Audiobook-player/hildonize.py new file mode 100644 index 0000000..08049ba --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/hildonize.py @@ -0,0 +1,743 @@ +#!/usr/bin/env python + +""" +Open Issues + @bug not all of a message is shown + @bug Buttons are too small +""" + + +import gobject +import gtk +import dbus + + +class _NullHildonModule(object): + pass + + +try: + import hildon as _hildon + hildon = _hildon # Dumb but gets around pyflakiness +except (ImportError, OSError): + hildon = _NullHildonModule + + +IS_HILDON_SUPPORTED = hildon is not _NullHildonModule + + +class _NullHildonProgram(object): + + def add_window(self, window): + pass + + +def _hildon_get_app_class(): + return hildon.Program + + +def _null_get_app_class(): + return _NullHildonProgram + + +try: + hildon.Program + get_app_class = _hildon_get_app_class +except AttributeError: + get_app_class = _null_get_app_class + + +def _hildon_set_application_title(window, title): + pass + + +def _null_set_application_title(window, title): + window.set_title(title) + + +if IS_HILDON_SUPPORTED: + set_application_title = _hildon_set_application_title +else: + set_application_title = _null_set_application_title + + +def _fremantle_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.StackableWindow() + if oldWindow.get_child() is not None: + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _hildon_hildonize_window(app, window): + oldWindow = window + newWindow = hildon.Window() + if oldWindow.get_child() is not None: + oldWindow.get_child().reparent(newWindow) + app.add_window(newWindow) + return newWindow + + +def _null_hildonize_window(app, window): + return window + + +try: + hildon.StackableWindow + hildonize_window = _fremantle_hildonize_window +except AttributeError: + try: + hildon.Window + hildonize_window = _hildon_hildonize_window + except AttributeError: + hildonize_window = _null_hildonize_window + + +def _fremantle_hildonize_menu(window, gtkMenu): + appMenu = hildon.AppMenu() + window.set_app_menu(appMenu) + gtkMenu.get_parent().remove(gtkMenu) + return appMenu + + +def _hildon_hildonize_menu(window, gtkMenu): + hildonMenu = gtk.Menu() + for child in gtkMenu.get_children(): + child.reparent(hildonMenu) + window.set_menu(hildonMenu) + gtkMenu.destroy() + return hildonMenu + + +def _null_hildonize_menu(window, gtkMenu): + return gtkMenu + + +try: + hildon.AppMenu + GTK_MENU_USED = False + IS_FREMANTLE_SUPPORTED = True + hildonize_menu = _fremantle_hildonize_menu +except AttributeError: + GTK_MENU_USED = True + IS_FREMANTLE_SUPPORTED = False + if IS_HILDON_SUPPORTED: + hildonize_menu = _hildon_hildonize_menu + else: + hildonize_menu = _null_hildonize_menu + + +def _hildon_set_button_auto_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_AUTO_HEIGHT) + + +def _null_set_button_auto_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_AUTO_HEIGHT + gtk.Button.set_theme_size + set_button_auto_selectable = _hildon_set_button_auto_selectable +except AttributeError: + set_button_auto_selectable = _null_set_button_auto_selectable + + +def _hildon_set_button_finger_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_FINGER_HEIGHT) + + +def _null_set_button_finger_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_FINGER_HEIGHT + gtk.Button.set_theme_size + set_button_finger_selectable = _hildon_set_button_finger_selectable +except AttributeError: + set_button_finger_selectable = _null_set_button_finger_selectable + + +def _hildon_set_button_thumb_selectable(button): + button.set_theme_size(hildon.HILDON_SIZE_THUMB_HEIGHT) + + +def _null_set_button_thumb_selectable(button): + pass + + +try: + hildon.HILDON_SIZE_THUMB_HEIGHT + gtk.Button.set_theme_size + set_button_thumb_selectable = _hildon_set_button_thumb_selectable +except AttributeError: + set_button_thumb_selectable = _null_set_button_thumb_selectable + + +def _hildon_set_cell_thumb_selectable(renderer): + renderer.set_property("scale", 1.5) + + +def _null_set_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_cell_thumb_selectable = _hildon_set_cell_thumb_selectable +else: + set_cell_thumb_selectable = _null_set_cell_thumb_selectable + + +def _hildon_set_pix_cell_thumb_selectable(renderer): + renderer.set_property("stock-size", 48) + + +def _null_set_pix_cell_thumb_selectable(renderer): + pass + + +if IS_HILDON_SUPPORTED: + set_pix_cell_thumb_selectable = _hildon_set_pix_cell_thumb_selectable +else: + set_pix_cell_thumb_selectable = _null_set_pix_cell_thumb_selectable + + +def _fremantle_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, "", message) + + +def _hildon_show_information_banner(parent, message): + hildon.hildon_banner_show_information(parent, None, message) + + +def _null_show_information_banner(parent, message): + pass + + +if IS_FREMANTLE_SUPPORTED: + show_information_banner = _fremantle_show_information_banner +else: + try: + hildon.hildon_banner_show_information + show_information_banner = _hildon_show_information_banner + except AttributeError: + show_information_banner = _null_show_information_banner + + +def _fremantle_show_busy_banner_start(parent, message): + hildon.hildon_gtk_window_set_progress_indicator(parent, True) + return parent + + +def _fremantle_show_busy_banner_end(banner): + hildon.hildon_gtk_window_set_progress_indicator(banner, False) + + +def _hildon_show_busy_banner_start(parent, message): + return hildon.hildon_banner_show_animation(parent, None, message) + + +def _hildon_show_busy_banner_end(banner): + banner.destroy() + + +def _null_show_busy_banner_start(parent, message): + return None + + +def _null_show_busy_banner_end(banner): + assert banner is None + + +try: + hildon.hildon_gtk_window_set_progress_indicator + show_busy_banner_start = _fremantle_show_busy_banner_start + show_busy_banner_end = _fremantle_show_busy_banner_end +except AttributeError: + try: + hildon.hildon_banner_show_animation + show_busy_banner_start = _hildon_show_busy_banner_start + show_busy_banner_end = _hildon_show_busy_banner_end + except AttributeError: + show_busy_banner_start = _null_show_busy_banner_start + show_busy_banner_end = _null_show_busy_banner_end + + +def _hildon_hildonize_text_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7) + + +def _null_hildonize_text_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_text_entry = _hildon_hildonize_text_entry +else: + hildonize_text_entry = _null_hildonize_text_entry + + +def _hildon_window_to_portrait(window): + # gtk documentation is unclear whether this does a "=" or a "|=" + flags = hildon.PORTRAIT_MODE_SUPPORT | hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _hildon_window_to_landscape(window): + # gtk documentation is unclear whether this does a "=" or a "&= ~" + flags = hildon.PORTRAIT_MODE_SUPPORT + hildon.hildon_gtk_window_set_portrait_flags(window, flags) + + +def _null_window_to_portrait(window): + pass + + +def _null_window_to_landscape(window): + pass + + +try: + hildon.PORTRAIT_MODE_SUPPORT + hildon.PORTRAIT_MODE_REQUEST + hildon.hildon_gtk_window_set_portrait_flags + + window_to_portrait = _hildon_window_to_portrait + window_to_landscape = _hildon_window_to_landscape +except AttributeError: + window_to_portrait = _null_window_to_portrait + window_to_landscape = _null_window_to_landscape + + +def get_device_orientation(): + bus = dbus.SystemBus() + try: + rawMceRequest = bus.get_object("com.nokia.mce", "/com/nokia/mce/request") + mceRequest = dbus.Interface(rawMceRequest, dbus_interface="com.nokia.mce.request") + orientation, standState, faceState, xAxis, yAxis, zAxis = mceRequest.get_device_orientation() + except dbus.exception.DBusException: + # catching for documentation purposes that when a system doesn't + # support this, this is what to expect + raise + + if orientation == "": + return gtk.ORIENTATION_HORIZONTAL + elif orientation == "": + return gtk.ORIENTATION_VERTICAL + else: + raise RuntimeError("Unknown orientation: %s" % orientation) + + +def _hildon_hildonize_password_entry(textEntry): + textEntry.set_property('hildon-input-mode', 7 | (1 << 29)) + + +def _null_hildonize_password_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_password_entry = _hildon_hildonize_password_entry +else: + hildonize_password_entry = _null_hildonize_password_entry + + +def _hildon_hildonize_combo_entry(comboEntry): + comboEntry.set_property('hildon-input-mode', 1 << 4) + + +def _null_hildonize_combo_entry(textEntry): + pass + + +if IS_HILDON_SUPPORTED: + hildonize_combo_entry = _hildon_hildonize_combo_entry +else: + hildonize_combo_entry = _null_hildonize_combo_entry + + +def _fremantle_hildonize_scrollwindow(scrolledWindow): + pannableWindow = hildon.PannableArea() + + child = scrolledWindow.get_child() + scrolledWindow.remove(child) + pannableWindow.add(child) + + parent = scrolledWindow.get_parent() + if parent is not None: + parent.remove(scrolledWindow) + parent.add(pannableWindow) + + return pannableWindow + + +def _hildon_hildonize_scrollwindow(scrolledWindow): + hildon.hildon_helper_set_thumb_scrollbar(scrolledWindow, True) + return scrolledWindow + + +def _null_hildonize_scrollwindow(scrolledWindow): + return scrolledWindow + + +try: + hildon.PannableArea + hildonize_scrollwindow = _fremantle_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow +except AttributeError: + try: + hildon.hildon_helper_set_thumb_scrollbar + hildonize_scrollwindow = _hildon_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _hildon_hildonize_scrollwindow + except AttributeError: + hildonize_scrollwindow = _null_hildonize_scrollwindow + hildonize_scrollwindow_with_viewport = _null_hildonize_scrollwindow + + +def _hildon_request_number(parent, title, range, default): + spinner = hildon.NumberEditor(*range) + spinner.set_value(default) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _null_request_number(parent, title, range, default): + adjustment = gtk.Adjustment(default, range[0], range[1], 1, 5, 0) + spinner = gtk.SpinButton(adjustment, 0, 0) + spinner.set_wrap(False) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(spinner) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return spinner.get_value_as_int() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.NumberEditor # TODO deprecated in fremantle + request_number = _hildon_request_number +except AttributeError: + request_number = _null_request_number + + +def _hildon_touch_selector(parent, title, items, defaultIndex): + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + selector = hildon.TouchSelector() + selector.append_text_column(model, True) + selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE) + selector.set_active(0, defaultIndex) + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + return selector.get_active(0) + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +def _on_null_touch_selector_activated(treeView, path, column, dialog, pathData): + dialog.response(gtk.RESPONSE_OK) + pathData[0] = path + + +def _null_touch_selector(parent, title, items, defaultIndex = -1): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + for item in items: + model.append((item, )) + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(scrolledWin) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + pathData = [None] + treeView.connect("row-activated", _on_null_touch_selector_activated, dialog, pathData) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + if pathData[0] is None: + raise RuntimeError("No selection made") + return pathData[0][0] + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelector + touch_selector = _hildon_touch_selector +except AttributeError: + touch_selector = _null_touch_selector + + +def _hildon_touch_selector_entry(parent, title, items, defaultItem): + # Got a segfault when using append_text_column with TouchSelectorEntry, so using this way + try: + selector = hildon.TouchSelectorEntry(text=True) + except TypeError: + selector = hildon.hildon_touch_selector_entry_new_text() + defaultIndex = -1 + for i, item in enumerate(items): + selector.append_text(item) + if item == defaultItem: + defaultIndex = i + + dialog = hildon.PickerDialog(parent) + dialog.set_selector(selector) + + if 0 < defaultIndex: + selector.set_active(0, defaultIndex) + else: + selector.get_entry().set_text(defaultItem) + + try: + dialog.show_all() + response = dialog.run() + finally: + dialog.hide() + + if response == gtk.RESPONSE_OK: + return selector.get_entry().get_text() + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + + +def _on_null_touch_selector_entry_entry_changed(entry, result, selection, defaultIndex): + custom = entry.get_text().strip() + if custom: + result[0] = custom + selection.unselect_all() + else: + result[0] = None + selection.select_path((defaultIndex, )) + + +def _on_null_touch_selector_entry_entry_activated(customEntry, dialog, result): + dialog.response(gtk.RESPONSE_OK) + result[0] = customEntry.get_text() + + +def _on_null_touch_selector_entry_tree_activated(treeView, path, column, dialog, result): + dialog.response(gtk.RESPONSE_OK) + model = treeView.get_model() + itr = model.get_iter(path) + if itr is not None: + result[0] = model.get_value(itr, 0) + + +def _null_touch_selector_entry(parent, title, items, defaultItem): + parentSize = parent.get_size() + + model = gtk.ListStore(gobject.TYPE_STRING) + defaultIndex = -1 + for i, item in enumerate(items): + model.append((item, )) + if item == defaultItem: + defaultIndex = i + + cell = gtk.CellRendererText() + set_cell_thumb_selectable(cell) + column = gtk.TreeViewColumn(title) + column.pack_start(cell, expand=True) + column.add_attribute(cell, "text", 0) + + treeView = gtk.TreeView() + treeView.set_model(model) + treeView.append_column(column) + selection = treeView.get_selection() + selection.set_mode(gtk.SELECTION_SINGLE) + + scrolledWin = gtk.ScrolledWindow() + scrolledWin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledWin.add(treeView) + + customEntry = gtk.Entry() + + layout = gtk.VBox() + layout.pack_start(customEntry, expand=False) + layout.pack_start(scrolledWin) + + dialog = gtk.Dialog( + title, + parent, + gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL), + ) + dialog.set_default_response(gtk.RESPONSE_CANCEL) + dialog.get_child().add(layout) + dialog.resize(parentSize[0], max(parentSize[1]-100, 100)) + + scrolledWin = hildonize_scrollwindow(scrolledWin) + + result = [None] + if 0 < defaultIndex: + selection.select_path((defaultIndex, )) + result[0] = defaultItem + else: + customEntry.set_text(defaultItem) + + customEntry.connect("activate", _on_null_touch_selector_entry_entry_activated, dialog, result) + customEntry.connect("changed", _on_null_touch_selector_entry_entry_changed, result, selection, defaultIndex) + treeView.connect("row-activated", _on_null_touch_selector_entry_tree_activated, dialog, result) + + try: + dialog.show_all() + response = dialog.run() + + if response == gtk.RESPONSE_OK: + _, itr = selection.get_selected() + if itr is not None: + return model.get_value(itr, 0) + else: + enteredText = customEntry.get_text().strip() + if enteredText: + return enteredText + elif result[0] is not None: + return result[0] + else: + raise RuntimeError("No selection made") + elif response == gtk.RESPONSE_CANCEL or response == gtk.RESPONSE_DELETE_EVENT: + raise RuntimeError("User cancelled request") + else: + raise RuntimeError("Unrecognized response %r", response) + finally: + dialog.hide() + dialog.destroy() + + +try: + hildon.PickerDialog + hildon.TouchSelectorEntry + touch_selector_entry = _hildon_touch_selector_entry +except AttributeError: + touch_selector_entry = _null_touch_selector_entry + + +if __name__ == "__main__": + app = get_app_class()() + + label = gtk.Label("Hello World from a Label!") + + win = gtk.Window() + win.add(label) + win = hildonize_window(app, win) + if False and IS_FREMANTLE_SUPPORTED: + appMenu = hildon.AppMenu() + for i in xrange(5): + b = gtk.Button(str(i)) + appMenu.append(b) + win.set_app_menu(appMenu) + win.show_all() + appMenu.show_all() + gtk.main() + elif False: + print touch_selector(win, "Test", ["A", "B", "C", "D"], 2) + elif False: + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "C") + print touch_selector_entry(win, "Test", ["A", "B", "C", "D"], "Blah") + elif False: + import pprint + name, value = "", "" + goodLocals = [ + (name, value) for (name, value) in locals().iteritems() + if not name.startswith("_") + ] + pprint.pprint(goodLocals) + elif False: + import time + show_information_banner(win, "Hello World") + time.sleep(5) + elif False: + import time + banner = show_busy_banner_start(win, "Hello World") + time.sleep(5) + show_busy_banner_end(banner) diff --git a/src/opt/Nqa-Audiobook-player/nqaap.py b/src/opt/Nqa-Audiobook-player/nqaap.py new file mode 100644 index 0000000..ab32ba2 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/nqaap.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import os +import logging + +import constants +import nqaap_gtk + + +_moduleLogger = logging.getLogger(__name__) + + +try: + os.makedirs(constants._data_path_) +except OSError, e: + if e.errno != 17: + raise + +logging.basicConfig(level=logging.DEBUG, filename=constants._user_logpath_) +_moduleLogger.info("%s %s-%s" % (constants.__pretty_app_name__, constants.__version__, constants.__build__)) +_moduleLogger.info("OS: %s" % (os.uname()[0], )) +_moduleLogger.info("Kernel: %s (%s) for %s" % os.uname()[2:]) +_moduleLogger.info("Hostname: %s" % os.uname()[1]) + +try: + nqaap_gtk.run() +finally: + logging.shutdown() diff --git a/src/opt/Nqa-Audiobook-player/nqaap_gtk.py b/src/opt/Nqa-Audiobook-player/nqaap_gtk.py new file mode 100644 index 0000000..eb2ed8f --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/nqaap_gtk.py @@ -0,0 +1,32 @@ +#! /usr/bin/env python + +import logging + +import dbus +import dbus.mainloop.glib +import gobject +import gtk + +from Player import Player +from Gui import Gui + + +_moduleLogger = logging.getLogger(__name__) + + +def run(): + l = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) + gobject.threads_init() + gtk.gdk.threads_init() + + gui = Gui() + controller = Player(ui = gui) + gui.controller = controller + gui.load_settings() + + gtk.main() + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + run() diff --git a/src/opt/Nqa-Audiobook-player/settings.py b/src/opt/Nqa-Audiobook-player/settings.py new file mode 100644 index 0000000..afb50a9 --- /dev/null +++ b/src/opt/Nqa-Audiobook-player/settings.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +This file is part of Multilist. + +Multilist is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Multilist is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Multilist. If not, see . + +Copyright (C) 2008 Christoph Würstle +""" + +import logging + +import gtk + +import hildonize +import gtk_toolbox + +try: + _ +except NameError: + _ = lambda x: x + + +_moduleLogger = logging.getLogger(__name__) + + +class SettingsDialog(object): + + def __init__(self, parent): + self.window = None + + self.__isPortraitCheckbutton = gtk.CheckButton("Portrait Mode") + + self.__rotationSection = gtk.VBox() + self.__rotationSection.pack_start(self.__isPortraitCheckbutton, False, True) + + rotationFrame = gtk.Frame("Rotation") + rotationFrame.add(self.__rotationSection) + + self.__audioBooksPath = gtk.Entry() + self.__audioBooksPathButton = gtk.Button("Choose") + self.__audioBooksPathButton.connect("clicked", self._on_path_choose) + + self.__audiobookPathSection = gtk.HBox() + self.__audiobookPathSection.pack_start(self.__audioBooksPath, True, True) + self.__audiobookPathSection.pack_start(self.__audioBooksPathButton, False, True) + + self.__audiobookSection = gtk.VBox() + self.__audiobookSection.pack_start(self.__audiobookPathSection) + + audiobookFrame = gtk.Frame("Audiobooks") + audiobookFrame.add(self.__audiobookSection) + + settingsBox = gtk.VBox() + settingsBox.pack_start(rotationFrame, False, True) + settingsBox.pack_start(audiobookFrame, False, True) + settingsView = gtk.Viewport() + settingsView.add(settingsBox) + settingsScrollView = gtk.ScrolledWindow() + settingsScrollView.add(settingsView) + settingsScrollView.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + parent.pack_start(settingsScrollView, True, True) + + settingsScrollView = hildonize.hildonize_scrollwindow(settingsScrollView) + + def set_portrait_state(self, isPortrait): + self.__isPortraitCheckbutton.set_active(isPortrait) + + def is_portrait(self): + return self.__isPortraitCheckbutton.get_active() + + def set_audiobook_path(self, path): + self.__audioBooksPath.set_text(path) + + def get_audiobook_path(self): + return self.__audioBooksPath.get_text() + + @gtk_toolbox.log_exception(_moduleLogger) + def _on_path_choose(self, *args): + fileChooser = gtk.FileChooserDialog( + title="Audiobooks", + action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, + parent=self.window, + ) + fileChooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + fileChooser.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) + fileChooser.set_filename(self.__audioBooksPath.get_text()) + userResponse = fileChooser.run() + fileChooser.hide() + if userResponse == gtk.RESPONSE_OK: + filename = fileChooser.get_filename() + self.__audioBooksPath.set_text(filename) diff --git a/src/usr/share/applications/hildon/nqaap.desktop b/src/usr/share/applications/hildon/nqaap.desktop new file mode 100644 index 0000000..4054f62 --- /dev/null +++ b/src/usr/share/applications/hildon/nqaap.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0.0 +Encoding=UTF-8 +Name=nQaap +Exec=python /opt/Nqa-Audiobook-player/nqaap.py +Icon=nqaap +X-Icon-path=/usr/share/icons +X-Window-Icon=nqaap +Type=Application +X-Osso-Type=application/x-executable \ No newline at end of file diff --git a/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png b/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png new file mode 100644 index 0000000..9e38229 Binary files /dev/null and b/src/usr/share/icons/hicolor/48x48/hildon/nqaap.png differ diff --git a/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png b/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png new file mode 100644 index 0000000..8538783 Binary files /dev/null and b/src/usr/share/icons/hicolor/scalable/hildon/nqaap.png differ