def test__sort_on_value(self): m = ObjectStore() iterBob = m.append(row=["bob"]) iterAlice = m.append(row=["alice"]) m.append(row=["charlie"]) result = ObjectStore._sort_on_value(m, iterAlice, iterBob, None) self.assertEqual(result, cmp("alice", "bob"))
def test_plugin_list(self): model = ObjectStore() model.append([PLUGIN]) plist = PluginListView() plist.set_model(model) with realized(plist): plist.select_by_plugin_id("foobar") plist.destroy()
def test_iter_path_changed(self): m = ObjectStore() def handler(model, path, iter_, result): result[0] += 1 result = [0] m.connect("row-changed", handler, result) m.append([object()]) iter_ = m.get_iter_first() m.iter_changed(iter_) self.assertEqual(result[0], 1) m.path_changed(m.get_path(iter_)) self.assertEqual(result[0], 2)
def test_is_empty(self): m = ObjectStore() self.assertTrue(m.is_empty()) iter_ = m.append(row=[1]) self.assertFalse(m.is_empty()) m.remove(iter_) self.assertTrue(m.is_empty())
def _render_column(self, column, **kwargs): view = Gtk.TreeView() model = ObjectStore() view.set_model(model) song = AudioFile({"~filename": "/dev/null", "~#rating": 0.6666}) song.update(kwargs) model.append(row=[song]) view.append_column(column) if column.get_resizable(): column.set_expand(True) with visible(view): view.columns_autosize() text = column.get_cells()[0].get_property("text") self.assertIsNot(text, None) return text
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 PluginPreferences(self, *args): current = config.gettext("settings", "language") if not current: current = None combo = Gtk.ComboBox() model = ObjectStore() combo.set_model(model) for lang_id in ([None] + sorted(get_available_languages("quodlibet"))): iter_ = model.append(row=[lang_id]) if lang_id == current: combo.set_active_iter(iter_) def cell_func(combo, render, model, iter_, *args): value = model.get_value(iter_) if value is None: text = escape(_("System Default")) else: if value == u"C": value = u"en" text = "%s <span weight='light'>(%s)</span>" % ( escape(value), escape(iso639.translate(value.split("_", 1)[0]))) render.set_property("markup", text) render = Gtk.CellRendererText() render.props.ellipsize = Pango.EllipsizeMode.END combo.pack_start(render, True) combo.set_cell_data_func(render, cell_func) def on_combo_changed(combo): new_language = model.get_value(combo.get_active_iter()) if new_language is None: new_language = u"" config.settext("settings", "language", new_language) combo.connect("changed", on_combo_changed) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.pack_start(combo, False, False, 0) box.pack_start( Gtk.Label( label=_( "A restart is required for any changes to take effect"), wrap=True, xalign=0), False, False, 0) return box
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 __preview(self, songs): if songs is None: songs = [row[0].song for row in (self.view.get_model() or [])] if songs: pattern_text = gdecode(self.combo.get_child().get_text()) else: pattern_text = "" try: pattern = TagsFromPattern(pattern_text) except re.error: qltk.ErrorMessage( self, _("Invalid pattern"), _("The pattern\n\t<b>%s</b>\nis invalid. " "Possibly it contains the same tag twice or " "it has unbalanced brackets (< / >).") % ( util.escape(pattern_text))).run() return else: if pattern_text: self.combo.prepend_text(pattern_text) self.combo.write(TBP) invalid = [] for header in pattern.headers: if not min([song.can_change(header) for song in songs]): invalid.append(header) if len(invalid) and songs: if len(invalid) == 1: title = _("Invalid tag") msg = _("Invalid tag <b>%s</b>\n\nThe files currently" " selected do not support editing this tag.") else: title = _("Invalid tags") msg = _("Invalid tags <b>%s</b>\n\nThe files currently" " selected do not support editing these tags.") qltk.ErrorMessage( self, title, msg % ", ".join(invalid)).run() pattern = TagsFromPattern("") self.view.set_model(None) model = ObjectStore() for col in self.view.get_columns(): self.view.remove_column(col) render = Gtk.CellRendererText() col = TreeViewColumn(title=_('File')) col.pack_start(render, True) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) def cell_data_file(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.name) col.set_cell_data_func(render, cell_data_file) def cell_data_header(column, cell, model, iter_, header): entry = model.get_value(iter_) cell.set_property("text", entry.get_match(header)) self.view.append_column(col) for i, header in enumerate(pattern.headers): render = Gtk.CellRendererText() render.set_property('editable', True) render.connect('edited', self.__row_edited, model, header) escaped_title = header.replace("_", "__") col = Gtk.TreeViewColumn(escaped_title, render) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) col.set_cell_data_func(render, cell_data_header, header) self.view.append_column(col) for song in songs: entry = ListEntry(song) match = pattern.match(song) for h in pattern.headers: text = match.get(h, '') for f in self.filter_box.filters: if f.active: text = f.filter(h, text) if not song.can_multiple_values(h): text = u", ".join(text.split("\n")) entry.matches[h] = text model.append([entry]) # save for last to potentially save time if songs: self.view.set_model(model) self.preview.set_sensitive(False) self.save.set_sensitive(len(pattern.headers) > 0)
def test_insert_many(self): m = ObjectStore() m.append(row=[42]) m.append(row=[24]) m.insert_many(1, range(10)) self.failUnlessEqual([r[0] for r in m], [42] + range(10) + [24])
def __init__(self, library, songs, parent=None): super(SongProperties, self).__init__(dialog=False) self.set_transient_for(qltk.get_top_parent(parent)) default_width = 600 config_suffix = "" if len(songs) <= 1: default_width -= 200 config_suffix += "single" self.set_default_size(default_width, 400) self.enable_window_tracking("quodlibet_properties", size_suffix=config_suffix) self.auto_save_on_change = config.getboolean( 'editing', 'auto_save_changes', False) paned = ConfigRPaned("memory", "quodlibet_properties_pos", 0.4) notebook = qltk.Notebook() notebook.props.scrollable = True pages = [] pages.extend([Ctr(self, library) for Ctr in [EditTags, TagsFromPath, RenameFiles]]) if len(songs) > 1: pages.append(TrackNumbers(self, library)) for page in pages: page.show() notebook.append_page(page) fbasemodel = ObjectStore() fmodel = ObjectModelSort(model=fbasemodel) fview = HintedTreeView(model=fmodel) fview.connect('button-press-event', self.__pre_selection_changed) fview.set_rules_hint(True) selection = fview.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) self.__save = None render = Gtk.CellRendererText() c1 = Gtk.TreeViewColumn(_('File'), render) if fview.supports_hints(): render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('xpad', 3) def cell_data(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property('text', entry.name) c1.set_cell_data_func(render, cell_data) def sort_func(model, a, b, data): a = model.get_value(a) b = model.get_value(b) return cmp(a.name, b.name) fmodel.set_sort_func(100, sort_func) c1.set_sort_column_id(100) fview.append_column(c1) sw = ScrolledWindow() sw.add(fview) sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) # only show the list if there are is more than one song if len(songs) > 1: sw.show_all() paned.pack1(sw, shrink=False, resize=True) for song in songs: fbasemodel.append(row=[_ListEntry(song)]) self.connect("changed", self.__on_changed) selection.select_all() paned.pack2(notebook, shrink=False, resize=True) csig = selection.connect('changed', self.__selection_changed) connect_destroy(library, 'changed', self.__on_library_changed, fbasemodel, fview) connect_destroy(library, 'removed', self.__on_library_removed, fbasemodel, selection, csig) self.emit('changed', songs) self.add(paned) paned.set_position(175) notebook.show() paned.show()
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().__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))
def __preview(self, songs): if songs is None: songs = [row[0].song for row in (self.view.get_model() or [])] if songs: pattern_text = self.combo.get_child().get_text() else: pattern_text = "" try: pattern = TagsFromPattern(pattern_text) except re.error: qltk.ErrorMessage( self, _("Invalid pattern"), _("The pattern\n\t<b>%s</b>\nis invalid. " "Possibly it contains the same tag twice or " "it has unbalanced brackets (< / >).") % (util.escape(pattern_text))).run() return else: if pattern_text: self.combo.prepend_text(pattern_text) self.combo.write(TBP) invalid = [] for header in pattern.headers: if not min([song.can_change(header) for song in songs]): invalid.append(header) if len(invalid) and songs: if len(invalid) == 1: title = _("Invalid tag") msg = _("Invalid tag <b>%s</b>\n\nThe files currently" " selected do not support editing this tag.") else: title = _("Invalid tags") msg = _("Invalid tags <b>%s</b>\n\nThe files currently" " selected do not support editing these tags.") qltk.ErrorMessage(self, title, msg % ", ".join(invalid)).run() pattern = TagsFromPattern("") self.view.set_model(None) model = ObjectStore() for col in self.view.get_columns(): self.view.remove_column(col) render = Gtk.CellRendererText() col = TreeViewColumn(title=_('File')) col.pack_start(render, True) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) def cell_data_file(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.name) col.set_cell_data_func(render, cell_data_file) def cell_data_header(column, cell, model, iter_, header): entry = model.get_value(iter_) cell.set_property("text", entry.get_match(header)) self.view.append_column(col) for i, header in enumerate(pattern.headers): render = Gtk.CellRendererText() render.set_property('editable', True) render.connect('edited', self.__row_edited, model, header) escaped_title = header.replace("_", "__") col = Gtk.TreeViewColumn(escaped_title, render) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) col.set_cell_data_func(render, cell_data_header, header) self.view.append_column(col) for song in songs: entry = ListEntry(song) match = pattern.match(song) for h in pattern.headers: text = match.get(h, '') for f in self.filter_box.filters: if f.active: text = f.filter(h, text) if not song.can_multiple_values(h): text = u", ".join(text.split("\n")) entry.matches[h] = text model.append([entry]) # save for last to potentially save time if songs: self.view.set_model(model) self.preview.set_sensitive(False) self.save.set_sensitive(len(pattern.headers) > 0)
def __init__(self, library, songs, parent=None): super().__init__(dialog=False) self.set_transient_for(qltk.get_top_parent(parent)) default_width = 600 config_suffix = "" if len(songs) <= 1: default_width -= 200 config_suffix += "single" self.set_default_size(default_width, 400) self.enable_window_tracking("quodlibet_properties", size_suffix=config_suffix) self.auto_save_on_change = config.getboolean('editing', 'auto_save_changes', False) paned = ConfigRPaned("memory", "quodlibet_properties_pos", 0.4) notebook = qltk.Notebook() notebook.props.scrollable = True pages = [] pages.extend([ Ctr(self, library) for Ctr in [EditTags, TagsFromPath, RenameFiles] ]) if len(songs) > 1: pages.append(TrackNumbers(self, library)) for page in pages: page.show() notebook.append_page(page) fbasemodel = ObjectStore() fmodel = ObjectModelSort(model=fbasemodel) fview = HintedTreeView(model=fmodel) fview.connect('button-press-event', self.__pre_selection_changed) fview.set_rules_hint(True) selection = fview.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) self.__save = None render = Gtk.CellRendererText() c1 = Gtk.TreeViewColumn(_('File'), render) if fview.supports_hints(): render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('xpad', 3) def cell_data(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property('text', entry.name) c1.set_cell_data_func(render, cell_data) def sort_func(model, a, b, data): a = model.get_value(a) b = model.get_value(b) return cmp(a.name, b.name) fmodel.set_sort_func(100, sort_func) c1.set_sort_column_id(100) fview.append_column(c1) sw = ScrolledWindow() sw.add(fview) sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) # only show the list if there are is more than one song if len(songs) > 1: sw.show_all() paned.pack1(sw, shrink=False, resize=True) for song in songs: fbasemodel.append(row=[_ListEntry(song)]) self.connect("changed", self.__on_changed) selection.select_all() paned.pack2(notebook, shrink=False, resize=True) csig = selection.connect('changed', self.__selection_changed) connect_destroy(library, 'changed', self.__on_library_changed, fbasemodel, fview) connect_destroy(library, 'removed', self.__on_library_removed, fbasemodel, selection, csig) self.emit('changed', songs) self.add(paned) paned.set_position(175) notebook.show() paned.show()
def test_insert_many(self): m = ObjectStore() m.append(row=[42]) m.append(row=[24]) m.insert_many(1, range(10)) self.failUnlessEqual([r[0] for r in m], [42] + list(range(10)) + [24])
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()