示例#1
0
    def test_signal_count(self):
        m = ObjectStore()

        def handler(model, path, iter_, result):
            result[0] += 1

        inserted = [0]
        m.connect("row-inserted", handler, inserted)
        changed = [0]
        m.connect("row-changed", handler, changed)

        m.append([1])
        m.prepend([8])
        m.insert(0, [1])
        m.insert_before(None, [1])
        m.insert_after(None, [1])
        m.insert_many(0, [1, 2, 3])
        m.append_many([1, 2, 3])
        list(m.iter_append_many([1, 2, 3]))
        list(m.iter_append_many(xrange(3)))

        self.assertEqual(changed[0], 0)
        self.assertEqual(inserted[0], len(m))
    def test_signal_count(self):
        m = ObjectStore()

        def handler(model, path, iter_, result):
            result[0] += 1

        inserted = [0]
        m.connect("row-inserted", handler, inserted)
        changed = [0]
        m.connect("row-changed", handler, changed)

        m.append([1])
        m.prepend([8])
        m.insert(0, [1])
        m.insert_before(None, [1])
        m.insert_after(None, [1])
        m.insert_many(0, [1, 2, 3])
        m.append_many([1, 2, 3])
        list(m.iter_append_many([1, 2, 3]))
        list(m.iter_append_many(xrange(3)))

        self.assertEqual(changed[0], 0)
        self.assertEqual(inserted[0], len(m))
示例#3
0
class SearchWindow(Dialog):
    def __init__(self, parent, album):
        self.album = album
        self.album.sort(key=lambda s: sort_key(s))

        self._resultlist = ObjectStore()
        self._releasecache = {}
        self._qthread = QueryThread()
        self.current_release = None

        super(SearchWindow, self).__init__(_("MusicBrainz lookup"))

        self.add_button(_("_Cancel"), Gtk.ResponseType.REJECT)
        self.add_icon_button(_("_Save"), Icons.DOCUMENT_SAVE,
                             Gtk.ResponseType.ACCEPT)

        self.set_default_size(650, 500)
        self.set_border_width(5)
        self.set_transient_for(parent)

        save_button = self.get_widget_for_response(Gtk.ResponseType.ACCEPT)
        save_button.set_sensitive(False)

        vb = Gtk.VBox()
        vb.set_spacing(8)

        hb = Gtk.HBox()
        hb.set_spacing(8)
        sq = self.search_query = Gtk.Entry()
        sq.connect('activate', self._do_query)

        sq.set_text(build_query(album))

        lbl = Gtk.Label(label=_("_Query:"))
        lbl.set_use_underline(True)
        lbl.set_mnemonic_widget(sq)
        stb = self.search_button = Gtk.Button(_('S_earch'), use_underline=True)
        stb.connect('clicked', self._do_query)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(sq, True, True, 0)
        hb.pack_start(stb, False, True, 0)
        vb.pack_start(hb, False, True, 0)

        self.result_combo = ResultComboBox(self._resultlist)
        self.result_combo.connect('changed', self._result_changed)
        vb.pack_start(self.result_combo, False, True, 0)

        rhb = Gtk.HBox()
        rl = Gtk.Label()
        rl.set_markup(_("Results <i>(drag to reorder)</i>"))
        rl.set_alignment(0, 0.5)
        rhb.pack_start(rl, False, True, 0)
        rl = self.result_label = Gtk.Label(label="")
        rhb.pack_end(rl, False, True, 0)
        vb.pack_start(rhb, False, True, 0)
        sw = Gtk.ScrolledWindow()
        sw.set_shadow_type(Gtk.ShadowType.IN)
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
        rtv = self.result_treeview = ResultTreeView(self.album)
        rtv.set_border_width(8)
        sw.add(rtv)
        vb.pack_start(sw, True, True, 0)

        self.get_action_area().set_border_width(4)
        self.get_content_area().pack_start(vb, True, True, 0)
        self.connect('response', self._on_response)
        self.connect("destroy", self._on_destroy)

        stb.emit('clicked')
        self.get_child().show_all()

    def _on_destroy(self, *args):
        self._qthread.stop()

    def _on_response(self, widget, response):
        if response != Gtk.ResponseType.ACCEPT:
            self.destroy()
            return

        self._save()

    def _save(self):
        """Writes values to Song objects."""

        year_only = pconfig.getboolean("year_only")
        albumartist = pconfig.getboolean("albumartist")
        artistsort = pconfig.getboolean("artist_sort")
        musicbrainz = pconfig.getboolean("standard")
        labelid = pconfig.getboolean("labelid2")

        for release, track, song in self.result_treeview.iter_tracks():
            meta = build_song_data(release, track)
            apply_options(meta, year_only, albumartist, artistsort,
                          musicbrainz, labelid)
            apply_to_song(meta, song)

        self.destroy()

    def _do_query(self, *args):
        """Search for album using the query text."""

        query = util.gdecode(self.search_query.get_text())

        if not query:
            self.result_label.set_markup("<b>%s</b>" %
                                         _("Please enter a query."))
            self.search_button.set_sensitive(True)
            return

        self.result_label.set_markup("<i>%s</i>" % _(u"Searching…"))

        self._qthread.add(self._process_results, search_releases, query)

    def _process_results(self, results):
        """Called when a query result is returned.

        `results` is None if an error occurred.
        """

        self._resultlist.clear()
        self.search_button.set_sensitive(True)

        if results is None:
            self.result_label.set_text(_("Error encountered. Please retry."))
            self.search_button.set_sensitive(True)
            return

        self._resultlist.append_many(results)

        if len(results) > 0:
            self.result_label.set_markup("<i>%s</i>" % _(u"Loading result…"))
            self.result_combo.set_active(0)
        else:
            self.result_label.set_markup(_("No results found."))

    def _result_changed(self, combo):
        """Called when a release is chosen from the result combo."""

        idx = combo.get_active()
        if idx == -1:
            return
        release = self._resultlist[idx][0]

        if release.id in self._releasecache:
            self._update_result(self._releasecache[release.id])
        else:
            self.result_label.set_markup("<i>%s</i>" % _(u"Loading result…"))
            self.result_treeview.update_release(None)
            self._qthread.add(self._update_result, release.fetch_full)

    def _update_result(self, full_release):
        """Callback for release detail download from result combo."""

        if full_release is None:
            self.result_label.set_text(_("Error encountered. Please retry."))
            return

        self.result_label.set_text(u"")
        self._releasecache.setdefault(full_release.id, full_release)

        self.result_treeview.update_release(full_release)
        self.current_release = full_release
        save_button = self.get_widget_for_response(Gtk.ResponseType.ACCEPT)
        save_button.set_sensitive(True)
