class MainView(Gtk.Box): __gtype_name__ = 'MainView' __gsignals__ = { 'error': (GObject.SignalFlags.RUN_FIRST, None, (str, )), 'error-fatal': (GObject.SignalFlags.RUN_FIRST, None, (str, )), 'show-action': (GObject.SignalFlags.RUN_FIRST, None, (int, object)), } image_container_box = GtkTemplate.Child() top_box = GtkTemplate.Child() show_title = GtkTemplate.Child() api_icon = GtkTemplate.Child() api_user = GtkTemplate.Child() btn_episode_remove = GtkTemplate.Child() btn_episode_show_entry = GtkTemplate.Child() entry_episode = GtkTemplate.Child() btn_episode_add = GtkTemplate.Child() btn_play_next = GtkTemplate.Child() spinbtn_score = GtkTemplate.Child() btn_score_set = GtkTemplate.Child() statusbox = GtkTemplate.Child() statusmodel = GtkTemplate.Child() notebook = GtkTemplate.Child() def __init__(self, config, debug=False): Gtk.Box.__init__(self) self.init_template() self._configfile = utils.to_config_path('ui-Gtk.json') self._config = config self._engine = None self._account = None self._debug = debug self._image_thread = None self._current_page = None self.statusbox_handler = None self.notebook_switch_handler = None self._pages = {} self._page_handler_ids = {} self._init_widgets() self._init_signals() def load_engine_account(self, engine, account): self._engine = engine self._account = account self._engine_start() self._init_signals_engine() def load_account_mediatype(self, account, mediatype): if account: self._account = account self._engine_reload(account, mediatype) def _init_widgets(self): self.image_box = ImageBox(100, 150) self.image_box.show() self.image_container_box.pack_start(self.image_box, False, False, 0) self.statusbar = Gtk.Statusbar() self.statusbar.push(0, 'Trackma-gtk ' + utils.VERSION) self.statusbar.show() self.pack_start(self.statusbar, False, False, 0) def _init_signals(self): self.btn_episode_remove.connect("clicked", self._on_btn_episode_remove_clicked) self.btn_episode_show_entry.connect("clicked", self._show_episode_entry) self.entry_episode.connect("activate", self._on_entry_episode_activate) self.entry_episode.connect("focus-out-event", self._hide_episode_entry) self.btn_episode_add.connect("clicked", self._on_btn_episode_add_clicked) self.btn_play_next.connect("clicked", self._on_btn_play_next_clicked, True) self.spinbtn_score.connect("activate", self._on_spinbtn_score_activate) self.btn_score_set.connect("clicked", self._on_spinbtn_score_activate) self.statusbox_handler = self.statusbox.connect( "changed", self._on_statusbox_changed) self.notebook_switch_handler = self.notebook.connect( "switch-page", self._on_switch_notebook_page) def _init_signals_engine(self): self._engine.connect_signal('episode_changed', self._on_changed_show_idle) self._engine.connect_signal('score_changed', self._on_changed_show_idle) self._engine.connect_signal('status_changed', self._on_changed_show_status_idle) self._engine.connect_signal('playing', self._on_playing_show_idle) self._engine.connect_signal('show_added', self._on_changed_show_status_idle) self._engine.connect_signal('show_deleted', self._on_changed_show_status_idle) self._engine.connect_signal('prompt_for_update', self._on_prompt_update_next_idle) def _engine_start(self): threading.Thread(target=self._engine_start_task).start() def _engine_start_task(self): if self._engine.loaded: return try: self._engine.start() except utils.TrackmaFatal as e: self.emit('error-fatal', e) return GLib.idle_add(self._update_widgets) def _engine_reload(self, account, mediatype): self.set_buttons_sensitive(False) threading.Thread(target=self._engine_reload_task, args=[account, mediatype]).start() def _engine_reload_task(self, account, mediatype): try: self._engine.reload(account, mediatype) except utils.TrackmaError as e: self.emit('error', e) except utils.TrackmaFatal as e: self.emit('error-fatal', e) return GLib.idle_add(self._update_widgets) def _update_widgets(self): self.statusbox.handler_block(self.statusbox_handler) self._reset_widgets() self._create_notebook_pages() self._set_score_ranges() self.populate_all_pages() self._populate_statusbox() self.statusbox.handler_unblock(self.statusbox_handler) self.set_status_idle("Ready.") self.set_buttons_sensitive_idle(True) def _reset_widgets(self): self.show_title.set_text('<span size="14000"><b>Trackma</b></span>') self.show_title.set_use_markup(True) self.image_box.reset() current_api = utils.available_libs[self._account['api']] api_iconfile = current_api[1] self.api_icon.set_from_file(api_iconfile) self.api_user.set_text("%s (%s)" % (self._engine.get_userconfig('username'), self._engine.api_info['mediatype'])) can_play = self._engine.mediainfo['can_play'] can_update = self._engine.mediainfo['can_update'] self.btn_play_next.set_sensitive(can_play) self.btn_episode_show_entry.set_sensitive(can_update) self.entry_episode.set_sensitive(can_update) self.btn_episode_add.set_sensitive(can_update) def _create_notebook_pages(self): statuses_nums = self._engine.mediainfo['statuses'] statuses_names = self._engine.mediainfo['statuses_dict'] self.notebook.handler_block(self.notebook_switch_handler) # Clear notebook for i in range(self.notebook.get_n_pages()): self.notebook.remove_page(-1) self._pages = {} self._page_handler_ids = {} # Insert pages for status in statuses_nums: self._pages[status] = NotebookPage(self._engine, self.notebook.get_n_pages(), status, self._config) self._page_handler_ids[status] = [] self._page_handler_ids[status].append(self._pages[status].connect( 'show-selected', self._on_show_selected)) self._page_handler_ids[status].append(self._pages[status].connect( 'show-action', self._on_show_action)) self._page_handler_ids[status].append(self._pages[status].connect( 'column-toggled', self._on_column_toggled)) self.notebook.append_page(self._pages[status], Gtk.Label(statuses_names[status])) self.notebook.handler_unblock(self.notebook_switch_handler) self.notebook.show_all() def populate_all_pages(self): for status in self._pages: self.populate_page(status) def populate_page(self, status): self._block_handlers_for_status(status) tree_view = self._pages[status].show_tree_view tree_view.append_start() library = self._engine.library() for show in self._engine.filter_list(tree_view.status_filter): tree_view.append(show, self._engine.altname(show['id']), library.get(show['id'])) tree_view.append_finish() self._unblock_handlers_for_status(status) def _block_handlers_for_status(self, status): for handler_id in self._page_handler_ids[status]: self._pages[status].handler_block(handler_id) def _unblock_handlers_for_status(self, status): for handler_id in self._page_handler_ids[status]: self._pages[status].handler_unblock(handler_id) def _populate_statusbox(self): statuses_nums = self._engine.mediainfo['statuses'] statuses_names = self._engine.mediainfo['statuses_dict'] self.statusmodel.clear() for status in statuses_nums: self.statusmodel.append([str(status), statuses_names[status]]) self.statusbox.set_model(self.statusmodel) self.statusbox.show_all() def _set_score_ranges(self): score_decimal_places = 0 if isinstance(self._engine.mediainfo['score_step'], float): score_decimal_places = len( str(self._engine.mediainfo['score_step']).split('.')[1]) self.spinbtn_score.set_value(0) self.spinbtn_score.set_digits(score_decimal_places) self.spinbtn_score.set_range(0, self._engine.mediainfo['score_max']) self.spinbtn_score.get_adjustment().set_step_increment( self._engine.mediainfo['score_step']) for view in self._pages.values(): view.decimals = score_decimal_places def set_status_idle(self, msg): # Thread safe GLib.idle_add(self._set_status, msg) def _set_status(self, msg): print(msg) self.statusbar.push(0, msg) def set_buttons_sensitive_idle(self, boolean): # Thread safe GLib.idle_add(self.set_buttons_sensitive, boolean) def set_buttons_sensitive(self, boolean, lists_too=True): if lists_too: self.notebook.set_sensitive(boolean) if self._current_page.selected_show or not boolean: if self._engine.mediainfo['can_play']: self.btn_play_next.set_sensitive(boolean) if self._engine.mediainfo['can_update']: self.btn_episode_show_entry.set_sensitive(boolean) self.entry_episode.set_sensitive(boolean) self.btn_episode_add.set_sensitive(boolean) self.btn_episode_remove.set_sensitive(boolean) self.btn_score_set.set_sensitive(boolean) self.spinbtn_score.set_sensitive(boolean) self.statusbox.set_sensitive(boolean) def _on_btn_episode_remove_clicked(self, widget): self.emit('show-action', ShowEventType.EPISODE_REMOVE, (self._current_page.selected_show, )) def _show_episode_entry(self, *args): self.btn_episode_show_entry.hide() self.entry_episode.set_text(self.btn_episode_show_entry.get_label()) self.entry_episode.show() self.entry_episode.grab_focus() def _on_entry_episode_activate(self, widget): try: episode = int(self.entry_episode.get_text()) self.emit('show-action', ShowEventType.EPISODE_SET, (self._current_page.selected_show, episode)) except ValueError: pass def _hide_episode_entry(self, *args): self.entry_episode.hide() self.btn_episode_show_entry.show() def _on_btn_episode_add_clicked(self, widget): self.emit('show-action', ShowEventType.EPISODE_ADD, (self._current_page.selected_show, )) def _on_btn_play_next_clicked(self, widget, playnext, ep=None): self.emit('show-action', ShowEventType.PLAY_NEXT, (self._current_page.selected_show, )) def _on_spinbtn_score_activate(self, widget): score = self.spinbtn_score.get_value() self.emit('show-action', ShowEventType.SET_SCORE, (self._current_page.selected_show, score)) def _on_statusbox_changed(self, widget): statusiter = self.statusbox.get_active_iter() status = self.statusmodel.get(statusiter, 0)[0] self.emit('show-action', ShowEventType.SET_STATUS, (self._current_page.selected_show, status)) def message_handler(self, classname, msgtype, msg): # Thread safe if msgtype == messenger.TYPE_WARN: self.set_status_idle("%s warning: %s" % (classname, msg)) elif msgtype != messenger.TYPE_DEBUG: self.set_status_idle("%s: %s" % (classname, msg)) elif self._debug: print('[D] {}: {}'.format(classname, msg)) def _on_changed_show_idle(self, show): GLib.idle_add(self._update_show, show) def _update_show(self, show): status = show['my_status'] self._pages[status].show_tree_view.update(show) if show['id'] == self._current_page.selected_show: self.btn_episode_show_entry.set_label(str(show['my_progress'])) self.spinbtn_score.set_value(show['my_score']) def change_show_title_idle(self, show, altname): GLib.idle_add(self._update_show_title, show, altname) def _update_show_title(self, show, altname): status = show['my_status'] self._pages[status].show_tree_view.update_title(show, altname) def _on_changed_show_status_idle(self, show, old_status=None): GLib.idle_add(self._update_show_status, show, old_status) def _update_show_status(self, show, old_status): # Rebuild lists status = show['my_status'] self.populate_page(status) if old_status: self.populate_page(old_status) pagenumber = self._pages[status].pagenumber self.notebook.set_current_page(pagenumber) self._pages[status].show_tree_view.select(show) def _on_playing_show_idle(self, show, is_playing, episode): GLib.idle_add(self._set_show_playing, show, is_playing, episode) def _set_show_playing(self, show, is_playing, episode): status = show['my_status'] self._pages[status].show_tree_view.playing(show, is_playing) def _on_prompt_update_next_idle(self, show, played_ep): GLib.idle_add(self._prompt_update_next, show, played_ep) def _prompt_update_next(self, show, played_ep): dialog = Gtk.MessageDialog( self.get_toplevel(), Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "Update %s to episode %d?" % (show['title'], played_ep)) dialog.show_all() dialog.connect("response", self._on_response_update_next, show, played_ep) def _on_response_update_next(self, widget, response, show, played_ep): widget.destroy() if response == Gtk.ResponseType.YES: self.emit('show-action', ShowEventType.EPISODE_SET, (show['id'], played_ep)) def _on_switch_notebook_page(self, notebook, page, page_num): self._current_page = page self._update_widgets_for_selected_show() def _on_show_selected(self, page, selected_show): self._update_widgets_for_selected_show() def _update_widgets_for_selected_show(self): if not self._current_page.selected_show: self.set_buttons_sensitive(False, lists_too=False) return self.set_buttons_sensitive(True, lists_too=False) show = self._engine.get_show_info(self._current_page.selected_show) # Block handlers self.statusbox.handler_block(self.statusbox_handler) if self._image_thread is not None: self._image_thread.cancel() self.show_title.set_text('<span size="14000"><b>{0}</b></span>'.format( html.escape(show['title']))) self.show_title.set_use_markup(True) # Episode selector self.btn_episode_show_entry.set_label(str(show['my_progress'])) self._hide_episode_entry() # Status selector for i in self.statusmodel: if str(i[0]) == str(show['my_status']): self.statusbox.set_active_iter(i.iter) break # Score selector self.spinbtn_score.set_value(show['my_score']) # Image if show.get('image_thumb') or show.get('image'): utils.make_dir(utils.to_cache_path()) filename = utils.to_cache_path( "%s_%s_%s.jpg" % (self._engine.api_info['shortname'], self._engine.api_info['mediatype'], show['id'])) if os.path.isfile(filename): self.image_box.set_image(filename) else: self.image_box.set_image_remote( show.get('image_thumb') or show['image'], filename) else: self.image_box.set_text('No Image') # Unblock handlers self.statusbox.handler_unblock(self.statusbox_handler) def _on_show_action(self, page, event_type, data): self.emit('show-action', event_type, data) def get_current_status(self): return self._current_page.status def get_selected_show(self): if not self._current_page: return None return self._current_page.selected_show def _on_column_toggled(self, page, column_name, visible): if visible: # Make column visible self._config['visible_columns'].append(column_name) else: # Make column invisible if len(self._config['visible_columns']) <= 1: return # There should be at least 1 column visible self._config['visible_columns'].remove(column_name) for page in self._pages.values(): page.set_column_visible(column_name, visible) utils.save_config(self._config, self._configfile)
class ShowInfoBox(Gtk.Box): __gtype_name__ = 'ShowInfoBox' label_title = GtkTemplate.Child() data_container = GtkTemplate.Child() image_container = GtkTemplate.Child() def __init__(self, engine, orientation=Gtk.Orientation.HORIZONTAL): Gtk.Box.__init__(self) self.init_template() self._engine = engine self._show = None self.image_thread = None self.details = None self.details_e = None self.image_box = ImageBox(225, 300) self.image_box.show() self.image_container.pack_start(self.image_box, False, False, 0) self.data_label = Gtk.Label('') self.data_label.set_line_wrap(True) self.data_label.set_property('selectable', True) if isinstance(orientation, Gtk.Orientation): self.data_container.set_orientation(orientation) self.data_container.pack_start(self.data_label, True, True, 0) def set_size(self, w, h): self.scrolled_sidebox.set_size_request(w, h) def load(self, show): self._show = show # Load image if show.get('image'): imagefile = utils.to_cache_path( "%s_%s_f_%s.jpg" % (self._engine.api_info['shortname'], self._engine.api_info['mediatype'], show['id'])) if os.path.isfile(imagefile): self.image_box.set_image(imagefile) else: self.image_box.set_image_remote(show['image'], imagefile) else: self.image_box.set_text('No Image') # Start info loading thread threading.Thread(target=self._show_load_start_task).start() def _show_load_start_task(self): # Thread to ask the engine for show details try: self.details = self._engine.get_show_details(self._show) except utils.TrackmaError as e: self.details = None self.details_e = e GObject.idle_add(self._show_load_finish_idle) def _show_load_finish_idle(self): if self.details: # Put the returned details into the lines VBox self.label_title.set_text(html.escape(self.details['title'])) detail = list() for line in self.details['extra']: if line[0] and line[1]: title, content, *_ = line if isinstance(content, list): content = ", ".join(filter(None, content)) detail.append( "<b>%s</b>\n%s" % (html.escape(str(title)), html.escape(str(content)))) self.data_label.set_text("\n\n".join(detail)) self.data_label.set_use_markup(True) else: self.label_title.set_text('Error while getting details.') if self.details_e: self.data_label.set_text(str(self.details_e)) self.label_title.show() self.data_label.show()