1 from __future__ import with_statement
17 if hildonize.IS_FREMANTLE_SUPPORTED:
18 # I don't normally do this but I want to error as loudly as possibly when an issue arises
22 _moduleLogger = logging.getLogger(__name__)
28 _moduleLogger.info("Starting GUI")
29 self._clipboard = gtk.clipboard_get()
30 self._callMonitor = CallMonitor.CallMonitor()
31 self.__settingsWindow = None
32 self.__settingsManager = None
33 self._bookSelection = []
34 self._bookSelectionIndex = -1
35 self._chapterSelection = []
36 self._chapterSelectionIndex = -1
37 self._sleepSelection = ["0", "1", "10", "20", "30", "60"]
38 self._sleepSelectionIndex = 0
40 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
41 self.__isPortrait = False
43 self.controller = None
44 self.sleep_timer = None
45 self.auto_chapter_selected = False # true if we are in the
46 # midle of an automatic
49 self.ignore_next_chapter_change = False
52 self._callMonitor.connect("call_start", self.__on_call_started)
53 self._callMonitor.start()
56 self._app = hildonize.get_app_class()()
57 self.win = gtk.Window()
58 self.win = hildonize.hildonize_window(self._app, self.win)
59 self.win.set_title(constants.__pretty_app_name__)
62 self.cover = gtk.Image()
66 # Label that hold the title of the book,and maybe the chapter
67 self.title = gtk.Label()
68 self.title.set_justify(gtk.JUSTIFY_CENTER)
69 self._set_display_title("Select a book to start listening")
71 self.chapter = gtk.Label()
72 self.chapter.set_justify(gtk.JUSTIFY_CENTER)
75 if hildonize.IS_FREMANTLE_SUPPORTED:
76 self.seek = hildon.Seekbar()
77 self.seek.set_range(0.0, 100)
78 self.seek.set_draw_value(False)
79 self.seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
80 self.seek.connect('change-value', self.seek_changed) # event
81 # self.seek.connect('value-changed',self.seek_changed) # event
83 adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
84 self.seek = gtk.HScale(adjustment)
85 self.seek.set_draw_value(False)
86 self.seek.connect('change-value', self.seek_changed) # event
89 if hildonize.IS_FREMANTLE_SUPPORTED:
90 self.backButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
91 image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PREVIOUS, gtk.HILDON_SIZE_FINGER_HEIGHT)
92 self.backButton.set_image(image)
94 self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
96 self.forwardButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
97 image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_NEXT, gtk.HILDON_SIZE_FINGER_HEIGHT)
98 self.forwardButton.set_image(image)
100 self.backButton = gtk.Button(stock=gtk.STOCK_MEDIA_PREVIOUS)
101 self.button = gtk.Button()
102 self.forwardButton = gtk.Button(stock=gtk.STOCK_MEDIA_NEXT)
103 self.set_button_text("Play", "Start playing the audiobook")
104 self.backButton.connect('clicked', self._on_previous_chapter)
105 self.button.connect('clicked', self.play_pressed) # event
106 self.forwardButton.connect('clicked', self._on_next_chapter)
108 self._toolbar = gtk.HBox()
109 self._toolbar.pack_start(self.backButton, False, False, 0)
110 self._toolbar.pack_start(self.button, True, True, 0)
111 self._toolbar.pack_start(self.forwardButton, False, False, 0)
113 # Box to hold the controls:
114 self._controlLayout = gtk.VBox()
115 self._controlLayout.pack_start(gtk.Label(), True, True, 0)
116 self._controlLayout.pack_start(self.title, False, True, 0)
117 self._controlLayout.pack_start(self.chapter, False, True, 0)
118 self._controlLayout.pack_start(gtk.Label(), True, True, 0)
119 self._controlLayout.pack_start(self.seek, False, True, 0)
120 self._controlLayout.pack_start(self._toolbar, False, True, 0)
122 #Box that divides the layout in two: cover on the lefta
123 #and controls on the right
124 self._viewLayout = gtk.HBox()
125 self._viewLayout.pack_start(self.cover, True, True, 0)
126 self._viewLayout.add(self._controlLayout)
128 self._menuBar = gtk.MenuBar()
131 self._mainLayout = gtk.VBox()
132 self._mainLayout.pack_start(self._menuBar, False, False, 0)
133 self._mainLayout.pack_start(self._viewLayout)
135 # Add hbox to the window
136 self.win.add(self._mainLayout)
140 self._populate_menu()
142 self.win.connect("delete_event", self.quit) # Add shutdown event
143 self.win.connect("key-press-event", self.on_key_press)
144 self.win.connect("window-state-event", self._on_window_state_change)
151 def _populate_menu(self):
152 self._menuBar = hildonize.hildonize_menu(
156 if hildonize.IS_FREMANTLE_SUPPORTED:
157 # Create a picker button
158 self.book_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
159 hildon.BUTTON_ARRANGEMENT_VERTICAL)
160 self.book_button.set_title("Audiobook") # Set a title to the button
161 self.book_button.connect("clicked", self._on_select_audiobook)
163 # Create a picker button
164 self.chapter_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
165 hildon.BUTTON_ARRANGEMENT_VERTICAL)
166 self.chapter_button.set_title("Chapter") # Set a title to the button
167 self.chapter_button.connect("clicked", self._on_select_chapter)
169 # Create a picker button
170 self.sleeptime_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
171 hildon.BUTTON_ARRANGEMENT_VERTICAL)
172 self.sleeptime_button.set_title("Sleeptimer") # Set a title to the button
173 self.sleeptime_button.connect("clicked", self._on_select_sleep)
175 settings_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
176 settings_button.set_label("Settings")
177 settings_button.connect("clicked", self._on_settings)
179 about_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
180 about_button.set_label("About")
181 about_button.connect("clicked", self._on_about_activate)
183 help_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
184 help_button.set_label("Help")
185 help_button.connect("clicked", self.get_help)
187 self._menuBar.append(self.book_button) # Add the button to menu
188 self._menuBar.append(self.chapter_button) # Add the button to menu
189 self._menuBar.append(self.sleeptime_button) # Add the button to menu
190 self._menuBar.append(settings_button)
191 self._menuBar.append(help_button)
192 self._menuBar.append(about_button)
193 self._menuBar.show_all()
195 self._audiobookMenuItem = gtk.MenuItem("Audiobook: ")
196 self._audiobookMenuItem.connect("activate", self._on_select_audiobook)
198 self._chapterMenuItem = gtk.MenuItem("Chapter: ")
199 self._chapterMenuItem.connect("activate", self._on_select_chapter)
201 self._sleepMenuItem = gtk.MenuItem("Sleeptimer: 0")
202 self._sleepMenuItem.connect("activate", self._on_select_sleep)
204 settingsMenuItem = gtk.MenuItem("Settings")
205 settingsMenuItem.connect("activate", self._on_settings)
207 aboutMenuItem = gtk.MenuItem("About")
208 aboutMenuItem.connect("activate", self._on_about_activate)
210 helpMenuItem = gtk.MenuItem("Help")
211 helpMenuItem.connect("activate", self.get_help)
213 booksMenu = gtk.Menu()
214 booksMenu.append(self._audiobookMenuItem)
215 booksMenu.append(self._chapterMenuItem)
216 booksMenu.append(self._sleepMenuItem)
217 booksMenu.append(settingsMenuItem)
218 booksMenu.append(helpMenuItem)
219 booksMenu.append(aboutMenuItem)
221 booksMenuItem = gtk.MenuItem("Books")
223 booksMenuItem.set_submenu(booksMenu)
224 self._menuBar.append(booksMenuItem)
225 self._menuBar.show_all()
227 def setup_timers(self):
228 self.seek_timer = timeout_add_seconds(3, self.update_seek)
230 def save_settings(self):
231 config = ConfigParser.SafeConfigParser()
232 self._save_settings(config)
233 with open(constants._user_settings_, "wb") as configFile:
234 config.write(configFile)
236 def _save_settings(self, config):
237 config.add_section(constants.__pretty_app_name__)
238 config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
239 config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen))
240 config.set(constants.__pretty_app_name__, "audiopath", self.controller.get_books_path())
242 def load_settings(self):
243 config = ConfigParser.SafeConfigParser()
244 config.read(constants._user_settings_)
245 self._load_settings(config)
247 def _load_settings(self, config):
249 window_in_fullscreen = False
250 booksPath = constants._default_book_path_
252 isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
253 window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
254 booksPath = config.get(constants.__pretty_app_name__, "audiopath")
255 except ConfigParser.NoSectionError, e:
257 "Settings file %s is missing section %s" % (
258 constants._user_settings_,
263 if isPortrait ^ self.__isPortrait:
265 orientation = gtk.ORIENTATION_VERTICAL
267 orientation = gtk.ORIENTATION_HORIZONTAL
268 self.set_orientation(orientation)
270 self.__window_in_fullscreen = window_in_fullscreen
271 if self.__window_in_fullscreen:
272 self.win.fullscreen()
274 self.win.unfullscreen()
276 self.controller.load_books_path(booksPath)
279 def __format_name(path):
280 if os.path.isfile(path):
281 return os.path.basename(path).rsplit(".", 1)[0]
283 return os.path.basename(path)
285 @gtk_toolbox.log_exception(_moduleLogger)
286 def _on_select_audiobook(self, *args):
287 if not self._bookSelection:
289 index = hildonize.touch_selector(
292 (self.__format_name(bookPath) for bookPath in self._bookSelection),
293 self._bookSelectionIndex if 0 <= self._bookSelectionIndex else 0,
295 self._bookSelectionIndex = index
296 bookName = self._bookSelection[index]
297 self.controller.set_book(bookName)
299 @gtk_toolbox.log_exception(_moduleLogger)
300 def _on_select_chapter(self, *args):
301 if not self._chapterSelection:
303 index = hildonize.touch_selector(
306 (self.__format_name(chapterPath) for chapterPath in self._chapterSelection),
307 self._chapterSelectionIndex if 0 <= self._chapterSelectionIndex else 0,
309 self._chapterSelectionIndex = index
310 chapterName = self._chapterSelection[index]
311 self.controller.set_chapter(chapterName)
313 @gtk_toolbox.log_exception(_moduleLogger)
314 def _on_select_sleep(self, *args):
315 if self.sleep_timer is not None:
316 gobject.source_remove(self.sleep_timer)
319 index = hildonize.touch_selector(
322 self._sleepSelection,
323 self._sleepSelectionIndex if 0 <= self._sleepSelectionIndex else 0,
326 _moduleLogger.exception("Handling as if user cancelled")
327 hildonize.show_information_banner(self.win, "Sleep timer canceled")
330 self._sleepSelectionIndex = index
331 sleepName = self._sleepSelection[index]
333 time_out = int(sleepName)
335 timeout_add_seconds(time_out * 60, self.sleep)
337 if hildonize.IS_FREMANTLE_SUPPORTED:
338 self.sleeptime_button.set_text("Sleeptimer", sleepName)
340 self._sleepMenuItem.get_child().set_text("Sleeptimer: %s" % (sleepName, ))
342 @gtk_toolbox.log_exception(_moduleLogger)
343 def __on_call_started(self, callMonitor):
346 @gtk_toolbox.log_exception(_moduleLogger)
347 def _on_settings(self, *args):
348 if self.__settingsWindow is None:
350 self.__settingsManager = settings.SettingsDialog(vbox)
352 self.__settingsWindow = gtk.Window()
353 self.__settingsWindow.add(vbox)
354 self.__settingsWindow = hildonize.hildonize_window(self._app, self.__settingsWindow)
355 self.__settingsManager.window = self.__settingsWindow
357 self.__settingsWindow.set_title("Settings")
358 self.__settingsWindow.set_transient_for(self.win)
359 self.__settingsWindow.set_default_size(*self.win.get_size())
360 self.__settingsWindow.connect("delete-event", self._on_settings_delete)
361 self.__settingsManager.set_portrait_state(self.__isPortrait)
362 self.__settingsManager.set_audiobook_path(self.controller.get_books_path())
363 self.__settingsWindow.set_modal(True)
364 self.__settingsWindow.show_all()
366 @gtk_toolbox.log_exception(_moduleLogger)
367 def _on_settings_delete(self, *args):
368 self.__settingsWindow.emit_stop_by_name("delete-event")
369 self.__settingsWindow.hide()
370 self.__settingsWindow.set_modal(False)
372 isPortrait = self.__settingsManager.is_portrait()
373 if isPortrait ^ self.__isPortrait:
375 orientation = gtk.ORIENTATION_VERTICAL
377 orientation = gtk.ORIENTATION_HORIZONTAL
378 self.set_orientation(orientation)
379 if self.__settingsManager.get_audiobook_path() != self.controller.get_books_path():
380 self.controller.load_books_path(self.__settingsManager.get_audiobook_path())
384 @gtk_toolbox.log_exception(_moduleLogger)
385 def update_seek(self):
386 #print self.controller.get_percentage()
387 if self.controller.is_playing():
388 gtk.gdk.threads_enter()
389 self.seek.set_value(self.controller.get_percentage() * 100)
390 gtk.gdk.threads_leave()
391 #self.controller.get_percentage()
392 return True # run again
394 @gtk_toolbox.log_exception(_moduleLogger)
396 _moduleLogger.info("sleep time timeout")
397 hildonize.show_information_banner(self.win, "Sleep timer")
398 self.controller.stop()
399 self.set_button_text("Resume", "Resume playing the audiobook")
400 return False # do not repeat
402 @gtk_toolbox.log_exception(_moduleLogger)
403 def get_help(self, button):
404 Browser.open("file:///opt/Nqa-Audiobook-player/Help/nqaap.html")
406 @gtk_toolbox.log_exception(_moduleLogger)
407 def seek_changed(self, seek, scroll , value):
408 # print "sok", scroll
409 self.controller.seek_percent(seek.get_value() / 100.0)
411 @gtk_toolbox.log_exception(_moduleLogger)
412 def _on_next_chapter(self, *args):
413 self.controller.next_chapter()
415 @gtk_toolbox.log_exception(_moduleLogger)
416 def _on_previous_chapter(self, *args):
417 self.controller.previous_chapter()
419 @gtk_toolbox.log_exception(_moduleLogger)
420 def play_pressed(self, button):
421 if self.controller.is_playing():
426 @gtk_toolbox.log_exception(_moduleLogger)
427 def on_key_press(self, widget, event, *args):
428 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
429 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
431 event.keyval == gtk.keysyms.F6 or
432 event.keyval in RETURN_TYPES and isCtrl
434 # The "Full screen" hardware key has been pressed
435 if self.__window_in_fullscreen:
436 self.win.unfullscreen ()
438 self.win.fullscreen ()
440 elif event.keyval == gtk.keysyms.o and isCtrl:
441 self._toggle_rotate()
444 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
445 event.get_state() & gtk.gdk.CONTROL_MASK
448 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
449 with open(constants._user_logpath_, "r") as f:
450 logLines = f.xreadlines()
451 log = "".join(logLines)
452 self._clipboard.set_text(str(log))
454 elif event.keyval in RETURN_TYPES:
455 if self.controller.is_playing():
460 elif event.keyval == gtk.keysyms.Left:
461 self.controller.previous_chapter()
463 elif event.keyval == gtk.keysyms.Right:
464 self.controller.next_chapter()
467 @gtk_toolbox.log_exception(_moduleLogger)
468 def _on_window_state_change(self, widget, event, *args):
469 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
470 self.__window_in_fullscreen = True
472 self.__window_in_fullscreen = False
474 @gtk_toolbox.log_exception(_moduleLogger)
475 def quit(self, *args): # what are the arguments?
476 _moduleLogger.info("Shutting down")
479 self.controller.stop() # to save the state
483 @gtk_toolbox.log_exception(_moduleLogger)
484 def _on_about_activate(self, *args):
485 dlg = gtk.AboutDialog()
486 dlg.set_name(constants.__pretty_app_name__)
487 dlg.set_version("%s-%d" % (constants.__version__, constants.__build__))
488 dlg.set_copyright("Copyright 2010")
490 dlg.set_website("http://nqaap.garage.maemo.org/")
491 dlg.set_authors(["Pengman <pengmeister@gmail.com>", "Ed Page <eopage@byu.net>"])
498 self.set_button_text("Stop", "Stop playing the audiobook")
499 self.controller.play()
502 self.set_button_text("Resume", "Resume playing the audiobook")
503 self.controller.stop()
505 def set_orientation(self, orientation):
506 if orientation == gtk.ORIENTATION_VERTICAL:
507 if self.__isPortrait:
509 hildonize.window_to_portrait(self.win)
510 self.__isPortrait = True
512 self._viewLayout.remove(self._controlLayout)
513 self._mainLayout.add(self._controlLayout)
514 elif orientation == gtk.ORIENTATION_HORIZONTAL:
515 if not self.__isPortrait:
517 hildonize.window_to_landscape(self.win)
518 self.__isPortrait = False
520 self._mainLayout.remove(self._controlLayout)
521 self._viewLayout.add(self._controlLayout)
523 raise NotImplementedError(orientation)
525 def get_orientation(self):
526 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
528 def _toggle_rotate(self):
529 if self.__isPortrait:
530 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
532 self.set_orientation(gtk.ORIENTATION_VERTICAL)
534 def change_chapter(self, chapterName):
535 if chapterName is None:
536 _moduleLogger.debug("chapter selection canceled.")
537 #import pdb; pdb.set_trace() # start debugger
538 self.ignore_next_chapter_change = True
539 return True # this should end the function and indicate it has been handled
541 if self.ignore_next_chapter_change:
542 self.ignore_next_chapter_change = False
543 _moduleLogger.debug("followup chapter selection canceled.")
544 #import pdb; pdb.set_trace() # start debugger
545 return True # this should end the function and indicate it has been handled
547 if self.auto_chapter_selected:
548 _moduleLogger.debug("chapter changed (by controller) to: %s" % chapterName)
549 self.auto_chapter_selected = False
552 _moduleLogger.debug("chapter selection sendt to controller: %s" % chapterName)
553 self.controller.set_chapter(chapterName) # signal controller
554 self.set_button_text("Play", "Start playing the audiobook") # reset button
556 def set_button_text(self, title, text):
557 if hildonize.IS_FREMANTLE_SUPPORTED:
558 self.button.set_text(title, text)
560 self.button.set_label("%s" % (title, ))
562 def set_books(self, books):
563 _moduleLogger.debug("new books")
564 del self._bookSelection[:]
565 self._bookSelection.extend(books)
566 if len(books) == 0 and self.controller is not None:
567 hildonize.show_information_banner(self.win, "No audiobooks found. \nPlease place your audiobooks in the directory %s" % self.controller.get_books_path())
569 def set_book(self, bookPath, cover):
570 bookName = self.__format_name(bookPath)
572 self.set_button_text("Play", "Start playing the audiobook") # reset button
573 self._set_display_title(bookName)
574 if hildonize.IS_FREMANTLE_SUPPORTED:
575 self.book_button.set_text("Audiobook", bookName)
577 self._audiobookMenuItem.get_child().set_text("Audiobook: %s" % (bookName, ))
579 self.cover.set_from_file(cover)
581 def set_chapter(self, chapterIndex):
583 Called from controller whenever a new chapter is started
585 chapter parameter is supposed to be the index for the chapter, not the name
587 self.auto_chapter_selected = True
588 self._set_display_chapter(str(chapterIndex + 1))
589 if hildonize.IS_FREMANTLE_SUPPORTED:
590 self.chapter_button.set_text("Chapter", str(chapterIndex))
592 self._chapterMenuItem.get_child().set_text("Chapter: %s" % (chapterIndex, ))
594 def set_chapters(self, chapters):
595 _moduleLogger.debug("setting chapters" )
596 del self._chapterSelection[:]
597 self._chapterSelection.extend(chapters)
599 def set_sleep_timer(self, mins):
603 def set_selected_value(self, button, value):
604 i = button.get_selector().get_model(0).index[value] # get index of value from list
605 button.set_active(i) # set active index to that index
607 def _set_display_title(self, title):
608 self.title.set_markup("<b><big>%s</big></b>" % title)
610 def _set_display_chapter(self, chapter):
611 self.chapter.set_markup("<b><big>Chapter %s</big></b>" % chapter)
614 def _old_timeout_add_seconds(timeout, callback):
615 return gobject.timeout_add(timeout * 1000, callback)
618 def _timeout_add_seconds(timeout, callback):
619 return gobject.timeout_add_seconds(timeout, callback)
623 gobject.timeout_add_seconds
624 timeout_add_seconds = _timeout_add_seconds
625 except AttributeError:
626 timeout_add_seconds = _old_timeout_add_seconds
629 if __name__ == "__main__":