示例#4
0
class ResultTreeView(HintedTreeView, MultiDragTreeView):
    """The result treeview"""
    def __init__(self, album):
        self.album = album
        self._release = None
        self.model = ObjectStore()
        self.model.append_many(album)

        super(ResultTreeView, self).__init__(self.model)
        self.set_headers_clickable(True)
        self.set_rules_hint(True)
        self.set_reorderable(True)
        self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)

        mode = Pango.EllipsizeMode
        cols = [
            (_('Filename'), self.__name_datafunc, True, mode.MIDDLE),
            (_('Disc'), self.__disc_datafunc, False, mode.END),
            (_('Track'), self.__track_datafunc, False, mode.END),
            (_('Title'), self.__title_datafunc, True, mode.END),
            (_('Artist'), self.__artist_datafunc, True, mode.END),
        ]

        for title, func, resize, mode in cols:
            render = Gtk.CellRendererText()
            render.set_property('ellipsize', mode)
            col = Gtk.TreeViewColumn(title, render)
            col.set_cell_data_func(render, func)
            col.set_resizable(resize)
            col.set_expand(resize)
            self.append_column(col)

    def iter_tracks(self):
        """Yields tuples of (release, track, song) combinations as they
        are shown in the list.
        """

        tracks = self._tracks
        for idx, (song, ) in enumerate(self.model):
            if song is None:
                continue
            if idx >= len(tracks):
                continue
            track = tracks[idx]
            yield (self._release, track, song)

    def update_release(self, full_release):
        """Updates the TreeView, handling results with a different number of
        tracks than the album being tagged.

        Passing in None will reset the list.
        """

        if full_release is not None:
            tracks = full_release.tracks
        else:
            tracks = []

        for i in range(len(self.model), len(tracks)):
            self.model.append((None, ))
        for i in range(len(self.model), len(tracks), -1):
            if self.model[-1][0] is not None:
                break
            itr = self.model.get_iter_from_string(str(len(self.model) - 1))
            self.model.remove(itr)

        self._release = full_release

        for row in self.model:
            self.model.row_changed(row.path, row.iter)

        # Only show artists if we have any
        has_artists = bool(filter(lambda t: t.artists, tracks))
        col = self.get_column(4)
        col.set_visible(has_artists)

        # Only show discs column if we have more than one disc
        col = self.get_column(1)
        col.set_visible(
            bool(full_release) and bool(full_release.disc_count > 1))

        self.columns_autosize()

    @property
    def _tracks(self):
        if self._release is None:
            return []
        return self._release.tracks

    def __name_datafunc(self, col, cell, model, itr, data):
        song = model[itr][0]
        if song:
            cell.set_property('text', fsn2text(song("~basename")))
        else:
            cell.set_property('text', '')

    def __track_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].tracknumber)

    def __disc_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].discnumber)

    def __title_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].title)

    def __artist_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            names = [a.name for a in self._tracks[idx].artists]
            cell.set_property('text', ", ".join(names))
