Beispiel #1
0
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)
Beispiel #2
0
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()