c10c9c6baba4424f636e028df19aef35e82bbd56
[watersofshiloah] / src / windows.py
1 # @todo Add icons to buttons/rows to indicate that the currently playing track is coming from that
2
3 import ConfigParser
4 import datetime
5 import logging
6
7 import gobject
8 import gtk
9
10 import constants
11 import hildonize
12 import util.misc as misc_utils
13
14 import banners
15 import playcontrol
16 import presenter
17
18
19 _moduleLogger = logging.getLogger(__name__)
20
21
22 class BasicWindow(gobject.GObject):
23
24         __gsignals__ = {
25                 'quit' : (
26                         gobject.SIGNAL_RUN_LAST,
27                         gobject.TYPE_NONE,
28                         (),
29                 ),
30                 'fullscreen' : (
31                         gobject.SIGNAL_RUN_LAST,
32                         gobject.TYPE_NONE,
33                         (gobject.TYPE_PYOBJECT, ),
34                 ),
35         }
36
37         def __init__(self, player, store, index):
38                 gobject.GObject.__init__(self)
39                 self._isDestroyed = False
40
41                 self._player = player
42                 self._store = store
43                 self._index = index
44
45                 self._clipboard = gtk.clipboard_get()
46                 self._windowInFullscreen = False
47
48                 self._errorBanner = banners.StackingBanner()
49
50                 self._layout = gtk.VBox()
51                 self._layout.pack_start(self._errorBanner.toplevel, False, True)
52
53                 self._window = gtk.Window()
54                 self._window.add(self._layout)
55                 self._window = hildonize.hildonize_window(self, self._window)
56
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)
61
62         @property
63         def window(self):
64                 return self._window
65
66         def save_settings(self, config, sectionName):
67                 config.add_section(sectionName)
68                 config.set(sectionName, "fullscreen", str(self._windowInFullscreen))
69
70         def load_settings(self, config, sectionName):
71                 try:
72                         self._windowInFullscreen = config.getboolean(sectionName, "fullscreen")
73                 except ConfigParser.NoSectionError, e:
74                         _moduleLogger.info(
75                                 "Settings file %s is missing section %s" % (
76                                         constants._user_settings_,
77                                         e.section,
78                                 )
79                         )
80
81                 if self._windowInFullscreen:
82                         self._window.fullscreen()
83                 else:
84                         self._window.unfullscreen()
85
86         @misc_utils.log_exception(_moduleLogger)
87         def _on_destroy(self, *args):
88                 self._isDestroyed = True
89
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
94                 else:
95                         self._windowInFullscreen = False
96                 self.emit("fullscreen", self._windowInFullscreen)
97
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)
102                 if (
103                         event.keyval == gtk.keysyms.F6 or
104                         event.keyval in RETURN_TYPES and isCtrl
105                 ):
106                         # The "Full screen" hardware key has been pressed
107                         if self._windowInFullscreen:
108                                 self._window.unfullscreen ()
109                         else:
110                                 self._window.fullscreen ()
111                         return True
112                 elif (
113                         event.keyval in (gtk.keysyms.w, ) and
114                         event.get_state() & gtk.gdk.CONTROL_MASK
115                 ):
116                         self._window.destroy()
117                 elif (
118                         event.keyval in (gtk.keysyms.q, ) and
119                         event.get_state() & gtk.gdk.CONTROL_MASK
120                 ):
121                         self.emit("quit")
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))
127                         return True
128
129
130 class SourceSelector(BasicWindow):
131
132         def __init__(self, player, store, index):
133                 self._languages = []
134
135                 BasicWindow.__init__(self, player, store, index)
136
137                 self._loadingBanner = banners.GenericBanner()
138
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)
143
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)
148
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)
153
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)
158
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)
165
166                 self._playcontrol = playcontrol.PlayControl(player, store)
167
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)
171
172                 self._window.set_title(constants.__pretty_app_name__)
173                 self._window.show_all()
174                 self._errorBanner.toplevel.hide()
175                 self._playcontrol.toplevel.hide()
176
177                 self._refresh()
178
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)
184
185         def _hide_loading(self):
186                 self._loadingBanner.hide()
187                 self._buttonLayout.set_sensitive(True)
188
189         def _refresh(self):
190                 self._show_loading()
191                 self._index.download(
192                         "get_languages",
193                         self._on_languages,
194                         self._on_error,
195                 )
196
197         def _create_button(self, icon, message):
198                 image = self._store.get_image_from_store(self._store.STORE_LOOKUP[icon])
199
200                 label = gtk.Label()
201                 label.set_text(message)
202
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)
208
209                 return button
210
211         @misc_utils.log_exception(_moduleLogger)
212         def _on_languages(self, languages):
213                 self._hide_loading()
214                 self._languages = list(languages)
215
216         @misc_utils.log_exception(_moduleLogger)
217         def _on_error(self, exception):
218                 self._hide_loading()
219                 self._errorBanner.push_message(exception)
220
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())
227
228
229 gobject.type_register(SourceSelector)
230
231
232 class RadioWindow(BasicWindow):
233
234         def __init__(self, player, store, index, languageId):
235                 BasicWindow.__init__(self, player, store, index)
236
237                 self._player.connect("state-change", self._on_player_state_change)
238                 self._player.connect("title-change", self._on_player_title_change)
239
240                 self._loadingBanner = banners.GenericBanner()
241
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)
248
249                 self._programmingModel = gtk.ListStore(
250                         gobject.TYPE_STRING,
251                         gobject.TYPE_STRING,
252                 )
253
254                 textrenderer = gtk.CellRendererText()
255                 timeColumn = gtk.TreeViewColumn("Time")
256                 timeColumn.pack_start(textrenderer, expand=True)
257                 timeColumn.add_attribute(textrenderer, "text", 0)
258
259                 textrenderer = gtk.CellRendererText()
260                 titleColumn = gtk.TreeViewColumn("Program")
261                 titleColumn.pack_start(textrenderer, expand=True)
262                 titleColumn.add_attribute(textrenderer, "text", 1)
263
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)
270
271                 self._treeScroller = gtk.ScrolledWindow()
272                 self._treeScroller.add(self._treeView)
273                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
274
275                 self._presenter = presenter.StreamMiniPresenter(self._store)
276                 if self._player.state == "play":
277                         self._presenter.set_state(self._store.STORE_LOOKUP["play"])
278                 else:
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)
284
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)
289
290                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
291                 self._layout.pack_start(self._radioLayout, True, True)
292
293                 self._window.set_title("Radio")
294                 self._window.show_all()
295                 self._errorBanner.toplevel.hide()
296                 self._loadingBanner.toplevel.hide()
297
298                 self._dateShown = datetime.datetime.now()
299                 self._refresh()
300
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...")
305
306         def _hide_loading(self):
307                 self._loadingBanner.hide()
308
309         def _refresh(self):
310                 self._show_loading()
311                 self._programmingModel.clear()
312                 self._index.download(
313                         "get_radio_channels",
314                         self._on_channels,
315                         self._on_load_error,
316                 )
317
318         def _get_current_row(self):
319                 nowTime = self._dateShown.strftime("%H:%M:%S")
320                 i = 0
321                 for i, row in enumerate(self._programmingModel):
322                         if nowTime < row[0]:
323                                 if i == 0:
324                                         return 0
325                                 else:
326                                         return i - 1
327                 else:
328                         return i
329
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():
333                         return
334
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"])
339                 else:
340                         _moduleLogger.info("Unhandled player state %s" % newState)
341                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
342
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)
346                 self._destroy()
347
348         @misc_utils.log_exception(_moduleLogger)
349         def _on_navigating(self, widget, navState):
350                 if navState == "clicking":
351                         if self._player.state == "play":
352                                 imageName = "pause"
353                         else:
354                                 imageName = "play"
355                 elif navState == "down":
356                         imageName = "home"
357                 elif navState == "up":
358                         imageName = "play"
359                 elif navState == "left":
360                         imageName = "play"
361                 elif navState == "right":
362                         imageName = "play"
363
364                 self._presenter.set_state(self._store.STORE_LOOKUP[imageName])
365
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"])
370                 else:
371                         self._presenter.set_state(self._store.STORE_LOOKUP["pause"])
372
373                 if navState == "clicking":
374                         if self._player.state == "play":
375                                 self._player.pause()
376                         else:
377                                 self._player.play()
378                 elif navState == "down":
379                         self.window.destroy()
380                 elif navState == "up":
381                         pass
382                 elif navState == "left":
383                         self._dateShown += datetime.timedelta(days=1)
384                         self._refresh()
385                 elif navState == "right":
386                         self._dateShown -= datetime.timedelta(days=1)
387                         self._refresh()
388
389         @misc_utils.log_exception(_moduleLogger)
390         def _on_channels(self, channels):
391                 if self._isDestroyed:
392                         _moduleLogger.info("Download complete but window destroyed")
393                         return
394
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",
401                         self._on_channel,
402                         self._on_load_error,
403                         channel["id"],
404                         self._dateShown,
405                 )
406
407         @misc_utils.log_exception(_moduleLogger)
408         def _on_channel(self, programs):
409                 if self._isDestroyed:
410                         _moduleLogger.info("Download complete but window destroyed")
411                         return
412
413                 self._hide_loading()
414                 for program in programs:
415                         row = program["time"], program["title"]
416                         self._programmingModel.append(row)
417
418                 path = (self._get_current_row(), )
419                 self._treeView.scroll_to_cell(path)
420                 self._treeView.get_selection().select_path(path)
421
422         @misc_utils.log_exception(_moduleLogger)
423         def _on_load_error(self, exception):
424                 self._hide_loading()
425                 self._errorBanner.push_message(exception)
426
427         @misc_utils.log_exception(_moduleLogger)
428         def _on_row_changed(self, selection):
429                 if len(self._programmingModel) == 0:
430                         return
431
432                 rowIndex = self._get_current_row()
433                 path = (rowIndex, )
434                 if not selection.path_is_selected(path):
435                         # Undo the user's changing of the selection
436                         selection.select_path(path)
437
438
439 gobject.type_register(RadioWindow)
440
441
442 class ListWindow(BasicWindow):
443
444         def __init__(self, player, store, index):
445                 BasicWindow.__init__(self, player, store, index)
446
447                 self._loadingBanner = banners.GenericBanner()
448
449                 modelTypes, columns = zip(*self._get_columns())
450
451                 self._model = gtk.ListStore(*modelTypes)
452
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)
460
461                 self._treeScroller = gtk.ScrolledWindow()
462                 self._treeScroller.add(self._treeView)
463                 self._treeScroller.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
464
465                 self._playcontrol = playcontrol.PlayControl(self._player, self._store)
466
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)
470
471                 self._layout.pack_start(self._loadingBanner.toplevel, False, False)
472                 self._layout.pack_start(self._contentLayout, True, True)
473
474                 self._window.show_all()
475                 self._errorBanner.toplevel.hide()
476                 self._loadingBanner.toplevel.hide()
477
478                 self._refresh()
479                 self._playcontrol.refresh()
480
481         @classmethod
482         def _get_columns(cls):
483                 raise NotImplementedError("")
484
485         def _get_current_row(self):
486                 raise NotImplementedError("")
487
488         @misc_utils.log_exception(_moduleLogger)
489         def _on_row_activated(self, view, path, column):
490                 raise NotImplementedError("")
491
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...")
496
497         def _hide_loading(self):
498                 self._loadingBanner.hide()
499
500         def _refresh(self):
501                 self._show_loading()
502                 self._model.clear()
503
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)
508
509
510 class ConferencesWindow(ListWindow):
511
512         def __init__(self, player, store, index, languageId):
513                 self._languageId = languageId
514
515                 ListWindow.__init__(self, player, store, index)
516                 self._window.set_title("Conferences")
517
518         @classmethod
519         def _get_columns(cls):
520                 yield gobject.TYPE_STRING, None
521
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
527
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
533
534         def _get_current_row(self):
535                 # @todo Not implemented yet
536                 return 0
537
538         def _refresh(self):
539                 ListWindow._refresh(self)
540                 self._index.download(
541                         "get_conferences",
542                         self._on_conferences,
543                         self._on_error,
544                         self._languageId,
545                 )
546
547         @misc_utils.log_exception(_moduleLogger)
548         def _on_conferences(self, programs):
549                 if self._isDestroyed:
550                         _moduleLogger.info("Download complete but window destroyed")
551                         return
552
553                 self._hide_loading()
554                 for program in programs:
555                         row = program["id"], program["title"], program["full_title"]
556                         self._model.append(row)
557
558                 path = (self._get_current_row(), )
559                 self._treeView.scroll_to_cell(path)
560                 self._treeView.get_selection().select_path(path)
561
562         @misc_utils.log_exception(_moduleLogger)
563         def _on_error(self, exception):
564                 self._hide_loading()
565                 self._errorBanner.push_message(exception)
566
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)
571
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())
576
577
578 gobject.type_register(ConferencesWindow)
579
580
581 class ConferenceSessionsWindow(ListWindow):
582
583         def __init__(self, player, store, index, conferenceId):
584                 self._conferenceId = conferenceId
585
586                 ListWindow.__init__(self, player, store, index)
587                 self._window.set_title("Sessions")
588
589         @classmethod
590         def _get_columns(cls):
591                 yield gobject.TYPE_STRING, None
592
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
598
599         def _get_current_row(self):
600                 # @todo Not implemented yet
601                 return 0
602
603         def _refresh(self):
604                 ListWindow._refresh(self)
605                 self._index.download(
606                         "get_conference_sessions",
607                         self._on_conference_sessions,
608                         self._on_error,
609                         self._conferenceId,
610                 )
611
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")
616                         return
617
618                 self._hide_loading()
619                 for program in programs:
620                         row = program["id"], program["title"]
621                         self._model.append(row)
622
623                 path = (self._get_current_row(), )
624                 self._treeView.scroll_to_cell(path)
625                 self._treeView.get_selection().select_path(path)
626
627         @misc_utils.log_exception(_moduleLogger)
628         def _on_error(self, exception):
629                 self._hide_loading()
630                 self._errorBanner.push_message(exception)
631
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)
636
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())
641
642
643 gobject.type_register(ConferenceSessionsWindow)
644
645
646 class ConferenceTalksWindow(ListWindow):
647
648         def __init__(self, player, store, index, sessionId):
649                 self._sessionId = sessionId
650
651                 ListWindow.__init__(self, player, store, index)
652                 self._window.set_title("Talks")
653
654         @classmethod
655         def _get_columns(cls):
656                 yield gobject.TYPE_STRING, None
657
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
663
664         def _get_current_row(self):
665                 # @todo Not implemented yet
666                 return 0
667
668         def _refresh(self):
669                 ListWindow._refresh(self)
670                 self._index.download(
671                         "get_conference_talks",
672                         self._on_conference_talks,
673                         self._on_error,
674                         self._sessionId,
675                 )
676
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")
681                         return
682
683                 self._hide_loading()
684                 for program in programs:
685                         row = program["id"], "%s\n%s" % (program["title"], program["speaker"])
686                         self._model.append(row)
687
688                 path = (self._get_current_row(), )
689                 self._treeView.scroll_to_cell(path)
690                 self._treeView.get_selection().select_path(path)
691
692         @misc_utils.log_exception(_moduleLogger)
693         def _on_error(self, exception):
694                 self._hide_loading()
695                 self._errorBanner.push_message(exception)
696
697         @misc_utils.log_exception(_moduleLogger)
698         def _on_row_activated(self, view, path, column):
699                 raise NotImplementedError("")
700
701
702 gobject.type_register(ConferenceTalksWindow)