示例#5
0
class SearchWindow(Dialog):

    def __init__(self, parent, album):
        self.album = album
        self.album.sort(key=lambda s: sort_key(s))

        self._resultlist = ObjectStore()
        self._releasecache = {}
        self._qthread = QueryThread()
        self.current_release = None

        super(SearchWindow, self).__init__(_("MusicBrainz lookup"))

        self.add_button(_("_Cancel"), Gtk.ResponseType.REJECT)
        self.add_icon_button(_("_Save"), Icons.DOCUMENT_SAVE,
                             Gtk.ResponseType.ACCEPT)

        self.set_default_size(650, 500)
        self.set_border_width(5)
        self.set_transient_for(parent)

        save_button = self.get_widget_for_response(Gtk.ResponseType.ACCEPT)
        save_button.set_sensitive(False)

        vb = Gtk.VBox()
        vb.set_spacing(8)

        hb = Gtk.HBox()
        hb.set_spacing(8)
        sq = self.search_query = Gtk.Entry()
        sq.connect('activate', self._do_query)

        sq.set_text(build_query(album))

        lbl = Gtk.Label(label=_("_Query:"))
        lbl.set_use_underline(True)
        lbl.set_mnemonic_widget(sq)
        stb = self.search_button = Gtk.Button(_('S_earch'), use_underline=True)
        stb.connect('clicked', self._do_query)
        hb.pack_start(lbl, False, True, 0)
        hb.pack_start(sq, True, True, 0)
        hb.pack_start(stb, False, True, 0)
        vb.pack_start(hb, False, True, 0)

        self.result_combo = ResultComboBox(self._resultlist)
        self.result_combo.connect('changed', self._result_changed)
        vb.pack_start(self.result_combo, False, True, 0)

        rhb = Gtk.HBox()
        rl = Gtk.Label()
        rl.set_markup(_("Results <i>(drag to reorder)</i>"))
        rl.set_alignment(0, 0.5)
        rhb.pack_start(rl, False, True, 0)
        rl = self.result_label = Gtk.Label(label="")
        rhb.pack_end(rl, False, True, 0)
        vb.pack_start(rhb, False, True, 0)
        sw = Gtk.ScrolledWindow()
        sw.set_shadow_type(Gtk.ShadowType.IN)
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.ALWAYS)
        rtv = self.result_treeview = ResultTreeView(self.album)
        rtv.set_border_width(8)
        sw.add(rtv)
        vb.pack_start(sw, True, True, 0)

        self.get_action_area().set_border_width(4)
        self.get_content_area().pack_start(vb, True, True, 0)
        self.connect('response', self._on_response)
        self.connect("destroy", self._on_destroy)

        stb.emit('clicked')
        self.get_child().show_all()

    def _on_destroy(self, *args):
        self._qthread.stop()

    def _on_response(self, widget, response):
        if response != Gtk.ResponseType.ACCEPT:
            self.destroy()
            return

        self._save()

    def _save(self):
        """Writes values to Song objects."""

        year_only = pconfig.getboolean("year_only")
        albumartist = pconfig.getboolean("albumartist")
        artistsort = pconfig.getboolean("artist_sort")
        musicbrainz = pconfig.getboolean("standard")
        labelid = pconfig.getboolean("labelid2")

        for release, track, song in self.result_treeview.iter_tracks():
            meta = build_song_data(release, track)
            apply_options(
                meta, year_only, albumartist, artistsort, musicbrainz, labelid)
            apply_to_song(meta, song)

        self.destroy()

    def _do_query(self, *args):
        """Search for album using the query text."""

        query = util.gdecode(self.search_query.get_text())

        if not query:
            self.result_label.set_markup(
                "<b>%s</b>" % _("Please enter a query."))
            self.search_button.set_sensitive(True)
            return

        self.result_label.set_markup("<i>%s</i>" % _(u"Searching…"))

        self._qthread.add(self._process_results, search_releases, query)

    def _process_results(self, results):
        """Called when a query result is returned.

        `results` is None if an error occurred.
        """

        self._resultlist.clear()
        self.search_button.set_sensitive(True)

        if results is None:
            self.result_label.set_text(_("Error encountered. Please retry."))
            self.search_button.set_sensitive(True)
            return

        self._resultlist.append_many(results)

        if len(results) > 0:
            self.result_label.set_markup("<i>%s</i>" % _(u"Loading result…"))
            self.result_combo.set_active(0)
        else:
            self.result_label.set_markup(_("No results found."))

    def _result_changed(self, combo):
        """Called when a release is chosen from the result combo."""

        idx = combo.get_active()
        if idx == -1:
            return
        release = self._resultlist[idx][0]

        if release.id in self._releasecache:
            self._update_result(self._releasecache[release.id])
        else:
            self.result_label.set_markup("<i>%s</i>" % _(u"Loading result…"))
            self.result_treeview.update_release(None)
            self._qthread.add(self._update_result, release.fetch_full)

    def _update_result(self, full_release):
        """Callback for release detail download from result combo."""

        if full_release is None:
            self.result_label.set_text(_("Error encountered. Please retry."))
            return

        self.result_label.set_text(u"")
        self._releasecache.setdefault(full_release.id, full_release)

        self.result_treeview.update_release(full_release)
        self.current_release = full_release
        save_button = self.get_widget_for_response(Gtk.ResponseType.ACCEPT)
        save_button.set_sensitive(True)
