1 from __future__ import with_statement
18 if hildonize.IS_FREMANTLE_SUPPORTED:
19 # I don't normally do this but I want to error as loudly as possibly when an issue arises
23 _moduleLogger = logging.getLogger(__name__)
28 # @todo Jump straight to book selection on first launch?
29 # @todo Absolute increments (+/-5 seconds)
30 # @todo Show elapsed time / time lef
31 # @todo Volume control when screen is off
32 # @todo Variable speed
33 # http://scaletempo.sourceforge.net/0/task-list.html
34 # http://www.gstreamer.net/data/doc/gstreamer/head/gst-plugins-bad-plugins/html/gst-plugins-bad-plugins-scaletempo.html
37 _moduleLogger.info("Starting GUI")
38 self._clipboard = gtk.clipboard_get()
39 self._callMonitor = CallMonitor.CallMonitor()
40 self.__settingsWindow = None
41 self.__settingsManager = None
42 self._bookSelection = []
43 self._bookSelectionIndex = -1
44 self._chapterSelection = []
45 self._chapterSelectionIndex = -1
46 self._sleepSelection = ["0", "1", "10", "20", "30", "60"]
47 self._sleepSelectionIndex = 0
49 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
50 self.__isPortrait = False
52 self.controller = None
53 self.sleep_timer = None
57 self._callMonitor.connect("call_start", self.__on_call_started)
58 self._callMonitor.start()
61 self._app = hildonize.get_app_class()()
62 self.win = gtk.Window()
63 self.win = hildonize.hildonize_window(self._app, self.win)
64 self.win.set_title(constants.__pretty_app_name__)
67 self.cover = gtk.Image()
71 # Label that hold the title of the book,and maybe the chapter
72 self.title = gtk.Label()
73 self.title.set_justify(gtk.JUSTIFY_CENTER)
74 self._set_display_title("Select a book to start listening")
76 self.chapter = gtk.Label()
77 self.chapter.set_justify(gtk.JUSTIFY_CENTER)
80 if hildonize.IS_FREMANTLE_SUPPORTED:
81 self.seek = hildon.Seekbar()
82 self.seek.set_range(0.0, 100)
83 self.seek.set_draw_value(False)
84 self.seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
85 self.seek.connect('change-value', self.seek_changed) # event
86 # self.seek.connect('value-changed',self.seek_changed) # event
88 adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
89 self.seek = gtk.HScale(adjustment)
90 self.seek.set_draw_value(False)
91 self.seek.connect('change-value', self.seek_changed) # event
94 if hildonize.IS_FREMANTLE_SUPPORTED:
95 self.backButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
96 image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PREVIOUS, gtk.HILDON_SIZE_FINGER_HEIGHT)
97 self.backButton.set_image(image)
99 self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
101 self.forwardButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
102 image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_NEXT, gtk.HILDON_SIZE_FINGER_HEIGHT)
103 self.forwardButton.set_image(image)
105 self.backButton = gtk.Button(stock=gtk.STOCK_MEDIA_PREVIOUS)
106 self.button = gtk.Button()
107 self.forwardButton = gtk.Button(stock=gtk.STOCK_MEDIA_NEXT)
108 self.set_button_text("Play", "Start playing the audiobook")
109 self.backButton.connect('clicked', self._on_previous_chapter)
110 self.button.connect('clicked', self.play_pressed) # event
111 self.forwardButton.connect('clicked', self._on_next_chapter)
113 self._toolbar = gtk.HBox()
114 self._toolbar.pack_start(self.backButton, False, False, 0)
115 self._toolbar.pack_start(self.button, True, True, 0)
116 self._toolbar.pack_start(self.forwardButton, False, False, 0)
118 # Box to hold the controls:
119 self._controlLayout = gtk.VBox()
120 self._controlLayout.pack_start(gtk.Label(), True, True, 0)
121 self._controlLayout.pack_start(self.title, False, True, 0)
122 self._controlLayout.pack_start(self.chapter, False, True, 0)
123 self._controlLayout.pack_start(gtk.Label(), True, True, 0)
124 self._controlLayout.pack_start(self.seek, False, True, 0)
125 self._controlLayout.pack_start(self._toolbar, False, True, 0)
127 #Box that divides the layout in two: cover on the lefta
128 #and controls on the right
129 self._viewLayout = gtk.HBox()
130 self._viewLayout.pack_start(self.cover, True, True, 0)
131 self._viewLayout.add(self._controlLayout)
133 self._menuBar = gtk.MenuBar()
136 self._mainLayout = gtk.VBox()
137 self._mainLayout.pack_start(self._menuBar, False, False, 0)
138 self._mainLayout.pack_start(self._viewLayout)
140 # Add hbox to the window
141 self.win.add(self._mainLayout)
145 self._populate_menu()
147 self.win.connect("delete_event", self.quit) # Add shutdown event
148 self.win.connect("key-press-event", self.on_key_press)
149 self.win.connect("window-state-event", self._on_window_state_change)
156 def _populate_menu(self):
157 self._menuBar = hildonize.hildonize_menu(
161 if hildonize.IS_FREMANTLE_SUPPORTED:
162 # Create a picker button
163 self.book_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
164 hildon.BUTTON_ARRANGEMENT_VERTICAL)
165 self.book_button.set_title("Audiobook") # Set a title to the button
166 self.book_button.connect("clicked", self._on_select_audiobook)
168 # Create a picker button
169 self.chapter_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
170 hildon.BUTTON_ARRANGEMENT_VERTICAL)
171 self.chapter_button.set_title("Chapter") # Set a title to the button
172 self.chapter_button.connect("clicked", self._on_select_chapter)
174 # Create a picker button
175 self.sleeptime_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
176 hildon.BUTTON_ARRANGEMENT_VERTICAL)
177 self.sleeptime_button.set_title("Sleeptimer") # Set a title to the button
178 self.sleeptime_button.connect("clicked", self._on_select_sleep)
180 settings_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
181 settings_button.set_label("Settings")
182 settings_button.connect("clicked", self._on_settings)
184 about_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
185 about_button.set_label("About")
186 about_button.connect("clicked", self._on_about_activate)
188 help_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
189 help_button.set_label("Help")
190 help_button.connect("clicked", self.get_help)
192 self._menuBar.append(self.book_button) # Add the button to menu
193 self._menuBar.append(self.chapter_button) # Add the button to menu
194 self._menuBar.append(self.sleeptime_button) # Add the button to menu
195 self._menuBar.append(settings_button)
196 self._menuBar.append(help_button)
197 self._menuBar.append(about_button)
198 self._menuBar.show_all()
200 self._audiobookMenuItem = gtk.MenuItem("Audiobook: ")
201 self._audiobookMenuItem.connect("activate", self._on_select_audiobook)
203 self._chapterMenuItem = gtk.MenuItem("Chapter: ")
204 self._chapterMenuItem.connect("activate", self._on_select_chapter)
206 self._sleepMenuItem = gtk.MenuItem("Sleeptimer: 0")
207 self._sleepMenuItem.connect("activate", self._on_select_sleep)
209 settingsMenuItem = gtk.MenuItem("Settings")
210 settingsMenuItem.connect("activate", self._on_settings)
212 aboutMenuItem = gtk.MenuItem("About")
213 aboutMenuItem.connect("activate", self._on_about_activate)
215 helpMenuItem = gtk.MenuItem("Help")
216 helpMenuItem.connect("activate", self.get_help)
218 booksMenu = gtk.Menu()
219 booksMenu.append(self._audiobookMenuItem)
220 booksMenu.append(self._chapterMenuItem)
221 booksMenu.append(self._sleepMenuItem)
222 booksMenu.append(settingsMenuItem)
223 booksMenu.append(helpMenuItem)
224 booksMenu.append(aboutMenuItem)
226 booksMenuItem = gtk.MenuItem("Books")
228 booksMenuItem.set_submenu(booksMenu)
229 self._menuBar.append(booksMenuItem)
230 self._menuBar.show_all()
232 def setup_timers(self):
233 self.seek_timer = timeout_add_seconds(3, self.update_seek)
235 def save_settings(self):
236 config = ConfigParser.SafeConfigParser()
237 self._save_settings(config)
238 with open(constants._user_settings_, "wb") as configFile:
239 config.write(configFile)
240 self.controller.save()
242 def _save_settings(self, config):
243 config.add_section(constants.__pretty_app_name__)
244 config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
245 config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen))
246 config.set(constants.__pretty_app_name__, "audiopath", self.controller.get_books_path())
248 def load_settings(self):
249 config = ConfigParser.SafeConfigParser()
250 config.read(constants._user_settings_)
251 self._load_settings(config)
253 def _load_settings(self, config):
255 window_in_fullscreen = False
256 booksPath = constants._default_book_path_
258 isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
259 window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
260 booksPath = config.get(constants.__pretty_app_name__, "audiopath")
261 except ConfigParser.NoSectionError, e:
263 "Settings file %s is missing section %s" % (
264 constants._user_settings_,
269 if isPortrait ^ self.__isPortrait:
271 orientation = gtk.ORIENTATION_VERTICAL
273 orientation = gtk.ORIENTATION_HORIZONTAL
274 self.set_orientation(orientation)
276 self.__window_in_fullscreen = window_in_fullscreen
277 if self.__window_in_fullscreen:
278 self.win.fullscreen()
280 self.win.unfullscreen()
282 self.controller.load(booksPath)
285 def __format_name(path):
286 if os.path.isfile(path):
287 return os.path.basename(path).rsplit(".", 1)[0]
289 return os.path.basename(path)
291 @gtk_toolbox.log_exception(_moduleLogger)
292 def _on_select_audiobook(self, *args):
293 if not self._bookSelection:
295 index = hildonize.touch_selector(
298 (self.__format_name(bookPath) for bookPath in self._bookSelection),
299 self._bookSelectionIndex if 0 <= self._bookSelectionIndex else 0,
301 self._bookSelectionIndex = index
302 bookName = self._bookSelection[index]
303 self.controller.set_book(bookName)
304 self.set_button_text("Play", "Start playing the audiobook") # reset button
306 @gtk_toolbox.log_exception(_moduleLogger)
307 def _on_select_chapter(self, *args):
308 if not self._chapterSelection:
310 index = hildonize.touch_selector(
313 (self.__format_name(chapterPath) for chapterPath in self._chapterSelection),
314 self._chapterSelectionIndex if 0 <= self._chapterSelectionIndex else 0,
316 self._chapterSelectionIndex = index
317 chapterName = self._chapterSelection[index]
318 self.controller.set_chapter(chapterName)
319 self.set_button_text("Play", "Start playing the audiobook") # reset button
321 @gtk_toolbox.log_exception(_moduleLogger)
322 def _on_select_sleep(self, *args):
323 if self.sleep_timer is not None:
324 gobject.source_remove(self.sleep_timer)
327 index = hildonize.touch_selector(
330 self._sleepSelection,
331 self._sleepSelectionIndex if 0 <= self._sleepSelectionIndex else 0,
334 _moduleLogger.exception("Handling as if user cancelled")
335 hildonize.show_information_banner(self.win, "Sleep timer canceled")
338 self._sleepSelectionIndex = index
339 sleepName = self._sleepSelection[index]
341 time_out = int(sleepName)
343 timeout_add_seconds(time_out * 60, self.sleep)
345 if hildonize.IS_FREMANTLE_SUPPORTED:
346 self.sleeptime_button.set_text("Sleeptimer", sleepName)
348 self._sleepMenuItem.get_child().set_text("Sleeptimer: %s" % (sleepName, ))
350 @gtk_toolbox.log_exception(_moduleLogger)
351 def __on_call_started(self, callMonitor):
354 @gtk_toolbox.log_exception(_moduleLogger)
355 def _on_settings(self, *args):
356 if self.__settingsWindow is None:
358 self.__settingsManager = settings.SettingsDialog(vbox)
360 self.__settingsWindow = gtk.Window()
361 self.__settingsWindow.add(vbox)
362 self.__settingsWindow = hildonize.hildonize_window(self._app, self.__settingsWindow)
363 self.__settingsManager.window = self.__settingsWindow
365 self.__settingsWindow.set_title("Settings")
366 self.__settingsWindow.set_transient_for(self.win)
367 self.__settingsWindow.set_default_size(*self.win.get_size())
368 self.__settingsWindow.connect("delete-event", self._on_settings_delete)
369 self.__settingsManager.set_portrait_state(self.__isPortrait)
370 self.__settingsManager.set_audiobook_path(self.controller.get_books_path())
371 self.__settingsWindow.set_modal(True)
372 self.__settingsWindow.show_all()
374 @gtk_toolbox.log_exception(_moduleLogger)
375 def _on_settings_delete(self, *args):
376 self.__settingsWindow.emit_stop_by_name("delete-event")
377 self.__settingsWindow.hide()
378 self.__settingsWindow.set_modal(False)
380 isPortrait = self.__settingsManager.is_portrait()
381 if isPortrait ^ self.__isPortrait:
383 orientation = gtk.ORIENTATION_VERTICAL
385 orientation = gtk.ORIENTATION_HORIZONTAL
386 self.set_orientation(orientation)
387 if self.__settingsManager.get_audiobook_path() != self.controller.get_books_path():
388 self.controller.reload(self.__settingsManager.get_audiobook_path())
392 @gtk_toolbox.log_exception(_moduleLogger)
393 def update_seek(self):
394 if self.controller.is_playing():
395 gtk.gdk.threads_enter()
396 self.seek.set_value(self.controller.get_percentage() * 100)
397 gtk.gdk.threads_leave()
398 #self.controller.get_percentage()
399 return True # run again
401 @gtk_toolbox.log_exception(_moduleLogger)
403 _moduleLogger.info("sleep time timeout")
404 hildonize.show_information_banner(self.win, "Sleep timer")
405 self.controller.stop()
406 self.set_button_text("Resume", "Resume playing the audiobook")
407 return False # do not repeat
409 @gtk_toolbox.log_exception(_moduleLogger)
410 def get_help(self, button):
411 Browser.open("file:///opt/Nqa-Audiobook-player/Help/nqaap.html")
413 @gtk_toolbox.log_exception(_moduleLogger)
414 def seek_changed(self, seek, scroll , value):
415 self.controller.seek_percent(seek.get_value() / 100.0)
417 @gtk_toolbox.log_exception(_moduleLogger)
418 def _on_next_chapter(self, *args):
419 self.controller.next_chapter()
421 @gtk_toolbox.log_exception(_moduleLogger)
422 def _on_previous_chapter(self, *args):
423 self.controller.previous_chapter()
425 @gtk_toolbox.log_exception(_moduleLogger)
426 def play_pressed(self, button):
427 if self.controller.is_playing():
432 @gtk_toolbox.log_exception(_moduleLogger)
433 def on_key_press(self, widget, event, *args):
434 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
435 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
437 event.keyval == gtk.keysyms.F6 or
438 event.keyval in RETURN_TYPES and isCtrl
440 # The "Full screen" hardware key has been pressed
441 if self.__window_in_fullscreen:
442 self.win.unfullscreen ()
444 self.win.fullscreen ()
446 elif event.keyval == gtk.keysyms.o and isCtrl:
447 self._toggle_rotate()
450 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
451 event.get_state() & gtk.gdk.CONTROL_MASK
454 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
455 with codecs.open(constants._user_logpath_, "r", "utf-8") as f:
456 logLines = f.xreadlines()
457 log = u"".join(logLines)
458 self._clipboard.set_text(log)
460 elif event.keyval in RETURN_TYPES:
461 if self.controller.is_playing():
466 elif event.keyval == gtk.keysyms.Left:
467 self.controller.previous_chapter()
469 elif event.keyval == gtk.keysyms.Right:
470 self.controller.next_chapter()
473 @gtk_toolbox.log_exception(_moduleLogger)
474 def _on_window_state_change(self, widget, event, *args):
475 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
476 self.__window_in_fullscreen = True
478 self.__window_in_fullscreen = False
480 @gtk_toolbox.log_exception(_moduleLogger)
481 def quit(self, *args): # what are the arguments?
482 _moduleLogger.info("Shutting down")
485 self.controller.stop() # to save the state
489 @gtk_toolbox.log_exception(_moduleLogger)
490 def _on_about_activate(self, *args):
491 dlg = gtk.AboutDialog()
492 dlg.set_name(constants.__pretty_app_name__)
493 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
494 dlg.set_copyright("Copyright 2010")
496 dlg.set_website("http://nqaap.garage.maemo.org/")
497 dlg.set_authors(["Pengman <pengmeister@gmail.com>", "Ed Page <eopage@byu.net>"])
504 self.set_button_text("Stop", "Stop playing the audiobook")
505 self.controller.play()
508 self.set_button_text("Resume", "Resume playing the audiobook")
509 self.controller.stop()
511 def set_orientation(self, orientation):
512 if orientation == gtk.ORIENTATION_VERTICAL:
513 if self.__isPortrait:
515 hildonize.window_to_portrait(self.win)
516 self.__isPortrait = True
518 self._viewLayout.remove(self._controlLayout)
519 self._mainLayout.add(self._controlLayout)
520 elif orientation == gtk.ORIENTATION_HORIZONTAL:
521 if not self.__isPortrait:
523 hildonize.window_to_landscape(self.win)
524 self.__isPortrait = False
526 self._mainLayout.remove(self._controlLayout)
527 self._viewLayout.add(self._controlLayout)
529 raise NotImplementedError(orientation)
531 def get_orientation(self):
532 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
534 def _toggle_rotate(self):
535 if self.__isPortrait:
536 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
538 self.set_orientation(gtk.ORIENTATION_VERTICAL)
540 def change_chapter(self, chapterName):
541 if chapterName is None:
542 _moduleLogger.debug("chapter selection canceled.")
543 return True # this should end the function and indicate it has been handled
545 _moduleLogger.debug("chapter changed (by controller) to: %s" % chapterName)
547 def set_button_text(self, title, text):
548 if hildonize.IS_FREMANTLE_SUPPORTED:
549 self.button.set_text(title, text)
551 self.button.set_label("%s" % (title, ))
553 def set_books(self, books):
554 _moduleLogger.debug("new books")
555 del self._bookSelection[:]
556 self._bookSelection.extend(books)
557 if len(books) == 0 and self.controller is not None:
558 hildonize.show_information_banner(self.win, "No audiobooks found. \nPlease place your audiobooks in the directory %s" % self.controller.get_books_path())
560 def set_book(self, bookPath, cover):
561 bookName = self.__format_name(bookPath)
563 self.set_button_text("Play", "Start playing the audiobook") # reset button
564 self._set_display_title(bookName)
565 if hildonize.IS_FREMANTLE_SUPPORTED:
566 self.book_button.set_text("Audiobook", bookName)
568 self._audiobookMenuItem.get_child().set_text("Audiobook: %s" % (bookName, ))
570 self.cover.set_from_file(cover)
572 def set_chapter(self, chapterIndex):
574 Called from controller whenever a new chapter is started
576 chapter parameter is supposed to be the index for the chapter, not the name
578 self.auto_chapter_selected = True
579 self._set_display_chapter(str(chapterIndex + 1))
580 if hildonize.IS_FREMANTLE_SUPPORTED:
581 self.chapter_button.set_text("Chapter", str(chapterIndex))
583 self._chapterMenuItem.get_child().set_text("Chapter: %s" % (chapterIndex+1, ))
585 def set_chapters(self, chapters):
586 _moduleLogger.debug("setting chapters" )
587 del self._chapterSelection[:]
588 self._chapterSelection.extend(chapters)
590 def set_sleep_timer(self, mins):
594 def set_selected_value(self, button, value):
595 i = button.get_selector().get_model(0).index[value] # get index of value from list
596 button.set_active(i) # set active index to that index
598 def _set_display_title(self, title):
599 self.title.set_markup("<b><big>%s</big></b>" % title)
601 def _set_display_chapter(self, chapter):
602 self.chapter.set_markup("<b><big>Chapter %s</big></b>" % chapter)
605 def _old_timeout_add_seconds(timeout, callback):
606 return gobject.timeout_add(timeout * 1000, callback)
609 def _timeout_add_seconds(timeout, callback):
610 return gobject.timeout_add_seconds(timeout, callback)
614 gobject.timeout_add_seconds
615 timeout_add_seconds = _timeout_add_seconds
616 except AttributeError:
617 timeout_add_seconds = _old_timeout_add_seconds
620 if __name__ == "__main__":