1 # @todo Add icons to buttons/rows to indicate that the currently playing track is coming from that
12 import util.misc as misc_utils
19 _moduleLogger = logging.getLogger(__name__)
22 class BasicWindow(gobject.GObject):
26 gobject.SIGNAL_RUN_LAST,
31 gobject.SIGNAL_RUN_LAST,
33 (gobject.TYPE_PYOBJECT, ),
37 def __init__(self, player, store, index):
38 gobject.GObject.__init__(self)
39 self._isDestroyed = False
45 self._clipboard = gtk.clipboard_get()
46 self._windowInFullscreen = False
48 self._errorBanner = banners.StackingBanner()
50 self._layout = gtk.VBox()
51 self._layout.pack_start(self._errorBanner.toplevel, False, True)
53 self._window = gtk.Window()
54 self._window.add(self._layout)
55 self._window = hildonize.hildonize_window(self, self._window)
57 self._window.set_icon(self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["icon"]))
58 self._window.connect("key-press-event", self._on_key_press)
59 self._window.connect("window-state-event", self._on_window_state_change)
60 self._window.connect("destroy", self._on_destroy)
66 def save_settings(self, config, sectionName):
67 config.add_section(sectionName)
68 config.set(sectionName, "fullscreen", str(self._windowInFullscreen))
70 def load_settings(self, config, sectionName):
72 self._windowInFullscreen = config.getboolean(sectionName, "fullscreen")
73 except ConfigParser.NoSectionError, e:
75 "Settings file %s is missing section %s" % (
76 constants._user_settings_,
81 if self._windowInFullscreen:
82 self._window.fullscreen()
84 self._window.unfullscreen()
86 @misc_utils.log_exception(_moduleLogger)
87 def _on_destroy(self, *args):
88 self._isDestroyed = True
90 @misc_utils.log_exception(_moduleLogger)
91 def _on_window_state_change(self, widget, event, *args):
92 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
93 self._windowInFullscreen = True
95 self._windowInFullscreen = False
96 self.emit("fullscreen", self._windowInFullscreen)
98 @misc_utils.log_exception(_moduleLogger)
99 def _on_key_press(self, widget, event, *args):
100 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
101 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
103 event.keyval == gtk.keysyms.F6 or
104 event.keyval in RETURN_TYPES and isCtrl
106 # The "Full screen" hardware key has been pressed
107 if self._windowInFullscreen:
108 self._window.unfullscreen ()
110 self._window.fullscreen ()
113 event.keyval in (gtk.keysyms.w, ) and
114 event.get_state() & gtk.gdk.CONTROL_MASK
116 self._window.destroy()
118 event.keyval in (gtk.keysyms.q, ) and
119 event.get_state() & gtk.gdk.CONTROL_MASK
122 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
123 with open(constants._user_logpath_, "r") as f:
124 logLines = f.xreadlines()
125 log = "".join(logLines)
126 self._clipboard.set_text(str(log))
130 class SourceSelector(BasicWindow):
132 def __init__(self, player, store, index):
135 BasicWindow.__init__(self, player, store, index)
137 self._loadingBanner = banners.GenericBanner()
139 self._radioButton = self._create_button("radio", "Radio")
140 self._radioButton.connect("clicked", self._on_source_selected, RadioWindow)
141 self._radioWrapper = gtk.VBox()
142 self._radioWrapper.pack_start(self._radioButton, False, True)
144 self._conferenceButton = self._create_button("conferences", "Conferences")
145 self._conferenceButton.connect("clicked", self._on_source_selected, ConferencesWindow)
146 self._conferenceWrapper = gtk.VBox()
147 self._conferenceWrapper.pack_start(self._conferenceButton, False, True)
149 self._magazineButton = self._create_button("magazines", "Magazines")
150 #self._magazineButton.connect("clicked", self._on_source_selected)
151 self._magazineWrapper = gtk.VBox()
152 self._magazineWrapper.pack_start(self._magazineButton, False, True)
154 self._scriptureButton = self._create_button("scriptures", "Scriptures")
155 #self._scriptureButton.connect("clicked", self._on_source_selected)
156 self._scriptureWrapper = gtk.VBox()
157 self._scriptureWrapper.pack_start(self._scriptureButton, False, True)
159 self._buttonLayout = gtk.VButtonBox()
160 self._buttonLayout.set_layout(gtk.BUTTONBOX_SPREAD)
161 self._buttonLayout.pack_start(self._radioWrapper, True, True)
162 self._buttonLayout.pack_start(self._conferenceWrapper, True, True)
163 self._buttonLayout.pack_start(self._magazineWrapper, True, True)
164 self._buttonLayout.pack_start(self._scriptureWrapper, True, True)
166 self._playcontrol = playcontrol.PlayControl(player, store)
168 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
169 self._layout.pack_start(self._buttonLayout, True, True)
170 self._layout.pack_start(self._playcontrol.toplevel, False, True)
172 self._window.set_title(constants.__pretty_app_name__)
173 self._window.show_all()
174 self._errorBanner.toplevel.hide()
175 self._playcontrol.toplevel.hide()
179 def _show_loading(self):
180 animationPath = self._store.STORE_LOOKUP["loading"]
181 animation = self._store.get_pixbuf_animation_from_store(animationPath)
182 self._loadingBanner.show(animation, "Loading...")
183 self._buttonLayout.set_sensitive(False)
185 def _hide_loading(self):
186 self._loadingBanner.hide()
187 self._buttonLayout.set_sensitive(True)
191 self._index.download(
197 def _create_button(self, icon, message):
198 image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon])
201 label.set_text(message)
203 buttonLayout = gtk.HBox(False, 5)
204 buttonLayout.pack_start(image, False, False)
205 buttonLayout.pack_start(label, False, True)
206 button = gtk.Button()
207 button.add(buttonLayout)
211 @misc_utils.log_exception(_moduleLogger)
212 def _on_languages(self, languages):
214 self._languages = list(languages)
216 @misc_utils.log_exception(_moduleLogger)
217 def _on_error(self, exception):
219 self._errorBanner.push_message(exception)
221 @misc_utils.log_exception(_moduleLogger)
222 def _on_source_selected(self, widget, Source):
223 sourceWindow = Source(self._player, self._store, self._index, self._languages[0]["id"])
224 sourceWindow.window.set_modal(True)
225 sourceWindow.window.set_transient_for(self._window)
226 sourceWindow.window.set_default_size(*self._window.get_size())
229 gobject.type_register(SourceSelector)
232 class RadioWindow(BasicWindow):
234 def __init__(self, player, store, index, languageId):
235 BasicWindow.__init__(self, player, store, index)
237 self._player.connect("state-change", self._on_player_state_change)
238 self._player.connect("title-change", self._on_player_title_change)
240 self._loadingBanner = banners.GenericBanner()
242 headerPath = self._store.STORE_LOOKUP["radio_header"]
243 self._header = self._store.get_image_from_store(headerPath)
244 self._headerNavigation = presenter.NavigationBox()
245 self._headerNavigation.toplevel.add(self._header)
246 self._headerNavigation.connect("action", self._on_nav_action)
247 self._headerNavigation.connect("navigating", self._on_navigating)
249 self._programmingModel = gtk.ListStore(
254 textrenderer = gtk.CellRendererText()
255 timeColumn = gtk.TreeViewColumn("Time")
256 timeColumn.pack_start(textrenderer, expand=True)
257 timeColumn.add_attribute(textrenderer, "text", 0)
259 textrenderer = gtk.CellRendererText()
260 titleColumn = gtk.TreeViewColumn("Program")
261 titleColumn.pack_start(textrenderer, expand=True)
262 titleColumn.add_attribute(textrenderer, "text", 1)
264 self._treeView = gtk.TreeView()
265 self._treeView.set_headers_visible(False)
266 self._treeView.set_model(self._programmingModel)
267 self._treeView.append_column(timeColumn)
268 self._treeView.append_column(titleColumn)
269 self._treeView.get_selection().connect("changed", self._on_row_changed)
271 self._treeScroller = gtk.ScrolledWindow()
272 self._treeScroller.add(self._treeView)
273 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
275 self._presenter = presenter.StreamMiniPresenter(self._store)
276 if self._player.state == "play":
277 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
279 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
280 self._presenterNavigation = presenter.NavigationBox()
281 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
282 self._presenterNavigation.connect("action", self._on_nav_action)
283 self._presenterNavigation.connect("navigating", self._on_navigating)
285 self._radioLayout = gtk.VBox(False)
286 self._radioLayout.pack_start(self._headerNavigation.toplevel, False, False)
287 self._radioLayout.pack_start(self._treeScroller, True, True)
288 self._radioLayout.pack_start(self._presenterNavigation.toplevel, False, True)
290 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
291 self._layout.pack_start(self._radioLayout, True, True)
293 self._window.set_title("Radio")
294 self._window.show_all()
295 self._errorBanner.toplevel.hide()
296 self._loadingBanner.toplevel.hide()
298 self._dateShown = datetime.datetime.now()
301 def _show_loading(self):
302 animationPath = self._store.STORE_LOOKUP["loading"]
303 animation = self._store.get_pixbuf_animation_from_store(animationPath)
304 self._loadingBanner.show(animation, "Loading...")
306 def _hide_loading(self):
307 self._loadingBanner.hide()
311 self._programmingModel.clear()
312 self._index.download(
313 "get_radio_channels",
318 def _get_current_row(self):
319 nowTime = self._dateShown.strftime("%H:%M:%S")
321 for i, row in enumerate(self._programmingModel):
330 @misc_utils.log_exception(_moduleLogger)
331 def _on_player_state_change(self, player, newState):
332 if self._headerNavigation.is_active() or self._presenterNavigation.is_active():
335 if newState == "play":
336 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
337 elif newState == "pause":
338 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
340 _moduleLogger.info("Unhandled player state %s" % newState)
341 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
343 @misc_utils.log_exception(_moduleLogger)
344 def _on_player_title_change(self, player, newState):
345 _moduleLogger.info("Player title magically changed to %s" % player.title)
348 @misc_utils.log_exception(_moduleLogger)
349 def _on_navigating(self, widget, navState):
350 if navState == "clicking":
351 if self._player.state == "play":
355 elif navState == "down":
357 elif navState == "up":
359 elif navState == "left":
361 elif navState == "right":
364 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
366 @misc_utils.log_exception(_moduleLogger)
367 def _on_nav_action(self, widget, navState):
368 if self._player.state == "play":
369 self._presenter.set_state(self._store.STORE_LOOKUP["play"])
371 self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
373 if navState == "clicking":
374 if self._player.state == "play":
378 elif navState == "down":
379 self.window.destroy()
380 elif navState == "up":
382 elif navState == "left":
383 self._dateShown += datetime.timedelta(days=1)
385 elif navState == "right":
386 self._dateShown -= datetime.timedelta(days=1)
389 @misc_utils.log_exception(_moduleLogger)
390 def _on_channels(self, channels):
391 if self._isDestroyed:
392 _moduleLogger.info("Download complete but window destroyed")
395 channels = list(channels)
396 if 1 < len(channels):
397 _moduleLogger.warning("More channels now available!")
398 channel = channels[0]
399 self._index.download(
400 "get_radio_channel_programming",
407 @misc_utils.log_exception(_moduleLogger)
408 def _on_channel(self, programs):
409 if self._isDestroyed:
410 _moduleLogger.info("Download complete but window destroyed")
414 for program in programs:
415 row = program["time"], program["title"]
416 self._programmingModel.append(row)
418 path = (self._get_current_row(), )
419 self._treeView.scroll_to_cell(path)
420 self._treeView.get_selection().select_path(path)
422 @misc_utils.log_exception(_moduleLogger)
423 def _on_load_error(self, exception):
425 self._errorBanner.push_message(exception)
427 @misc_utils.log_exception(_moduleLogger)
428 def _on_row_changed(self, selection):
429 if len(self._programmingModel) == 0:
432 rowIndex = self._get_current_row()
434 if not selection.path_is_selected(path):
435 # Undo the user's changing of the selection
436 selection.select_path(path)
439 gobject.type_register(RadioWindow)
442 class ListWindow(BasicWindow):
444 def __init__(self, player, store, index):
445 BasicWindow.__init__(self, player, store, index)
447 self._loadingBanner = banners.GenericBanner()
449 modelTypes, columns = zip(*self._get_columns())
451 self._model = gtk.ListStore(*modelTypes)
453 self._treeView = gtk.TreeView()
454 self._treeView.connect("row-activated", self._on_row_activated)
455 self._treeView.set_headers_visible(False)
456 self._treeView.set_model(self._model)
457 for column in columns:
458 if column is not None:
459 self._treeView.append_column(column)
461 self._treeScroller = gtk.ScrolledWindow()
462 self._treeScroller.add(self._treeView)
463 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
465 self._playcontrol = playcontrol.PlayControl(self._player, self._store)
467 self._contentLayout = gtk.VBox(False)
468 self._contentLayout.pack_start(self._treeScroller, True, True)
469 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
471 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
472 self._layout.pack_start(self._contentLayout, True, True)
474 self._window.show_all()
475 self._errorBanner.toplevel.hide()
476 self._loadingBanner.toplevel.hide()
479 self._playcontrol.refresh()
482 def _get_columns(cls):
483 raise NotImplementedError("")
485 def _get_current_row(self):
486 raise NotImplementedError("")
488 @misc_utils.log_exception(_moduleLogger)
489 def _on_row_activated(self, view, path, column):
490 raise NotImplementedError("")
492 def _show_loading(self):
493 animationPath = self._store.STORE_LOOKUP["loading"]
494 animation = self._store.get_pixbuf_animation_from_store(animationPath)
495 self._loadingBanner.show(animation, "Loading...")
497 def _hide_loading(self):
498 self._loadingBanner.hide()
504 def _select_row(self):
505 path = (self._get_current_row(), )
506 self._treeView.scroll_to_cell(path)
507 self._treeView.get_selection().select_path(path)
510 class ConferencesWindow(ListWindow):
512 def __init__(self, player, store, index, languageId):
513 self._languageId = languageId
515 ListWindow.__init__(self, player, store, index)
516 self._window.set_title("Conferences")
519 def _get_columns(cls):
520 yield gobject.TYPE_STRING, None
522 textrenderer = gtk.CellRendererText()
523 column = gtk.TreeViewColumn("Date")
524 column.pack_start(textrenderer, expand=True)
525 column.add_attribute(textrenderer, "text", 1)
526 yield gobject.TYPE_STRING, column
528 textrenderer = gtk.CellRendererText()
529 column = gtk.TreeViewColumn("Conference")
530 column.pack_start(textrenderer, expand=True)
531 column.add_attribute(textrenderer, "text", 2)
532 yield gobject.TYPE_STRING, column
534 def _get_current_row(self):
535 # @todo Not implemented yet
539 ListWindow._refresh(self)
540 self._index.download(
542 self._on_conferences,
547 @misc_utils.log_exception(_moduleLogger)
548 def _on_conferences(self, programs):
549 if self._isDestroyed:
550 _moduleLogger.info("Download complete but window destroyed")
554 for program in programs:
555 row = program["id"], program["title"], program["full_title"]
556 self._model.append(row)
558 path = (self._get_current_row(), )
559 self._treeView.scroll_to_cell(path)
560 self._treeView.get_selection().select_path(path)
562 @misc_utils.log_exception(_moduleLogger)
563 def _on_error(self, exception):
565 self._errorBanner.push_message(exception)
567 @misc_utils.log_exception(_moduleLogger)
568 def _on_row_activated(self, view, path, column):
569 itr = self._model.get_iter(path)
570 conferenceId = self._model.get_value(itr, 0)
572 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, self._index, conferenceId)
573 sessionsWindow.window.set_modal(True)
574 sessionsWindow.window.set_transient_for(self._window)
575 sessionsWindow.window.set_default_size(*self._window.get_size())
578 gobject.type_register(ConferencesWindow)
581 class ConferenceSessionsWindow(ListWindow):
583 def __init__(self, player, store, index, conferenceId):
584 self._conferenceId = conferenceId
586 ListWindow.__init__(self, player, store, index)
587 self._window.set_title("Sessions")
590 def _get_columns(cls):
591 yield gobject.TYPE_STRING, None
593 textrenderer = gtk.CellRendererText()
594 column = gtk.TreeViewColumn("Session")
595 column.pack_start(textrenderer, expand=True)
596 column.add_attribute(textrenderer, "text", 1)
597 yield gobject.TYPE_STRING, column
599 def _get_current_row(self):
600 # @todo Not implemented yet
604 ListWindow._refresh(self)
605 self._index.download(
606 "get_conference_sessions",
607 self._on_conference_sessions,
612 @misc_utils.log_exception(_moduleLogger)
613 def _on_conference_sessions(self, programs):
614 if self._isDestroyed:
615 _moduleLogger.info("Download complete but window destroyed")
619 for program in programs:
620 row = program["id"], program["title"]
621 self._model.append(row)
623 path = (self._get_current_row(), )
624 self._treeView.scroll_to_cell(path)
625 self._treeView.get_selection().select_path(path)
627 @misc_utils.log_exception(_moduleLogger)
628 def _on_error(self, exception):
630 self._errorBanner.push_message(exception)
632 @misc_utils.log_exception(_moduleLogger)
633 def _on_row_activated(self, view, path, column):
634 itr = self._model.get_iter(path)
635 sessionId = self._model.get_value(itr, 0)
637 sessionsWindow = ConferenceTalksWindow(self._player, self._store, self._index, sessionId)
638 sessionsWindow.window.set_modal(True)
639 sessionsWindow.window.set_transient_for(self._window)
640 sessionsWindow.window.set_default_size(*self._window.get_size())
643 gobject.type_register(ConferenceSessionsWindow)
646 class ConferenceTalksWindow(ListWindow):
648 def __init__(self, player, store, index, sessionId):
649 self._sessionId = sessionId
651 ListWindow.__init__(self, player, store, index)
652 self._window.set_title("Talks")
655 def _get_columns(cls):
656 yield gobject.TYPE_STRING, None
658 textrenderer = gtk.CellRendererText()
659 column = gtk.TreeViewColumn("Talk")
660 column.pack_start(textrenderer, expand=True)
661 column.add_attribute(textrenderer, "text", 1)
662 yield gobject.TYPE_STRING, column
664 def _get_current_row(self):
665 # @todo Not implemented yet
669 ListWindow._refresh(self)
670 self._index.download(
671 "get_conference_talks",
672 self._on_conference_talks,
677 @misc_utils.log_exception(_moduleLogger)
678 def _on_conference_talks(self, programs):
679 if self._isDestroyed:
680 _moduleLogger.info("Download complete but window destroyed")
684 for program in programs:
685 row = program["id"], "%s\n%s" % (program["title"], program["speaker"])
686 self._model.append(row)
688 path = (self._get_current_row(), )
689 self._treeView.scroll_to_cell(path)
690 self._treeView.get_selection().select_path(path)
692 @misc_utils.log_exception(_moduleLogger)
693 def _on_error(self, exception):
695 self._errorBanner.push_message(exception)
697 @misc_utils.log_exception(_moduleLogger)
698 def _on_row_activated(self, view, path, column):
699 raise NotImplementedError("")
702 gobject.type_register(ConferenceTalksWindow)