示例#6
0
class ResultTreeView(HintedTreeView, MultiDragTreeView):
    """The result treeview"""

    def __init__(self, album):
        self.album = album
        self._release = None
        self.model = ObjectStore()
        self.model.append_many(album)

        super(ResultTreeView, self).__init__(self.model)
        self.set_headers_clickable(True)
        self.set_rules_hint(True)
        self.set_reorderable(True)
        self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)

        mode = Pango.EllipsizeMode
        cols = [
                (_('Filename'), self.__name_datafunc, True, mode.MIDDLE),
                (_('Disc'), self.__disc_datafunc, False, mode.END),
                (_('Track'), self.__track_datafunc, False, mode.END),
                (_('Title'), self.__title_datafunc, True, mode.END),
                (_('Artist'), self.__artist_datafunc, True, mode.END),
            ]

        for title, func, resize, mode in cols:
            render = Gtk.CellRendererText()
            render.set_property('ellipsize', mode)
            col = Gtk.TreeViewColumn(title, render)
            col.set_cell_data_func(render, func)
            col.set_resizable(resize)
            col.set_expand(resize)
            self.append_column(col)

    def iter_tracks(self):
        """Yields tuples of (release, track, song) combinations as they
        are shown in the list.
        """

        tracks = self._tracks
        for idx, (song, ) in enumerate(self.model):
            if song is None:
                continue
            if idx >= len(tracks):
                continue
            track = tracks[idx]
            yield (self._release, track, song)

    def update_release(self, full_release):
        """Updates the TreeView, handling results with a different number of
        tracks than the album being tagged.

        Passing in None will reset the list.
        """

        if full_release is not None:
            tracks = full_release.tracks
        else:
            tracks = []

        for i in range(len(self.model), len(tracks)):
            self.model.append((None, ))
        for i in range(len(self.model), len(tracks), -1):
            if self.model[-1][0] is not None:
                break
            itr = self.model.get_iter_from_string(str(len(self.model) - 1))
            self.model.remove(itr)

        self._release = full_release

        for row in self.model:
            self.model.row_changed(row.path, row.iter)

        # Only show artists if we have any
        has_artists = bool(filter(lambda t: t.artists, tracks))
        col = self.get_column(4)
        col.set_visible(has_artists)

        # Only show discs column if we have more than one disc
        col = self.get_column(1)
        col.set_visible(
            bool(full_release) and bool(full_release.disc_count > 1))

        self.columns_autosize()

    @property
    def _tracks(self):
        if self._release is None:
            return []
        return self._release.tracks

    def __name_datafunc(self, col, cell, model, itr, data):
        song = model[itr][0]
        if song:
            cell.set_property('text', path.fsdecode(song("~basename")))
        else:
            cell.set_property('text', '')

    def __track_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].tracknumber)

    def __disc_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].discnumber)

    def __title_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            cell.set_property('text', self._tracks[idx].title)

    def __artist_datafunc(self, col, cell, model, itr, data):
        idx = model.get_path(itr)[0]
        if idx >= len(self._tracks):
            cell.set_property('text', '')
        else:
            names = [a.name for a in self._tracks[idx].artists]
            cell.set_property('text', ", ".join(names))
 def test_append_many_set(self):
     m = ObjectStore()
     m.append_many(set())
     m.append_many(set(range(10)))
     self.failUnlessEqual({r[0] for r in m}, set(range(10)))
 def test_append_many(self):
     m = ObjectStore()
     m.append_many(range(10))
     self.failUnlessEqual([r[0] for r in m], range(10))
