Noting things not implemented yet
[watersofshiloah] / src / windows.py
1 import ConfigParser
2 import datetime
3 import logging
4
5 import gobject
6 import gtk
7
8 import constants
9 import hildonize
10 import util.misc as misc_utils
11
12 import banners
13 import playcontrol
14 import presenter
15
16
17 _moduleLogger = logging.getLogger(__name__)
18
19
20 class BasicWindow(gobject.GObject):
21
22         __gsignals__ = {
23                 'quit' : (
24                         gobject.SIGNAL_RUN_LAST,
25                         gobject.TYPE_NONE,
26                         (),
27                 ),
28                 'fullscreen' : (
29                         gobject.SIGNAL_RUN_LAST,
30                         gobject.TYPE_NONE,
31                         (gobject.TYPE_PYOBJECT, ),
32                 ),
33         }
34
35         def __init__(self, player, store, index):
36                 gobject.GObject.__init__(self)
37                 self._isDestroyed = False
38
39                 self._player = player
40                 self._store = store
41                 self._index = index
42
43                 self._clipboard = gtk.clipboard_get()
44                 self._windowInFullscreen = False
45
46                 self._errorBanner = banners.StackingBanner()
47
48                 self._layout = gtk.VBox()
49                 self._layout.pack_start(self._errorBanner.toplevel, False, True)
50
51                 self._window = gtk.Window()
52                 self._window.add(self._layout)
53                 self._window = hildonize.hildonize_window(self, self._window)
54
55                 self._window.set_icon(self._store.get_pixbuf_from_store(self._store.STORE_LOOKUP["icon"]))
56                 self._window.connect("key-press-event", self._on_key_press)
57                 self._window.connect("window-state-event", self._on_window_state_change)
58                 self._window.connect("destroy", self._on_destroy)
59
60         @property
61         def window(self):
62                 return self._window
63
64         def save_settings(self, config, sectionName):
65                 config.add_section(sectionName)
66                 config.set(sectionName, "fullscreen", str(self._windowInFullscreen))
67
68         def load_settings(self, config, sectionName):
69                 try:
70                         self._windowInFullscreen = config.getboolean(sectionName, "fullscreen")
71                 except ConfigParser.NoSectionError, e:
72                         _moduleLogger.info(
73                                 "Settings file %s is missing section %s" % (
74                                         constants._user_settings_,
75                                         e.section,
76                                 )
77                         )
78
79                 if self._windowInFullscreen:
80                         self._window.fullscreen()
81                 else:
82                         self._window.unfullscreen()
83
84         @misc_utils.log_exception(_moduleLogger)
85         def _on_destroy(self, *args):
86                 self._isDestroyed = True
87
88         @misc_utils.log_exception(_moduleLogger)
89         def _on_window_state_change(self, widget, event, *args):
90                 if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
91                         self._windowInFullscreen = True
92                 else:
93                         self._windowInFullscreen = False
94                 self.emit("fullscreen", self._windowInFullscreen)
95
96         @misc_utils.log_exception(_moduleLogger)
97         def _on_key_press(self, widget, event, *args):
98                 RETURN_TYPES = (gtk.keysyms.Return, gtk.keysyms.ISO_Enter, gtk.keysyms.KP_Enter)
99                 isCtrl = bool(event.get_state() & gtk.gdk.CONTROL_MASK)
100                 if (
101                         event.keyval == gtk.keysyms.F6 or
102                         event.keyval in RETURN_TYPES and isCtrl
103                 ):
104                         # The "Full screen" hardware key has been pressed
105                         if self._windowInFullscreen:
106                                 self._window.unfullscreen ()
107                         else:
108                                 self._window.fullscreen ()
109                         return True
110                 elif (
111                         event.keyval in (gtk.keysyms.w, ) and
112                         event.get_state() & gtk.gdk.CONTROL_MASK
113                 ):
114                         self._window.destroy()
115                 elif (
116                         event.keyval in (gtk.keysyms.q, ) and
117                         event.get_state() & gtk.gdk.CONTROL_MASK
118                 ):
119                         self.emit("quit")
120                 elif event.keyval == gtk.keysyms.l and event.get_state() & gtk.gdk.CONTROL_MASK:
121                         with open(constants._user_logpath_, "r") as f:
122                                 logLines = f.xreadlines()
123                                 log = "".join(logLines)
124                                 self._clipboard.set_text(str(log))
125                         return True
126
127
128 class SourceSelector(BasicWindow):
129
130         def __init__(self, player, store, index):
131                 self._languages = []
132
133                 BasicWindow.__init__(self, player, store, index)
134
135                 self._loadingBanner = banners.GenericBanner()
136
137                 self._radioButton = self._create_button("radio", "Radio")
138                 self._radioButton.connect("clicked", self._on_source_selected, RadioWindow)
139                 self._radioWrapper = gtk.VBox()
140                 self._radioWrapper.pack_start(self._radioButton, False, True)
141
142                 self._conferenceButton = self._create_button("conferences", "Conferences")
143                 self._conferenceButton.connect("clicked", self._on_source_selected, ConferencesWindow)
144                 self._conferenceWrapper = gtk.VBox()
145                 self._conferenceWrapper.pack_start(self._conferenceButton, False, True)
146
147                 self._magazineButton = self._create_button("magazines", "Magazines")
148                 #self._magazineButton.connect("clicked", self._on_source_selected)
149                 self._magazineWrapper = gtk.VBox()
150                 self._magazineWrapper.pack_start(self._magazineButton, False, True)
151
152                 self._scriptureButton = self._create_button("scriptures", "Scriptures")
153                 #self._scriptureButton.connect("clicked", self._on_source_selected)
154                 self._scriptureWrapper = gtk.VBox()
155                 self._scriptureWrapper.pack_start(self._scriptureButton, False, True)
156
157                 self._buttonLayout = gtk.VBox(True, 5)
158                 self._buttonLayout.set_property("border-width", 5)
159                 self._buttonLayout.pack_start(self._radioWrapper, True, True)
160                 self._buttonLayout.pack_start(self._conferenceWrapper, True, True)
161                 self._buttonLayout.pack_start(self._magazineWrapper, True, True)
162                 self._buttonLayout.pack_start(self._scriptureWrapper, True, True)
163
164                 self._playcontrol = playcontrol.PlayControl(player, store)
165
166                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
167                 self._layout.pack_start(self._buttonLayout, True, True)
168                 self._layout.pack_start(self._playcontrol.toplevel, False, True)
169
170                 self._window.set_title(constants.__pretty_app_name__)
171                 self._window.show_all()
172                 self._errorBanner.toplevel.hide()
173                 self._playcontrol.toplevel.hide()
174
175                 self._refresh()
176
177         def _show_loading(self):
178                 animationPath = self._store.STORE_LOOKUP["loading"]
179                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
180                 self._loadingBanner.show(animation, "Loading...")
181                 self._buttonLayout.set_sensitive(False)
182
183         def _hide_loading(self):
184                 self._loadingBanner.hide()
185                 self._buttonLayout.set_sensitive(True)
186
187         def _refresh(self):
188                 self._show_loading()
189                 self._index.download(
190                         "get_languages",
191                         self._on_languages,
192                         self._on_error,
193                 )
194
195         def _create_button(self, icon, message):
196                 image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon])
197
198                 label = gtk.Label()
199                 label.set_text(message)
200
201                 buttonLayout = gtk.HBox(False, 5)
202                 buttonLayout.pack_start(image, False, False)
203                 buttonLayout.pack_start(label, False, True)
204                 button = gtk.Button()
205                 button.add(buttonLayout)
206
207                 return button
208
209         @misc_utils.log_exception(_moduleLogger)
210         def _on_languages(self, languages):
211                 self._hide_loading()
212                 self._languages = list(languages)
213
214         @misc_utils.log_exception(_moduleLogger)
215         def _on_error(self, exception):
216                 self._hide_loading()
217                 self._errorBanner.push_message(exception)
218
219         @misc_utils.log_exception(_moduleLogger)
220         def _on_source_selected(self, widget, Source):
221                 sourceWindow = Source(self._player, self._store, self._index, self._languages[0]["id"])
222                 sourceWindow.window.set_modal(True)
223                 sourceWindow.window.set_transient_for(self._window)
224                 sourceWindow.window.set_default_size(*self._window.get_size())
225
226
227 gobject.type_register(SourceSelector)
228
229
230 class RadioWindow(BasicWindow):
231
232         def __init__(self, player, store, index, languageId):
233                 BasicWindow.__init__(self, player, store, index)
234
235                 self._loadingBanner = banners.GenericBanner()
236
237                 headerPath = self._store.STORE_LOOKUP["radio_header"]
238                 self._header = self._store.get_image_from_store(headerPath)
239                 self._headerNavigation = presenter.NavigationBox()
240                 self._headerNavigation.toplevel.add(self._header)
241                 self._headerNavigation.connect("action", self._on_nav_action)
242
243
244                 self._programmingModel = gtk.ListStore(
245                         gobject.TYPE_STRING,
246                         gobject.TYPE_STRING,
247                 )
248
249                 textrenderer = gtk.CellRendererText()
250                 timeColumn = gtk.TreeViewColumn("Time")
251                 timeColumn.pack_start(textrenderer, expand=True)
252                 timeColumn.add_attribute(textrenderer, "text", 0)
253
254                 textrenderer = gtk.CellRendererText()
255                 titleColumn = gtk.TreeViewColumn("Program")
256                 titleColumn.pack_start(textrenderer, expand=True)
257                 titleColumn.add_attribute(textrenderer, "text", 1)
258
259                 self._treeView = gtk.TreeView()
260                 self._treeView.set_headers_visible(False)
261                 self._treeView.set_model(self._programmingModel)
262                 self._treeView.append_column(timeColumn)
263                 self._treeView.append_column(titleColumn)
264                 self._treeView.get_selection().connect("changed", self._on_row_changed)
265
266                 self._treeScroller = gtk.ScrolledWindow()
267                 self._treeScroller.add(self._treeView)
268                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
269
270                 self._presenter = presenter.StreamMiniPresenter(self._player, self._store)
271                 self._presenterNavigation = presenter.NavigationBox()
272                 self._presenterNavigation.toplevel.add(self._presenter.toplevel)
273                 self._presenterNavigation.connect("action", self._on_nav_action)
274
275                 self._radioLayout = gtk.VBox(False)
276                 self._radioLayout.pack_start(self._headerNavigation.toplevel, False, False)
277                 self._radioLayout.pack_start(self._treeScroller, True, True)
278                 self._radioLayout.pack_start(self._presenterNavigation.toplevel, False, True)
279
280                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
281                 self._layout.pack_start(self._radioLayout, True, True)
282
283                 self._window.set_title("Radio")
284                 self._window.show_all()
285                 self._errorBanner.toplevel.hide()
286                 self._loadingBanner.toplevel.hide()
287
288                 self._dateShown = datetime.datetime.now()
289                 self._refresh()
290
291         def _show_loading(self):
292                 animationPath = self._store.STORE_LOOKUP["loading"]
293                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
294                 self._loadingBanner.show(animation, "Loading...")
295
296         def _hide_loading(self):
297                 self._loadingBanner.hide()
298
299         def _refresh(self):
300                 self._show_loading()
301                 self._programmingModel.clear()
302                 self._index.download(
303                         "get_radio_channels",
304                         self._on_channels,
305                         self._on_load_error,
306                 )
307
308         def _get_current_row(self):
309                 nowTime = self._dateShown.strftime("%H:%M:%S")
310                 i = 0
311                 for i, row in enumerate(self._programmingModel):
312                         if nowTime < row[0]:
313                                 if i == 0:
314                                         return 0
315                                 else:
316                                         return i - 1
317                 else:
318                         return i
319
320         @misc_utils.log_exception(_moduleLogger)
321         def _on_nav_action(self, widget, navState):
322                 if navState == "clicking":
323                         pass
324                 elif navState == "down":
325                         self.window.destroy()
326                 elif navState == "up":
327                         pass
328                 elif navState == "left":
329                         self._dateShown += datetime.timedelta(days=1)
330                         self._refresh()
331                 elif navState == "right":
332                         self._dateShown -= datetime.timedelta(days=1)
333                         self._refresh()
334
335         @misc_utils.log_exception(_moduleLogger)
336         def _on_channels(self, channels):
337                 if self._isDestroyed:
338                         _moduleLogger.info("Download complete but window destroyed")
339                         return
340
341                 channels = list(channels)
342                 if 1 < len(channels):
343                         _moduleLogger.warning("More channels now available!")
344                 channel = channels[0]
345                 self._index.download(
346                         "get_radio_channel_programming",
347                         self._on_channel,
348                         self._on_load_error,
349                         channel["id"],
350                         self._dateShown,
351                 )
352
353         @misc_utils.log_exception(_moduleLogger)
354         def _on_channel(self, programs):
355                 if self._isDestroyed:
356                         _moduleLogger.info("Download complete but window destroyed")
357                         return
358
359                 self._hide_loading()
360                 for program in programs:
361                         row = program["time"], program["title"]
362                         self._programmingModel.append(row)
363
364                 path = (self._get_current_row(), )
365                 self._treeView.scroll_to_cell(path)
366                 self._treeView.get_selection().select_path(path)
367
368         @misc_utils.log_exception(_moduleLogger)
369         def _on_load_error(self, exception):
370                 self._hide_loading()
371                 self._errorBanner.push_message(exception)
372
373         @misc_utils.log_exception(_moduleLogger)
374         def _on_row_changed(self, selection):
375                 if len(self._programmingModel) == 0:
376                         return
377
378                 rowIndex = self._get_current_row()
379                 path = (rowIndex, )
380                 if not selection.path_is_selected(path):
381                         # Undo the user's changing of the selection
382                         selection.select_path(path)
383
384
385 gobject.type_register(RadioWindow)
386
387
388 class ListWindow(BasicWindow):
389
390         def __init__(self, player, store, index):
391                 BasicWindow.__init__(self, player, store, index)
392
393                 self._loadingBanner = banners.GenericBanner()
394
395                 modelTypes, columns = zip(*self._get_columns())
396
397                 self._model = gtk.ListStore(*modelTypes)
398
399                 self._treeView = gtk.TreeView()
400                 self._treeView.connect("row-activated", self._on_row_activated)
401                 self._treeView.set_headers_visible(False)
402                 self._treeView.set_model(self._model)
403                 for column in columns:
404                         if column is not None:
405                                 self._treeView.append_column(column)
406
407                 self._treeScroller = gtk.ScrolledWindow()
408                 self._treeScroller.add(self._treeView)
409                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
410
411                 self._playcontrol = playcontrol.PlayControl(self._player, self._store)
412
413                 self._contentLayout = gtk.VBox(False)
414                 self._contentLayout.pack_start(self._treeScroller, True, True)
415                 self._contentLayout.pack_start(self._playcontrol.toplevel, False, True)
416
417                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
418                 self._layout.pack_start(self._contentLayout, True, True)
419
420                 self._window.show_all()
421                 self._errorBanner.toplevel.hide()
422                 self._loadingBanner.toplevel.hide()
423
424                 self._refresh()
425                 self._playcontrol.refresh()
426
427         @classmethod
428         def _get_columns(cls):
429                 raise NotImplementedError("")
430
431         def _get_current_row(self):
432                 raise NotImplementedError("")
433
434         @misc_utils.log_exception(_moduleLogger)
435         def _on_row_activated(self, view, path, column):
436                 raise NotImplementedError("")
437
438         def _show_loading(self):
439                 animationPath = self._store.STORE_LOOKUP["loading"]
440                 animation = self._store.get_pixbuf_animation_from_store(animationPath)
441                 self._loadingBanner.show(animation, "Loading...")
442
443         def _hide_loading(self):
444                 self._loadingBanner.hide()
445
446         def _refresh(self):
447                 self._show_loading()
448                 self._model.clear()
449
450         def _select_row(self):
451                 path = (self._get_current_row(), )
452                 self._treeView.scroll_to_cell(path)
453                 self._treeView.get_selection().select_path(path)
454
455
456 class ConferencesWindow(ListWindow):
457
458         def __init__(self, player, store, index, languageId):
459                 self._languageId = languageId
460
461                 ListWindow.__init__(self, player, store, index)
462                 self._window.set_title("Conferences")
463
464         @classmethod
465         def _get_columns(cls):
466                 yield gobject.TYPE_STRING, None
467
468                 textrenderer = gtk.CellRendererText()
469                 column = gtk.TreeViewColumn("Date")
470                 column.pack_start(textrenderer, expand=True)
471                 column.add_attribute(textrenderer, "text", 1)
472                 yield gobject.TYPE_STRING, column
473
474                 textrenderer = gtk.CellRendererText()
475                 column = gtk.TreeViewColumn("Conference")
476                 column.pack_start(textrenderer, expand=True)
477                 column.add_attribute(textrenderer, "text", 2)
478                 yield gobject.TYPE_STRING, column
479
480         def _get_current_row(self):
481                 # @todo Not implemented yet
482                 return 0
483
484         def _refresh(self):
485                 ListWindow._refresh(self)
486                 self._index.download(
487                         "get_conferences",
488                         self._on_conferences,
489                         self._on_error,
490                         self._languageId,
491                 )
492
493         @misc_utils.log_exception(_moduleLogger)
494         def _on_conferences(self, programs):
495                 if self._isDestroyed:
496                         _moduleLogger.info("Download complete but window destroyed")
497                         return
498
499                 self._hide_loading()
500                 for program in programs:
501                         row = program["id"], program["title"], program["full_title"]
502                         self._model.append(row)
503
504                 path = (self._get_current_row(), )
505                 self._treeView.scroll_to_cell(path)
506                 self._treeView.get_selection().select_path(path)
507
508         @misc_utils.log_exception(_moduleLogger)
509         def _on_error(self, exception):
510                 self._hide_loading()
511                 self._errorBanner.push_message(exception)
512
513         @misc_utils.log_exception(_moduleLogger)
514         def _on_row_activated(self, view, path, column):
515                 itr = self._model.get_iter(path)
516                 conferenceId = self._model.get_value(itr, 0)
517
518                 sessionsWindow = ConferenceSessionsWindow(self._player, self._store, self._index, conferenceId)
519                 sessionsWindow.window.set_modal(True)
520                 sessionsWindow.window.set_transient_for(self._window)
521                 sessionsWindow.window.set_default_size(*self._window.get_size())
522
523
524 gobject.type_register(ConferencesWindow)
525
526
527 class ConferenceSessionsWindow(ListWindow):
528
529         def __init__(self, player, store, index, conferenceId):
530                 self._conferenceId = conferenceId
531
532                 ListWindow.__init__(self, player, store, index)
533                 self._window.set_title("Sessions")
534
535         @classmethod
536         def _get_columns(cls):
537                 yield gobject.TYPE_STRING, None
538
539                 textrenderer = gtk.CellRendererText()
540                 column = gtk.TreeViewColumn("Session")
541                 column.pack_start(textrenderer, expand=True)
542                 column.add_attribute(textrenderer, "text", 1)
543                 yield gobject.TYPE_STRING, column
544
545         def _get_current_row(self):
546                 # @todo Not implemented yet
547                 return 0
548
549         def _refresh(self):
550                 ListWindow._refresh(self)
551                 self._index.download(
552                         "get_conference_sessions",
553                         self._on_conference_sessions,
554                         self._on_error,
555                         self._conferenceId,
556                 )
557
558         @misc_utils.log_exception(_moduleLogger)
559         def _on_conference_sessions(self, programs):
560                 if self._isDestroyed:
561                         _moduleLogger.info("Download complete but window destroyed")
562                         return
563
564                 self._hide_loading()
565                 for program in programs:
566                         row = program["id"], program["title"]
567                         self._model.append(row)
568
569                 path = (self._get_current_row(), )
570                 self._treeView.scroll_to_cell(path)
571                 self._treeView.get_selection().select_path(path)
572
573         @misc_utils.log_exception(_moduleLogger)
574         def _on_error(self, exception):
575                 self._hide_loading()
576                 self._errorBanner.push_message(exception)
577
578         @misc_utils.log_exception(_moduleLogger)
579         def _on_row_activated(self, view, path, column):
580                 itr = self._model.get_iter(path)
581                 sessionId = self._model.get_value(itr, 0)
582
583                 sessionsWindow = ConferenceTalksWindow(self._player, self._store, self._index, sessionId)
584                 sessionsWindow.window.set_modal(True)
585                 sessionsWindow.window.set_transient_for(self._window)
586                 sessionsWindow.window.set_default_size(*self._window.get_size())
587
588
589 gobject.type_register(ConferenceSessionsWindow)
590
591
592 class ConferenceTalksWindow(ListWindow):
593
594         def __init__(self, player, store, index, sessionId):
595                 self._sessionId = sessionId
596
597                 ListWindow.__init__(self, player, store, index)
598                 self._window.set_title("Talks")
599
600         @classmethod
601         def _get_columns(cls):
602                 yield gobject.TYPE_STRING, None
603
604                 textrenderer = gtk.CellRendererText()
605                 column = gtk.TreeViewColumn("Talk")
606                 column.pack_start(textrenderer, expand=True)
607                 column.add_attribute(textrenderer, "text", 1)
608                 yield gobject.TYPE_STRING, column
609
610         def _get_current_row(self):
611                 # @todo Not implemented yet
612                 return 0
613
614         def _refresh(self):
615                 ListWindow._refresh(self)
616                 self._index.download(
617                         "get_conference_talks",
618                         self._on_conference_talks,
619                         self._on_error,
620                         self._sessionId,
621                 )
622
623         @misc_utils.log_exception(_moduleLogger)
624         def _on_conference_talks(self, programs):
625                 if self._isDestroyed:
626                         _moduleLogger.info("Download complete but window destroyed")
627                         return
628
629                 self._hide_loading()
630                 for program in programs:
631                         row = program["id"], "%s\n%s" % (program["title"], program["speaker"])
632                         self._model.append(row)
633
634                 path = (self._get_current_row(), )
635                 self._treeView.scroll_to_cell(path)
636                 self._treeView.get_selection().select_path(path)
637
638         @misc_utils.log_exception(_moduleLogger)
639         def _on_error(self, exception):
640                 self._hide_loading()
641                 self._errorBanner.push_message(exception)
642
643         @misc_utils.log_exception(_moduleLogger)
644         def _on_row_activated(self, view, path, column):
645                 raise NotImplementedError("")
646
647
648 gobject.type_register(ConferenceTalksWindow)