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._callMonitor = CallMonitor.CallMonitor()
30 self.__settingsWindow = None
31 self.__settingsManager = None
32 self._bookSelection = []
33 self._bookSelectionIndex = -1
34 self._chapterSelection = []
35 self._chapterSelectionIndex = -1
36 self._sleepSelection = ["0", "1", "10", "20", "30", "60"]
37 self._sleepSelectionIndex = 0
39 self.__window_in_fullscreen = False #The window isn't in full screen mode initially.
40 self.__isPortrait = False
42 self.controller = None
43 self.sleep_timer = None
44 self.auto_chapter_selected = False # true if we are in the
45 # midle of an automatic
48 self.ignore_next_chapter_change = False
51 self._callMonitor.connect("call_start", self.__on_call_started)
52 self._callMonitor.start()
55 if hildonize.IS_FREMANTLE_SUPPORTED:
56 gtk.set_application_name(constants.__pretty_app_name__) # window title
57 self._app = hildonize.get_app_class()()
58 self.win = gtk.Window()
59 self.win = hildonize.hildonize_window(self._app, self.win)
62 self.cover = gtk.Image()
66 # Label that hold the title of the book,and maybe the chapter
67 self.title = gtk.Label("Select a book to start listening")
68 self.title.set_justify(gtk.JUSTIFY_CENTER)
71 if hildonize.IS_FREMANTLE_SUPPORTED:
72 self.seek = hildon.Seekbar()
73 self.seek.set_range(0.0, 100)
74 self.seek.set_draw_value(False)
75 self.seek.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
76 self.seek.connect('change-value', self.seek_changed) # event
77 # self.seek.connect('value-changed',self.seek_changed) # event
79 adjustment = gtk.Adjustment(0, 0, 101, 1, 5, 1)
80 self.seek = gtk.HScale(adjustment)
81 self.seek.connect('change-value', self.seek_changed) # event
84 if hildonize.IS_FREMANTLE_SUPPORTED:
85 self.backButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
86 image = gtk.image_new_from_stock(gtk.STOCK_GO_BACK, gtk.HILDON_SIZE_FINGER_HEIGHT)
87 self.backButton.set_image(image)
89 self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
91 self.forwardButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
92 image = gtk.image_new_from_stock(gtk.STOCK_GO_FORWARD, gtk.HILDON_SIZE_FINGER_HEIGHT)
93 self.forwardButton.set_image(image)
95 self.backButton = gtk.Button(stock=gtk.STOCK_GO_BACK)
96 self.button = gtk.Button()
97 self.forwardButton = gtk.Button(stock=gtk.STOCK_GO_FORWARD)
98 self.set_button_text("Play", "Start playing the audiobook")
99 self.backButton.connect('clicked', self._on_previous_chapter)
100 self.button.connect('clicked', self.play_pressed) # event
101 self.forwardButton.connect('clicked', self._on_next_chapter)
103 self._toolbar = gtk.HBox()
104 self._toolbar.pack_start(self.backButton, False, False, 0)
105 self._toolbar.pack_start(self.button, True, True, 0)
106 self._toolbar.pack_start(self.forwardButton, False, False, 0)
108 # Box to hold the controls:
109 self._controlLayout = gtk.VBox()
110 self._controlLayout.pack_start(self.title, True, True, 0)
111 self._controlLayout.pack_start(self.seek, True, True, 0)
112 self._controlLayout.pack_start(self._toolbar, False, True, 0)
114 #Box that divides the layout in two: cover on the lefta
115 #and controls on the right
116 self._viewLayout = gtk.HBox()
117 self._viewLayout.pack_start(self.cover, True, True, 0)
118 self._viewLayout.add(self._controlLayout)
120 self._menuBar = gtk.MenuBar()
123 self._mainLayout = gtk.VBox()
124 self._mainLayout.pack_start(self._menuBar, False, False, 0)
125 self._mainLayout.pack_start(self._viewLayout)
127 # Add hbox to the window
128 self.win.add(self._mainLayout)
132 self._populate_menu()
134 self.win.connect("delete_event", self.quit) # Add shutdown event
135 self.win.connect("key-press-event", self.on_key_press)
136 self.win.connect("window-state-event", self._on_window_state_change)
143 def _populate_menu(self):
144 self._menuBar = hildonize.hildonize_menu(
148 if hildonize.IS_FREMANTLE_SUPPORTED:
149 # Create a picker button
150 self.book_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
151 hildon.BUTTON_ARRANGEMENT_VERTICAL)
152 self.book_button.set_title("Audiobook") # Set a title to the button
153 self.book_button.connect("clicked", self._on_select_audiobook)
155 # Create a picker button
156 self.chapter_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
157 hildon.BUTTON_ARRANGEMENT_VERTICAL)
158 self.chapter_button.set_title("Chapter") # Set a title to the button
159 self.chapter_button.connect("clicked", self._on_select_chapter)
161 # Create a picker button
162 self.sleeptime_button = hildon.Button(gtk.HILDON_SIZE_AUTO,
163 hildon.BUTTON_ARRANGEMENT_VERTICAL)
164 self.sleeptime_button.set_title("Sleeptimer") # Set a title to the button
165 self.sleeptime_button.connect("clicked", self._on_select_sleep)
167 settings_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
168 settings_button.set_label("Settings")
169 settings_button.connect("clicked", self._on_settings)
171 help_button = hildon.Button(gtk.HILDON_SIZE_AUTO, hildon.BUTTON_ARRANGEMENT_VERTICAL)
172 help_button.set_label("Help")
173 help_button.connect("clicked", self.get_help)
175 self._menuBar.append(self.book_button) # Add the button to menu
176 self._menuBar.append(self.chapter_button) # Add the button to menu
177 self._menuBar.append(self.sleeptime_button) # Add the button to menu
178 self._menuBar.append(settings_button)
179 self._menuBar.append(help_button)
180 self._menuBar.show_all()
182 self._audiobookMenuItem = gtk.MenuItem("Audiobook: ")
183 self._audiobookMenuItem.connect("activate", self._on_select_audiobook)
185 self._chapterMenuItem = gtk.MenuItem("Chapter: ")
186 self._chapterMenuItem.connect("activate", self._on_select_chapter)
188 self._sleepMenuItem = gtk.MenuItem("Sleeptimer: 0")
189 self._sleepMenuItem.connect("activate", self._on_select_sleep)
191 settingsMenuItem = gtk.MenuItem("Settings")
192 settingsMenuItem.connect("activate", self._on_settings)
194 helpMenuItem = gtk.MenuItem("Help")
195 helpMenuItem.connect("activate", self.get_help)
197 booksMenu = gtk.Menu()
198 booksMenu.append(self._audiobookMenuItem)
199 booksMenu.append(self._chapterMenuItem)
200 booksMenu.append(self._sleepMenuItem)
201 booksMenu.append(settingsMenuItem)
202 booksMenu.append(helpMenuItem)
204 booksMenuItem = gtk.MenuItem("Books")
206 booksMenuItem.set_submenu(booksMenu)
207 self._menuBar.append(booksMenuItem)
208 self._menuBar.show_all()
210 def setup_timers(self):
211 self.seek_timer = timeout_add_seconds(3, self.update_seek)
213 def save_settings(self):
214 config = ConfigParser.SafeConfigParser()
215 self._save_settings(config)
216 with open(constants._user_settings_, "wb") as configFile:
217 config.write(configFile)
219 def _save_settings(self, config):
220 config.add_section(constants.__pretty_app_name__)
221 config.set(constants.__pretty_app_name__, "portrait", str(self.__isPortrait))
222 config.set(constants.__pretty_app_name__, "fullscreen", str(self.__window_in_fullscreen))
223 config.set(constants.__pretty_app_name__, "audiopath", self.controller.get_books_path())
225 def load_settings(self):
226 config = ConfigParser.SafeConfigParser()
227 config.read(constants._user_settings_)
228 self._load_settings(config)
230 def _load_settings(self, config):
232 window_in_fullscreen = False
233 booksPath = constants._default_book_path_
235 isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait")
236 window_in_fullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
237 booksPath = config.get(constants.__pretty_app_name__, "audiopath")
238 except ConfigParser.NoSectionError, e:
240 "Settings file %s is missing section %s" % (
241 constants._user_settings_,
246 if isPortrait ^ self.__isPortrait:
248 orientation = gtk.ORIENTATION_VERTICAL
250 orientation = gtk.ORIENTATION_HORIZONTAL
251 self.set_orientation(orientation)
253 self.__window_in_fullscreen = window_in_fullscreen
254 if self.__window_in_fullscreen:
255 self.win.fullscreen()
257 self.win.unfullscreen()
259 self.controller.load_books_path(booksPath)
262 def __format_name(path):
263 if os.path.isfile(path):
264 return os.path.basename(path).rsplit(".", 1)[0]
266 return os.path.basename(path)
268 @gtk_toolbox.log_exception(_moduleLogger)
269 def _on_select_audiobook(self, *args):
270 if not self._bookSelection:
272 index = hildonize.touch_selector(
275 (self.__format_name(bookPath) for bookPath in self._bookSelection),
276 self._bookSelectionIndex if 0 <= self._bookSelectionIndex else 0,
278 self._bookSelectionIndex = index
279 bookName = self._bookSelection[index]
280 self.controller.set_book(bookName)
282 @gtk_toolbox.log_exception(_moduleLogger)
283 def _on_select_chapter(self, *args):
284 if not self._chapterSelection:
286 index = hildonize.touch_selector(
289 (self.__format_name(chapterPath) for chapterPath in self._chapterSelection),
290 self._chapterSelectionIndex if 0 <= self._chapterSelectionIndex else 0,
292 self._chapterSelectionIndex = index
293 chapterName = self._chapterSelection[index]
294 self.controller.set_chapter(chapterName)
296 @gtk_toolbox.log_exception(_moduleLogger)
297 def _on_select_sleep(self, *args):
298 if self.sleep_timer is not None:
299 gobject.source_remove(self.sleep_timer)
302 index = hildonize.touch_selector(
305 self._sleepSelection,
306 self._sleepSelectionIndex if 0 <= self._sleepSelectionIndex else 0,
309 _moduleLogger.exception("Handling as if user cancelled")
310 hildonize.show_information_banner(self.win, "Sleep timer canceled")
313 self._sleepSelectionIndex = index
314 sleepName = self._sleepSelection[index]
316 time_out = int(sleepName)
318 timeout_add_seconds(time_out * 60, self.sleep)
320 if hildonize.IS_FREMANTLE_SUPPORTED:
321 self.sleeptime_button.set_text("Sleeptimer", sleepName)
323 self._sleepMenuItem.get_child().set_text("Sleeptimer: %s" % (sleepName, ))
325 @gtk_toolbox.log_exception(_moduleLogger)
326 def __on_call_started(self, callMonitor):
329 @gtk_toolbox.log_exception(_moduleLogger)
330 def _on_settings(self, *args):
331 if self.__settingsWindow is None:
333 self.__settingsManager = settings.SettingsDialog(vbox)
335 self.__settingsWindow = gtk.Window()
336 self.__settingsWindow.add(vbox)
337 self.__settingsWindow = hildonize.hildonize_window(self._app, self.__settingsWindow)
338 self.__settingsManager.window = self.__settingsWindow
340 self.__settingsWindow.set_title("Settings")
341 self.__settingsWindow.set_transient_for(self.win)
342 self.__settingsWindow.set_default_size(*self.win.get_size())
343 self.__settingsWindow.connect("delete-event", self._on_settings_delete)
344 self.__settingsManager.set_portrait_state(self.__isPortrait)
345 self.__settingsManager.set_audiobook_path(self.controller.get_books_path())
346 self.__settingsWindow.set_modal(True)
347 self.__settingsWindow.show_all()
349 @gtk_toolbox.log_exception(_moduleLogger)
350 def _on_settings_delete(self, *args):
351 self.__settingsWindow.emit_stop_by_name("delete-event")
352 self.__settingsWindow.hide()
353 self.__settingsWindow.set_modal(False)
355 isPortrait = self.__settingsManager.is_portrait()
356 if isPortrait ^ self.__isPortrait:
358 orientation = gtk.ORIENTATION_VERTICAL
360 orientation = gtk.ORIENTATION_HORIZONTAL
361 self.set_orientation(orientation)
362 if self.__settingsManager.get_audiobook_path() != self.controller.get_books_path():
363 self.controller.load_books_path(self.__settingsManager.get_audiobook_path())
367 @gtk_toolbox.log_exception(_moduleLogger)
368 def update_seek(self):
369 #print self.controller.get_percentage()
370 if self.controller.is_playing():
371 gtk.gdk.threads_enter()
372 self.seek.set_value(self.controller.get_percentage() * 100)
373 gtk.gdk.threads_leave()
374 #self.controller.get_percentage()
375 return True # run again
377 @gtk_toolbox.log_exception(_moduleLogger)
379 _moduleLogger.info("sleep time timeout")
380 hildonize.show_information_banner(self.win, "Sleep timer")
381 self.controller.stop()
382 self.set_button_text("Resume", "Resume playing the audiobook")
383 return False # do not repeat
385 @gtk_toolbox.log_exception(_moduleLogger)
386 def get_help(self, button):
387 Browser.open("file:///opt/Nqa-Audiobook-player/Help/nqaap.html")
389 @gtk_toolbox.log_exception(_moduleLogger)
390 def seek_changed(self, seek, scroll , value):
391 # print "sok", scroll
392 self.controller.seek_percent(seek.get_value() / 100.0)
394 @gtk_toolbox.log_exception(_moduleLogger)
395 def _on_next_chapter(self, *args):
396 self.controller.next_chapter()
398 @gtk_toolbox.log_exception(_moduleLogger)
399 def _on_previous_chapter(self, *args):
400 self.controller.previous_chapter()
402 @gtk_toolbox.log_exception(_moduleLogger)
403 def play_pressed(self, button):
404 if self.controller.is_playing():
409 @gtk_toolbox.log_exception(_moduleLogger)
410 def on_key_press(self, widget, event, *args):
411 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
412 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
414 event.keyval == gtk.keysyms.F6 or
415 event.keyval in RETURN_TYPES and isCtrl
417 # The "Full screen" hardware key has been pressed
418 if self.__window_in_fullscreen:
419 self.win.unfullscreen ()
421 self.win.fullscreen ()
423 elif event.keyval == gtk.keysyms.o and isCtrl:
424 self._toggle_rotate()
427 event.keyval in (gtk.keysyms.w, gtk.keysyms.q) and
428 event.get_state() & gtk.gdk.CONTROL_MASK
431 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
432 with open(constants._user_logpath_, "r") as f:
433 logLines = f.xreadlines()
434 log = "".join(logLines)
435 self._clipboard.set_text(str(log))
437 elif event.keyval in RETURN_TYPES:
438 if self.controller.is_playing():
443 elif event.keyval == gtk.keysyms.Left:
444 self.controller.previous_chapter()
446 elif event.keyval == gtk.keysyms.Right:
447 self.controller.next_chapter()
450 @gtk_toolbox.log_exception(_moduleLogger)
451 def _on_window_state_change(self, widget, event, *args):
452 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
453 self.__window_in_fullscreen = True
455 self.__window_in_fullscreen = False
457 @gtk_toolbox.log_exception(_moduleLogger)
458 def quit(self, *args): # what are the arguments?
459 _moduleLogger.info("Shutting down")
462 self.controller.stop() # to save the state
469 self.set_button_text("Stop", "Stop playing the audiobook")
470 self.controller.play()
473 self.set_button_text("Resume", "Resume playing the audiobook")
474 self.controller.stop()
476 def set_orientation(self, orientation):
477 if orientation == gtk.ORIENTATION_VERTICAL:
478 if self.__isPortrait:
480 hildonize.window_to_portrait(self.win)
481 self.__isPortrait = True
483 self._viewLayout.remove(self._controlLayout)
484 self._mainLayout.add(self._controlLayout)
485 elif orientation == gtk.ORIENTATION_HORIZONTAL:
486 if not self.__isPortrait:
488 hildonize.window_to_landscape(self.win)
489 self.__isPortrait = False
491 self._mainLayout.remove(self._controlLayout)
492 self._viewLayout.add(self._controlLayout)
494 raise NotImplementedError(orientation)
496 def get_orientation(self):
497 return gtk.ORIENTATION_VERTICAL if self.__isPortrait else gtk.ORIENTATION_HORIZONTAL
499 def _toggle_rotate(self):
500 if self.__isPortrait:
501 self.set_orientation(gtk.ORIENTATION_HORIZONTAL)
503 self.set_orientation(gtk.ORIENTATION_VERTICAL)
505 def change_chapter(self, chapterName):
506 if chapterName is None:
507 _moduleLogger.debug("chapter selection canceled.")
508 #import pdb; pdb.set_trace() # start debugger
509 self.ignore_next_chapter_change = True
510 return True # this should end the function and indicate it has been handled
512 if self.ignore_next_chapter_change:
513 self.ignore_next_chapter_change = False
514 _moduleLogger.debug("followup chapter selection canceled.")
515 #import pdb; pdb.set_trace() # start debugger
516 return True # this should end the function and indicate it has been handled
518 if self.auto_chapter_selected:
519 _moduleLogger.debug("chapter changed (by controller) to: %s" % chapterName)
520 self.auto_chapter_selected = False
523 _moduleLogger.debug("chapter selection sendt to controller: %s" % chapterName)
524 self.controller.set_chapter(chapterName) # signal controller
525 self.set_button_text("Play", "Start playing the audiobook") # reset button
527 def set_button_text(self, title, text):
528 if hildonize.IS_FREMANTLE_SUPPORTED:
529 self.button.set_text("Resume", "Resume playing the audiobook")
531 self.button.set_label("%s - %s" % (title, text))
533 def set_books(self, books):
534 _moduleLogger.debug("new books")
535 del self._bookSelection[:]
536 self._bookSelection.extend(books)
537 if len(books) == 0 and self.controller is not None:
538 hildonize.show_information_banner(self.win, "No audiobooks found. \nPlease place your audiobooks in the directory %s" % self.controller.get_books_path())
540 def set_book(self, bookPath, cover):
541 bookName = self.__format_name(bookPath)
543 self.set_button_text("Play", "Start playing the audiobook") # reset button
544 self.title.set_text(bookName)
545 if hildonize.IS_FREMANTLE_SUPPORTED:
546 self.book_button.set_text("Audiobook", bookName)
548 self._audiobookMenuItem.get_child().set_text("Audiobook: %s" % (bookName, ))
550 self.cover.set_from_file(cover)
552 def set_chapter(self, chapterIndex):
554 Called from controller whenever a new chapter is started
556 chapter parameter is supposed to be the index for the chapter, not the name
558 self.auto_chapter_selected = True
559 if hildonize.IS_FREMANTLE_SUPPORTED:
560 self.chapter_button.set_text("Chapter", str(chapterIndex))
562 self._chapterMenuItem.get_child().set_text("Chapter: %s" % (chapterIndex, ))
564 def set_chapters(self, chapters):
565 _moduleLogger.debug("setting chapters" )
566 del self._chapterSelection[:]
567 self._chapterSelection.extend(chapters)
569 def set_sleep_timer(self, mins):
573 def set_selected_value(self, button, value):
574 i = button.get_selector().get_model(0).index[value] # get index of value from list
575 button.set_active(i) # set active index to that index
578 def _old_timeout_add_seconds(timeout, callback):
579 return gobject.timeout_add(timeout * 1000, callback)
582 def _timeout_add_seconds(timeout, callback):
583 return gobject.timeout_add_seconds(timeout, callback)
587 gobject.timeout_add_seconds
588 timeout_add_seconds = _timeout_add_seconds
589 except AttributeError:
590 timeout_add_seconds = _old_timeout_add_seconds
593 if __name__ == "__main__":