示例#9
0
 def test_append_many_set(self):
     m = ObjectStore()
     m.append_many(set())
     m.append_many(set(range(10)))
     self.failUnlessEqual({r[0] for r in m}, set(range(10)))
示例#10
0
 def test_append_many(self):
     m = ObjectStore()
     m.append_many(range(10))
     self.failUnlessEqual([r[0] for r in m], range(10))
示例#11
0
class MatchListsTreeView(HintedTreeView, Generic[T]):
    _b_order: List[Optional[int]]

    def __init__(self, a_items: List[T], b_items: List[T],
                 columns: List[ColumnSpec[T]]):
        self.model = ObjectStore()
        self.model.append_many(a_items)
        self._b_items = b_items

        super().__init__(self.model)
        self.set_headers_clickable(False)
        self.set_rules_hint(True)
        self.set_reorderable(False)
        self.get_selection().set_mode(Gtk.SelectionMode.NONE)

        def show_id(col, cell, model, itr, data):
            idx = model.get_path(itr)[0]
            imp_idx = self._b_order[idx]
            num = '_' if imp_idx is None else imp_idx + 1
            cell.set_property('markup', f'<span weight="bold">{num}</span>')

        def df_for_a_items(a_attr_getter):
            def data_func(col, cell, model, itr, data):
                a_item = model[itr][0]
                text = ''
                if a_item is not None:
                    text = a_attr_getter(a_item)
                cell.set_property('text', text)

            return data_func

        def df_for_b_items(b_attr_getter):
            def data_func(col, cell, model, itr, data):
                self._set_text(model, itr, cell, b_attr_getter)

            return data_func

        for c in columns:
            self._add_col(c.title, df_for_a_items(c.cell_text_getter),
                          c.is_resizable)

        self._add_col('#', show_id, False)

        for c in columns:
            self._add_col(c.title, df_for_b_items(c.cell_text_getter),
                          c.is_resizable)

        self._b_order = []  # Initialize the backing field of b_order
        self.b_order = list(range(len(b_items)))  # Update it and rows

        self.update_b_items(b_items)

    def _add_col(self, title, func, resize):
        render = Gtk.CellRendererText()
        render.set_property('ellipsize', Pango.EllipsizeMode.END)
        col = Gtk.TreeViewColumn(title, render)
        col.set_cell_data_func(render, func)
        col.set_resizable(resize)
        col.set_expand(resize)
        self.append_column(col)

    def _set_text(self, model, itr, cell, get_attr):
        idx = model.get_path(itr)[0]
        text = ''
        if idx < len(self._b_order):
            it_idx = self._b_order[idx]
            if it_idx is not None:
                text = get_attr(self._b_items[it_idx])
        cell.set_property('text', text)

    def update_b_items(self, b_items: List[T]):
        """
        Updates the TreeView, handling results with a different number of b_items than
        there are a_items.
        """
        self._b_items = b_items

        for i in range(len(self.model), len(b_items)):
            self.model.append((None, ))

        for i in range(len(self.model), len(b_items), -1):
            if self.model[-1][0] is not None:
                break
            itr = self.model.get_iter_from_string(str(len(self.model) - 1))
            self.model.remove(itr)

        self._rows_changed()
        self.columns_autosize()

    def _rows_changed(self):
        for row in self.model:
            self.model.row_changed(row.path, row.iter)

    @property
    def b_order(self) -> List[Optional[int]]:
        return list(self._b_order)

    @b_order.setter
    def b_order(self, order: List[Optional[int]]):
        """
        Supports a partial order list. For example, if there are 5 elements in the
        b_items list, you could supply [4, 1, 2]. This will result in an ascending order
        for the last 2 rows, so [0, 3].
        """
        if order == self._b_order:
            return

        b_len = len(self._b_items)
        if len(order) < b_len:
            # add missing indices
            for i in range(b_len):
                if i not in order:
                    order.append(i)

        while len(order) < len(self.model):
            order.append(None)

        self._b_order = order
        self._rows_changed()