def test_iter_append_many_empty(self): m = ObjectStore() for x in m.iter_append_many([]): pass for x in m.iter_append_many(iter([])): pass
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_filter(self): m = ObjectStore() f = ObjectModelFilter(child_model=m) m.insert_many(0, range(10)) def filter_func(model, iter_, data): if model.get_value(iter_) % 2 == 0: return True return False f.set_visible_func(filter_func) f.refilter() self.failUnlessEqual(range(0, 10, 2), list(f.itervalues()))
def test_sort(self): m = ObjectStore() f = ObjectModelSort(model=m) m.insert_many(0, range(10)) def sort_func(model, iter_a, iter_b, data): a = model.get_value(iter_a, 0) b = model.get_value(iter_b, 0) return -cmp(a, b) f.set_default_sort_func(sort_func) self.failUnlessEqual(sorted(range(10), reverse=True), list(f.itervalues()))
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
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 _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_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 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_insert_many_append(self): m = ObjectStore() m.insert_many(-1, range(10)) self.failUnlessEqual([r[0] for r in m], range(10)) m = ObjectStore() m.insert_many(99, range(10)) self.failUnlessEqual([r[0] for r in m], range(10))
def __init__(self, songs_lib: SongFileLibrary, Confirmer=ConfirmationPrompt): super().__init__(spacing=6) self._lists = ObjectModelSort(model=ObjectStore()) self._lists.set_default_sort_func(ObjectStore._sort_on_value) self.songs_lib = songs_lib try: self.pl_lib: PlaylistLibrary = songs_lib.playlists except (AttributeError, TypeError): print_w("No playlist library available") else: model = self._lists.get_model() print_d(f"Reading playlists from library: {self.pl_lib}") for playlist in self.pl_lib: model.append(row=[playlist]) # this is instanced with the necessary gtkdialog-settings, and afterwards # its run-method is called to get a to-be-compared Gtk.ResponseType self.Confirmer = Confirmer self.set_orientation(Gtk.Orientation.VERTICAL) self.__render = self.__create_cell_renderer() self.__view = view = self.__create_playlists_view(self.__render) self.__embed_in_scrolledwin(view) self.__configure_buttons(songs_lib) self.__configure_dnd(view) self.__connect_signals(view) self._sb_box = self.__create_searchbar(songs_lib) self._rh_box = None self._main_box = self.__create_box() self.show_all() for child in self.get_children(): child.show_all() if hasattr(self, "pl_lib"): self._ids = [ self.pl_lib.connect('removed', self.__removed), self.pl_lib.connect('added', self.__added), self.pl_lib.connect('changed', self.__changed), ] print_d( f"Connected signals: {self._ids} from {self.pl_lib!r} for {self}" ) else: self._ids = [] self.connect("destroy", self._destroy)
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_iter_values(self): m = ObjectStore() f = ObjectModelSort(model=m) m.insert_many(0, range(10)) self.failUnlessEqual(range(10), list(f.itervalues()))
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_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 test_append_many(self): m = ObjectStore() m.append_many(range(10)) self.failUnlessEqual([r[0] for r in m], range(10))
def __init__(self, parent=None): if self.is_not_unique(): return on_top = config.getboolean("settings", "plugins_window_on_top", False) super().__init__(dialog=on_top) self.set_title(_("Plugins")) self.set_default_size(750, 500) if parent and on_top: self.set_transient_for(parent) self.set_type_hint(Gdk.WindowTypeHint.NORMAL) self.enable_window_tracking("plugin_prefs") model = ObjectStore() filter_model = ObjectModelFilter(child_model=model) self._list_view = plv = PluginListView() plv.set_model(filter_model) plv.set_rules_hint(True) plv.connect("plugin-toggled", self.__plugin_toggled) sw = ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) sw.add(plv) sw.set_shadow_type(Gtk.ShadowType.IN) fb = Gtk.HBox(spacing=6) enabled_combo = PluginEnabledFilterCombo() enabled_combo.connect("changed", lambda s: filter_model.refilter()) enabled_combo.set_tooltip_text(_("Filter by plugin state / tag")) fb.pack_start(enabled_combo, True, True, 0) self._enabled_combo = enabled_combo type_combo = PluginTypeFilterCombo() type_combo.connect("changed", lambda s: filter_model.refilter()) type_combo.set_tooltip_text(_("Filter by plugin type")) fb.pack_start(type_combo, True, True, 0) self._type_combo = type_combo self._filter_entry = fe = UndoSearchEntry() fe.set_tooltip_text(_("Filter by plugin name or description")) fe.connect("changed", lambda s: filter_model.refilter()) errors = qltk.Button(_("Show _Errors"), Icons.DIALOG_WARNING) errors.set_focus_on_click(False) errors.connect('clicked', self.__show_errors) errors.show() errors = Align(errors, top=6, bottom=6) errors.set_no_show_all(True) bbox = Gtk.VBox() bbox.pack_start(errors, True, True, 0) pref_box = PluginPreferencesContainer() if const.DEBUG: refresh = qltk.Button(_("_Refresh"), Icons.VIEW_REFRESH) refresh.set_focus_on_click(False) refresh.connect('clicked', self.__refresh, plv, pref_box, errors, enabled_combo) bbox.pack_start(refresh, True, True, 0) filter_box = Gtk.VBox(spacing=6) filter_box.pack_start(fb, False, True, 0) filter_box.pack_start(fe, False, True, 0) vbox = Gtk.VBox() vbox.pack_start(Align(filter_box, border=6, right=-6), False, False, 0) vbox.pack_start(sw, True, True, 0) vbox.pack_start(Align(bbox, left=3, right=3, top=0), False, False, 3) paned = Paned() paned.pack1(vbox, False, False) close = qltk.Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) bb_align = Align(halign=Gtk.Align.END, valign=Gtk.Align.END) bb = Gtk.HButtonBox() bb.set_layout(Gtk.ButtonBoxStyle.END) bb.pack_start(close, True, True, 0) bb_align.add(bb) selection = plv.get_selection() selection.connect('changed', self.__selection_changed, pref_box) selection.emit('changed') right_box = Gtk.VBox() right_box.pack_start(pref_box, True, True, 0) if not self.has_close_button(): right_box.pack_start(bb_align, True, True, 0) align = Align(right_box, left=6, right=15, top=12, bottom=3) paned.pack2(align, True, False) paned.set_position(290) self.add(paned) self.__refill(plv, pref_box, errors, enabled_combo) self.connect('destroy', self.__destroy) filter_model.set_visible_func( self.__filter, (fe, enabled_combo, type_combo)) self.get_child().show_all() fe.grab_focus() restore_id = config.get("memory", "plugin_selection") plv.select_by_plugin_id(restore_id)
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)
def test_validate(self): self.failUnlessRaises(ValueError, ObjectStore, int) ObjectStore() ObjectStore(object) self.failUnlessRaises(ValueError, ObjectStore, object, object)
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_iterrows_empty(self): m = ObjectStore() self.assertEqual(list(m.iterrows()), [])
def test_iterrows(self): m = ObjectStore() m.insert_many(0, range(10)) for iter_, value in m.iterrows(): self.failUnlessEqual(m.get_value(iter_), value)
def test_itervalues(self): m = ObjectStore() m.insert_many(0, range(10)) self.failUnlessEqual(range(10), list(m.itervalues()))
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)
def __init__(self, parent, library): super(RenameFiles, self).__init__(spacing=6) self.__skip_interactive = False self.set_border_width(12) hbox = Gtk.HBox(spacing=6) cbes_defaults = NBP_EXAMPLES.split("\n") self.combo = ComboBoxEntrySave(NBP, cbes_defaults, title=_("Path Patterns"), edit_title=_(u"Edit saved patterns…")) self.combo.show_all() hbox.pack_start(self.combo, True, True, 0) self.preview = qltk.Button(_("_Preview"), Icons.VIEW_REFRESH) self.preview.show() hbox.pack_start(self.preview, False, True, 0) self.pack_start(hbox, False, True, 0) self.combo.get_child().connect('changed', self._changed) model = ObjectStore() self.view = Gtk.TreeView(model=model) self.view.show() sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.view) self.pack_start(sw, True, True, 0) self.pack_start(Gtk.VBox(), False, True, 0) # rename options rename_options = Gtk.HBox() # file name options filter_box = FilterPluginBox(self.handler, self.FILTERS) filter_box.connect("preview", self.__filter_preview) filter_box.connect("changed", self.__filter_changed) self.filter_box = filter_box frame_filename_options = Frame(_("File names"), filter_box) frame_filename_options.show_all() rename_options.pack_start(frame_filename_options, False, True, 0) # album art options albumart_box = Gtk.VBox() # move art moveart_box = Gtk.VBox() self.moveart = ConfigCheckButton(_('_Move album art'), "rename", "move_art", populate=True) self.moveart.set_tooltip_text( _("See '[albumart] filenames' config entry " + "for image search strings")) self.moveart.show() moveart_box.pack_start(self.moveart, False, True, 0) self.moveart_overwrite = ConfigCheckButton( _('_Overwrite album art at target'), "rename", "move_art_overwrite", populate=True) self.moveart_overwrite.show() moveart_box.pack_start(self.moveart_overwrite, False, True, 0) albumart_box.pack_start(moveart_box, False, True, 0) # remove empty removeemptydirs_box = Gtk.VBox() self.removeemptydirs = ConfigCheckButton( _('_Remove empty directories'), "rename", "remove_empty_dirs", populate=True) self.removeemptydirs.show() removeemptydirs_box.pack_start(self.removeemptydirs, False, True, 0) albumart_box.pack_start(removeemptydirs_box, False, True, 0) frame_albumart_options = Frame(_("Album art"), albumart_box) frame_albumart_options.show_all() rename_options.pack_start(frame_albumart_options, False, True, 0) self.pack_start(rename_options, False, True, 0) # Save button self.save = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.save.show() bbox = Gtk.HButtonBox() bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.save, True, True, 0) self.pack_start(bbox, False, True, 0) render = Gtk.CellRendererText() column = TreeViewColumn(title=_('File')) column.pack_start(render, True) def cell_data_file(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.name) column.set_cell_data_func(render, cell_data_file) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.view.append_column(column) render = Gtk.CellRendererText() render.set_property('editable', True) column = TreeViewColumn(title=_('New Name')) column.pack_start(render, True) def cell_data_new_name(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.new_name or u"") column.set_cell_data_func(render, cell_data_new_name) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.view.append_column(column) connect_obj(self.preview, 'clicked', self._preview, None) connect_obj(parent, 'changed', self.__class__._preview, self) connect_obj(self.save, 'clicked', self._rename, library) render.connect('edited', self.__row_edited) for child in self.get_children(): child.show()
def __init__(self, parent=None): if self.is_not_unique(): return super(PluginWindow, self).__init__() self.set_title(_("Plugins")) self.set_default_size(700, 500) self.set_transient_for(parent) self.enable_window_tracking("plugin_prefs") paned = Paned() vbox = Gtk.VBox() sw = ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) model = ObjectStore() filter_model = ObjectModelFilter(child_model=model) tv = PluginListView() tv.set_model(filter_model) tv.set_rules_hint(True) tv.connect("plugin-toggled", self.__plugin_toggled) fb = Gtk.HBox(spacing=6) filter_combo = PluginFilterCombo() filter_combo.connect("changed", lambda s: filter_model.refilter()) fb.pack_start(filter_combo, False, True, 0) filter_entry = ClearEntry() filter_entry.connect("changed", lambda s: filter_model.refilter()) filter_entry.enable_clear_button() fb.pack_start(filter_entry, True, True, 0) sw.add(tv) sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_size_request(200, -1) bbox = Gtk.HBox(homogeneous=True, spacing=12) errors = qltk.Button(_("Show _Errors"), Icons.DIALOG_WARNING) errors.set_focus_on_click(False) errors.connect('clicked', self.__show_errors) errors.set_no_show_all(True) bbox.pack_start(Align(errors, border=6, right=-6), True, True, 0) pref_box = PluginPreferencesContainer() if const.DEBUG: refresh = qltk.Button(_("_Refresh"), Icons.VIEW_REFRESH) refresh.set_focus_on_click(False) refresh.connect('clicked', self.__refresh, tv, pref_box, errors, filter_combo) bbox.pack_start(Align(refresh, border=6), True, True, 0) vbox.pack_start(Align(fb, border=6, right=-6), False, True, 0) vbox.pack_start(sw, True, True, 0) vbox.pack_start(bbox, False, True, 0) paned.pack1(vbox, False, False) close = qltk.Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) bb_align = Align(halign=Gtk.Align.END, valign=Gtk.Align.END) bb = Gtk.HButtonBox() bb.set_layout(Gtk.ButtonBoxStyle.END) bb.pack_start(close, True, True, 0) bb_align.add(bb) selection = tv.get_selection() selection.connect('changed', self.__selection_changed, pref_box) selection.emit('changed') right_box = Gtk.VBox(spacing=12) right_box.pack_start(pref_box, True, True, 0) self.use_header_bar() if not self.has_close_button(): right_box.pack_start(bb_align, True, True, 0) paned.pack2(Align(right_box, border=12), True, False) paned.set_position(250) self.add(paned) self.__refill(tv, pref_box, errors, filter_combo) self.connect('destroy', self.__destroy) filter_model.set_visible_func(self.__filter, (filter_entry, filter_combo)) self.get_child().show_all() filter_entry.grab_focus() restore_id = config.get("memory", "plugin_selection") tv.select_by_plugin_id(restore_id)
class PlaylistsBrowser(Browser, DisplayPatternMixin): name = _("Playlists") accelerated_name = _("_Playlists") keys = ["Playlists", "PlaylistsBrowser"] priority = 2 replaygain_profiles = ["track"] __last_render = None _PATTERN_FN = os.path.join(quodlibet.get_user_dir(), "playlist_pattern") _DEFAULT_PATTERN_TEXT = DEFAULT_PATTERN_TEXT def pack(self, songpane): self._main_box.pack1(self, True, False) self._rh_box = rhbox = Gtk.VBox(spacing=6) align = Align(self._sb_box, left=0, right=6, top=6) rhbox.pack_start(align, False, True, 0) rhbox.pack_start(songpane, True, True, 0) self._main_box.pack2(rhbox, True, False) rhbox.show() align.show_all() return self._main_box def unpack(self, container, songpane): self._rh_box.remove(songpane) container.remove(self._rh_box) container.remove(self) @classmethod def init(klass, library): klass.library = library model = klass.__lists.get_model() for playlist in os.listdir(PLAYLISTS): try: playlist = FileBackedPlaylist( PLAYLISTS, FileBackedPlaylist.unquote(playlist), library=library) model.append(row=[playlist]) except EnvironmentError: print_w("Invalid Playlist '%s'" % playlist) pass klass._ids = [ library.connect('removed', klass.__removed), library.connect('added', klass.__added), library.connect('changed', klass.__changed), ] klass.load_pattern() @classmethod def deinit(cls, library): model = cls.__lists.get_model() model.clear() for id_ in cls._ids: library.disconnect(id_) del cls._ids @classmethod def playlists(klass): return [row[0] for row in klass.__lists] @classmethod def changed(klass, playlist, refresh=True): model = klass.__lists for row in model: if row[0] is playlist: if refresh: print_d("Refreshing playlist %s..." % row[0]) klass.__lists.row_changed(row.path, row.iter) playlist.write() break else: model.get_model().append(row=[playlist]) playlist.write() @classmethod def __removed(klass, library, songs): for playlist in klass.playlists(): if playlist.remove_songs(songs): klass.changed(playlist) @classmethod def __added(klass, library, songs): filenames = {song("~filename") for song in songs} for playlist in klass.playlists(): if playlist.add_songs(filenames, library): klass.changed(playlist) @classmethod def __changed(klass, library, songs): for playlist in klass.playlists(): for song in songs: if song in playlist.songs: klass.changed(playlist) break def cell_data(self, col, cell, model, iter, data): playlist = model[iter][0] cell.markup = markup = self.display_pattern % playlist if self.__last_render == markup: return self.__last_render = markup cell.markup = markup cell.set_property('markup', markup) def Menu(self, songs, library, items): model, iters = self.__get_selected_songs() remove = qltk.MenuItem(_("_Remove from Playlist"), Icons.LIST_REMOVE) qltk.add_fake_accel(remove, "Delete") connect_obj(remove, 'activate', self.__remove, iters, model) playlist_iter = self.__view.get_selection().get_selected()[1] remove.set_sensitive(bool(playlist_iter)) items.append([remove]) menu = super(PlaylistsBrowser, self).Menu(songs, library, items) return menu def __get_selected_songs(self): songlist = qltk.get_top_parent(self).songlist model, rows = songlist.get_selection().get_selected_rows() iters = map(model.get_iter, rows) return model, iters __lists = ObjectModelSort(model=ObjectStore()) __lists.set_default_sort_func(ObjectStore._sort_on_value) def __init__(self, library): self.library = library super(PlaylistsBrowser, self).__init__(spacing=6) self.set_orientation(Gtk.Orientation.VERTICAL) self.__render = self.__create_cell_renderer() self.__view = view = self.__create_playlists_view(self.__render) self.__embed_in_scrolledwin(view) self.__configure_buttons(library) self.__configure_dnd(view, library) self.__connect_signals(view, library) self._sb_box = self.__create_searchbar(library) self._main_box = self.__create_box() self.show_all() self._query = None for child in self.get_children(): child.show_all() def __destroy(self, *args): del self._sb_box def __create_box(self): box = qltk.ConfigRHPaned("browsers", "playlistsbrowser_pos", 0.4) box.show_all() return box def __create_searchbar(self, library): self.accelerators = Gtk.AccelGroup() completion = LibraryTagCompletion(library.librarian) sbb = SearchBarBox(completion=completion, accel_group=self.accelerators) sbb.connect('query-changed', self.__text_parse) sbb.connect('focus-out', self.__focus) return sbb def __embed_in_scrolledwin(self, view): swin = ScrolledWindow() swin.set_shadow_type(Gtk.ShadowType.IN) swin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) swin.add(view) self.pack_start(swin, True, True, 0) def __configure_buttons(self, library): new_pl = qltk.Button(_("_New"), Icons.DOCUMENT_NEW, Gtk.IconSize.MENU) new_pl.connect('clicked', self.__new_playlist) import_pl = qltk.Button(_("_Import"), Icons.LIST_ADD, Gtk.IconSize.MENU) import_pl.connect('clicked', self.__import, library) hb = Gtk.HBox(spacing=6) hb.set_homogeneous(False) hb.pack_start(new_pl, True, True, 0) hb.pack_start(import_pl, True, True, 0) hb2 = Gtk.HBox(spacing=0) hb2.pack_start(hb, True, True, 0) hb2.pack_start(PreferencesButton(self), False, False, 6) self.pack_start(Align(hb2, left=3, bottom=3), False, False, 0) def __create_playlists_view(self, render): view = RCMHintedTreeView() view.set_enable_search(True) view.set_search_column(0) view.set_search_equal_func( lambda model, col, key, iter, data: not model[iter][col].name. lower().startswith(key.lower()), None) col = Gtk.TreeViewColumn("Playlists", render) col.set_cell_data_func(render, self.cell_data) view.append_column(col) view.set_model(self.__lists) view.set_rules_hint(True) view.set_headers_visible(False) return view def __configure_dnd(self, view, library): targets = [("text/x-quodlibet-songs", Gtk.TargetFlags.SAME_APP, DND_QL), ("text/uri-list", 0, DND_URI_LIST), ("text/x-moz-url", 0, DND_MOZ_URL)] targets = [Gtk.TargetEntry.new(*t) for t in targets] view.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY) view.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets[:2], Gdk.DragAction.COPY) view.connect('drag-data-received', self.__drag_data_received, library) view.connect('drag-data-get', self.__drag_data_get) view.connect('drag-motion', self.__drag_motion) view.connect('drag-leave', self.__drag_leave) def __connect_signals(self, view, library): view.connect('row-activated', lambda *x: self.songs_activated()) view.connect('popup-menu', self.__popup_menu, library) view.get_selection().connect('changed', self.activate) model = view.get_model() s = model.connect('row-changed', self.__check_current) connect_obj(self, 'destroy', model.disconnect, s) self.connect('key-press-event', self.__key_pressed) def __create_cell_renderer(self): render = Gtk.CellRendererText() render.set_padding(3, 3) render.set_property('ellipsize', Pango.EllipsizeMode.END) render.connect('editing-started', self.__start_editing) render.connect('edited', self.__edited) return render def key_pressed(self, event): if qltk.is_accel(event, "Delete"): self.__handle_songlist_delete() return True return False def __handle_songlist_delete(self, *args): model, iters = self.__get_selected_songs() self.__remove(iters, model) def __key_pressed(self, widget, event): if qltk.is_accel(event, "Delete"): model, iter = self.__view.get_selection().get_selected() if not iter: return False playlist = model[iter][0] dialog = ConfirmRemovePlaylistDialog(self, playlist) if dialog.run() == Gtk.ResponseType.YES: playlist.delete() model.get_model().remove( model.convert_iter_to_child_iter(iter)) return True elif qltk.is_accel(event, "F2"): model, iter = self.__view.get_selection().get_selected() if iter: self._start_rename(model.get_path(iter)) return True return False def __check_current(self, model, path, iter): model, citer = self.__view.get_selection().get_selected() if citer and model.get_path(citer) == path: songlist = qltk.get_top_parent(self).songlist self.activate(resort=not songlist.is_sorted()) def __drag_motion(self, view, ctx, x, y, time): targets = [t.name() for t in ctx.list_targets()] if "text/x-quodlibet-songs" in targets: view.set_drag_dest(x, y, into_only=True) return True else: # Highlighting the view itself doesn't work. view.get_parent().drag_highlight() return True def __drag_leave(self, view, ctx, time): view.get_parent().drag_unhighlight() def __remove(self, iters, smodel): def song_at(itr): return smodel[smodel.get_path(itr)][0] def remove_from_model(iters, smodel): for it in iters: smodel.remove(it) model, iter = self.__view.get_selection().get_selected() if iter: playlist = model[iter][0] removals = [song_at(iter_remove) for iter_remove in iters] if self._query is None or not self.get_filter_text(): # Calling playlist.remove_songs(songs) won't remove the # right ones if there are duplicates remove_from_model(iters, smodel) self.__rebuild_playlist_from_songs_model(playlist, smodel) # Emit manually self.library.emit('changed', removals) else: print_d("Removing %d song(s) from %s" % (len(removals), playlist)) playlist.remove_songs(removals, True) remove_from_model(iters, smodel) self.changed(playlist) self.activate() def __rebuild_playlist_from_songs_model(self, playlist, smodel): playlist.inhibit = True playlist.clear() playlist.extend([row[0] for row in smodel]) playlist.inhibit = False def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library): # TreeModelSort doesn't support GtkTreeDragDestDrop. view.emit_stop_by_name('drag-data-received') model = view.get_model() if tid == DND_QL: filenames = qltk.selection_get_filenames(sel) songs = filter(None, map(library.get, filenames)) if not songs: Gtk.drag_finish(ctx, False, False, etime) return try: path, pos = view.get_dest_row_at_pos(x, y) except TypeError: playlist = FileBackedPlaylist.from_songs( PLAYLISTS, songs, library) GLib.idle_add(self._select_playlist, playlist) else: playlist = model[path][0] playlist.extend(songs) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) else: if tid == DND_URI_LIST: uri = sel.get_uris()[0] name = os.path.basename(uri) elif tid == DND_MOZ_URL: data = sel.get_data() uri, name = data.decode('utf16', 'replace').split('\n') else: Gtk.drag_finish(ctx, False, False, etime) return name = name or os.path.basename(uri) or _("New Playlist") uri = uri.encode('utf-8') try: sock = urllib.urlopen(uri) f = NamedTemporaryFile() f.write(sock.read()) f.flush() if uri.lower().endswith('.pls'): playlist = parse_pls(f.name, library=library) elif uri.lower().endswith('.m3u'): playlist = parse_m3u(f.name, library=library) else: raise IOError library.add_filename(playlist) if name: playlist.rename(name) self.changed(playlist) Gtk.drag_finish(ctx, True, False, etime) except IOError: Gtk.drag_finish(ctx, False, False, etime) qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U " "and PLS formats.")).run() def __drag_data_get(self, view, ctx, sel, tid, etime): model, iters = self.__view.get_selection().get_selected_rows() songs = [] for iter in filter(lambda i: i, iters): songs += list(model[iter][0]) if tid == 0: qltk.selection_set_songs(sel, songs) else: sel.set_uris([song("~uri") for song in songs]) def _select_playlist(self, playlist, scroll=False): view = self.__view model = view.get_model() for row in model: if row[0] is playlist: view.get_selection().select_iter(row.iter) if scroll: view.scroll_to_cell(row.path, use_align=True, row_align=0.5) def __popup_menu(self, view, library): model, itr = view.get_selection().get_selected() if itr is None: return songs = list(model[itr][0]) songs = filter(lambda s: isinstance(s, AudioFile), songs) menu = SongsMenu(library, songs, playlists=False, remove=False, ratings=False) menu.preseparate() def _remove(model, itr): playlist = model[itr][0] dialog = ConfirmRemovePlaylistDialog(self, playlist) if dialog.run() == Gtk.ResponseType.YES: playlist.delete() model.get_model().remove(model.convert_iter_to_child_iter(itr)) rem = MenuItem(_("_Delete"), Icons.EDIT_DELETE) connect_obj(rem, 'activate', _remove, model, itr) menu.prepend(rem) def _rename(path): self._start_rename(path) ren = qltk.MenuItem(_("_Rename"), Icons.EDIT) qltk.add_fake_accel(ren, "F2") connect_obj(ren, 'activate', _rename, model.get_path(itr)) menu.prepend(ren) playlist = model[itr][0] PLAYLIST_HANDLER.populate_menu(menu, library, self, [playlist]) menu.show_all() return view.popup_menu(menu, 0, Gtk.get_current_event_time()) def _start_rename(self, path): view = self.__view self.__render.set_property('editable', True) view.set_cursor(path, view.get_columns()[0], start_editing=True) def __focus(self, widget, *args): qltk.get_top_parent(widget).songlist.grab_focus() def __text_parse(self, bar, text): self.activate() def _set_text(self, text): self._sb_box.set_text(text) def activate(self, widget=None, resort=True): songs = self._get_playlist_songs() text = self.get_filter_text() # TODO: remove static dependency on Query if Query.is_parsable(text): self._query = Query(text, SongList.star) songs = self._query.filter(songs) GLib.idle_add(self.songs_selected, songs, resort) @classmethod def refresh_all(cls): model = cls.__lists.get_model() for iter_, value in model.iterrows(): model.row_changed(model.get_path(iter_), iter_) @property def model(self): return self.__lists.get_model() def _get_playlist_songs(self): model, iter = self.__view.get_selection().get_selected() songs = iter and list(model[iter][0]) or [] songs = filter(lambda s: isinstance(s, AudioFile), songs) return songs def can_filter_text(self): return True def filter_text(self, text): self._set_text(text) self.activate() def get_filter_text(self): return self._sb_box.get_text() def can_filter(self, key): # TODO: special-case the ~playlists tag maybe? return super(PlaylistsBrowser, self).can_filter(key) def finalize(self, restore): config.set("browsers", "query_text", "") def unfilter(self): self.filter_text("") def active_filter(self, song): return (song in self._get_playlist_songs() and (self._query is None or self._query.search(song))) def save(self): model, iter = self.__view.get_selection().get_selected() name = iter and model[iter][0].name or "" config.set("browsers", "playlist", name) text = self.get_filter_text() config.set("browsers", "query_text", text) def __new_playlist(self, activator): playlist = FileBackedPlaylist.new(PLAYLISTS) self.model.append(row=[playlist]) self._select_playlist(playlist, scroll=True) model, iter = self.__view.get_selection().get_selected() path = model.get_path(iter) GLib.idle_add(self._start_rename, path) def __start_editing(self, render, editable, path): editable.set_text(self.__lists[path][0].name) def __edited(self, render, path, newname): return self._rename(path, newname) def _rename(self, path, newname): playlist = self.__lists[path][0] try: playlist.rename(newname) except ValueError as s: qltk.ErrorMessage(None, _("Unable to rename playlist"), s).run() else: row = self.__lists[path] child_model = self.model child_model.remove( self.__lists.convert_iter_to_child_iter(row.iter)) child_model.append(row=[playlist]) self._select_playlist(playlist, scroll=True) def __import(self, activator, library): filt = lambda fn: fn.endswith(".pls") or fn.endswith(".m3u") from quodlibet.qltk.chooser import FileChooser chooser = FileChooser(self, _("Import Playlist"), filt, get_home_dir()) files = chooser.run() chooser.destroy() for filename in files: if filename.endswith(".m3u"): playlist = parse_m3u(filename, library=library) elif filename.endswith(".pls"): playlist = parse_pls(filename, library=library) else: qltk.ErrorMessage( qltk.get_top_parent(self), _("Unable to import playlist"), _("Quod Libet can only import playlists in the M3U " "and PLS formats.")).run() return self.changed(playlist) library.add(playlist) def restore(self): try: name = config.get("browsers", "playlist") except config.Error as e: print_d("Couldn't get last playlist from config: %s" % e) else: self.__view.select_by_func(lambda r: r[0].name == name, one=True) try: text = config.get("browsers", "query_text") except config.Error as e: print_d("Couldn't get last search string from config: %s" % e) else: self._set_text(text) can_reorder = True def scroll(self, song): self.__view.iter_select_by_func(lambda r: song in r[0]) def reordered(self, songs): model, iter = self.__view.get_selection().get_selected() playlist = None if iter: playlist = model[iter][0] playlist[:] = songs elif songs: playlist = FileBackedPlaylist.from_songs(PLAYLISTS, songs) GLib.idle_add(self._select_playlist, playlist) if playlist: self.changed(playlist, refresh=False)
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().decode("utf-8") 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(const.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(_('File'), render) 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, prop, library): super().__init__(spacing=6) self.title = _("Track Numbers") self.set_border_width(12) label_start = Gtk.Label(label=_("Start fro_m:"), halign=Gtk.Align.END) label_start.set_use_underline(True) spin_start = Gtk.SpinButton() spin_start.set_range(0, 999) spin_start.set_increments(1, 10) spin_start.set_value(1) label_start.set_mnemonic_widget(spin_start) label_total = Gtk.Label(label=_("_Total tracks:"), halign=Gtk.Align.END) label_total.set_use_underline(True) spin_total = Gtk.SpinButton() spin_total.set_range(0, 999) spin_total.set_increments(1, 10) label_total.set_mnemonic_widget(spin_total) preview = qltk.Button(_("_Preview"), Icons.VIEW_REFRESH) grid = Gtk.Grid(row_spacing=4, column_spacing=4) grid.add(label_start) grid.attach_next_to(spin_start, label_start, Gtk.PositionType.RIGHT, 1, 1) grid.attach_next_to(label_total, label_start, Gtk.PositionType.BOTTOM, 1, 1) grid.attach_next_to(spin_total, label_total, Gtk.PositionType.RIGHT, 1, 1) grid.attach_next_to(Align(preview, halign=Gtk.Align.END), spin_start, Gtk.PositionType.RIGHT, 1, 1) preview.props.hexpand = True model = ObjectStore() view = HintedTreeView(model=model) self.pack_start(grid, False, True, 0) render = Gtk.CellRendererText() column = TreeViewColumn(title=_('File')) column.pack_start(render, True) column.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) column.set_cell_data_func(render, cell_data_file) view.append_column(column) render = Gtk.CellRendererText() render.set_property('editable', True) column = TreeViewColumn(title=_('Track')) column.pack_start(render, True) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) def cell_data_track(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.tracknumber) column.set_cell_data_func(render, cell_data_track) view.append_column(column) view.set_reorderable(True) w = Gtk.ScrolledWindow() w.set_shadow_type(Gtk.ShadowType.IN) w.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) w.add(view) self.pack_start(w, True, True, 0) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) save = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.save = save connect_obj(save, 'clicked', self.__save_files, prop, model, library) revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT) self.revert = revert bbox.pack_start(revert, True, True, 0) bbox.pack_start(save, True, True, 0) self.pack_start(bbox, False, True, 0) preview_args = [spin_start, spin_total, model, save, revert] preview.connect('clicked', self.__preview_tracks, *preview_args) connect_obj(revert, 'clicked', self.__update, None, *preview_args[1:]) spin_total.connect('value-changed', self.__preview_tracks, *preview_args) spin_start.connect('value-changed', self.__preview_tracks, *preview_args) connect_obj(view, 'drag-end', self.__class__.__preview_tracks, self, *preview_args) render.connect('edited', self.__row_edited, model, preview, save) connect_obj(prop, 'changed', self.__class__.__update, self, spin_total, model, save, revert) for child in self.get_children(): child.show_all()
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_iter_append_many(self): m = ObjectStore() iters = list(m.iter_append_many(range(10))) self.failUnlessEqual([r[0] for r in m], range(10)) values = [m.get_value(i) for i in iters] self.failUnlessEqual(values, range(10))
def __init__(self, songs, title=None): super().__init__( default_width=800, default_height=400, border_width=12, title=title) self._thread = AcoustidLookupThread(self.__lookup_cb) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) model = ObjectStore() self.view = view = ResultView() view.set_model(model) self.model = model self._iter_map = {} for song in songs: iter_ = self.model.append([SearchEntry(song)]) self._iter_map[song] = iter_ sw.add(view) self.pool = pool = FingerPrintPool() pool.connect('fingerprint-done', self.__fp_done_cb) pool.connect('fingerprint-error', self.__fp_error_cb) pool.connect('fingerprint-started', self.__fp_started_cb) for song in songs: pool.push(song) outer_box = Gtk.VBox(spacing=12) bbox = Gtk.HButtonBox() bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.set_spacing(6) self.__save = save = Button(_("_Save"), Icons.DOCUMENT_SAVE) save.connect("clicked", self.__on_save) save.set_sensitive(False) cancel = Button(_("_Cancel")) cancel.connect("clicked", lambda *x: self.destroy()) bbox.pack_start(save, True, True, 0) bbox.pack_start(cancel, True, True, 0) inner_box = Gtk.VBox(spacing=6) inner_box.pack_start(sw, True, True, 0) ccb = ConfigCheckButton( _("Write MusicBrainz tags"), "plugins", "fingerprint_write_mb_tags") ccb.set_active(get_write_mb_tags()) inner_box.pack_start(ccb, False, True, 0) ccb = ConfigCheckButton( _("Group by directory"), "plugins", "fingerprint_group_by_dir") ccb.set_active(get_group_by_dir()) ccb.connect("toggled", self.__group_toggled) self._group_ccb = ccb outer_box.pack_start(inner_box, True, True, 0) bottom_box = Gtk.HBox(spacing=12) mode_button = Gtk.ToggleButton(label=_("Album Mode")) mode_button.set_tooltip_text( _("Write album related tags and try to " "reduce the number of different album releases")) mode_button.set_active(True) mode_button.connect("toggled", self.__mode_toggle) bottom_box.pack_start(mode_button, False, True, 0) bottom_box.pack_start(self._group_ccb, False, True, 0) bottom_box.pack_start(bbox, True, True, 0) outer_box.pack_start(bottom_box, False, True, 0) outer_box.show_all() self.add(outer_box) self.__album_mode = True self.__group_by_dir = True self._release_scores = {} self._directory_scores = {} self.__done = 0 self.connect("destroy", self.__destroy)
def test_iter_append_many_iterable_int(self): m = ObjectStore() for x in m.iter_append_many((i for i in xrange(10))): pass self.failUnlessEqual([r[0] for r in m], range(10))
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()
def test_iter_append_many_iterable_object(self): objects = [object() for i in xrange(10)] m = ObjectStore() for x in m.iter_append_many((i for i in objects)): pass self.failUnlessEqual([r[0] for r in m], objects)
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 __init__(self, initial=None, filter=filesel_filter, folders=None): """ initial -- a path to a file which should be shown initially filter -- a function which filters paths shown in the file list folders -- list of shown folders in the directory tree """ super(FileSelector, self).__init__() self.__filter = filter if initial is not None: initial = util.fsnative(initial) if initial and os.path.isfile(initial): initial = os.path.dirname(initial) dirlist = DirectoryTree(initial, folders=folders) model = ObjectStore() filelist = AllTreeView(model) column = TreeViewColumn(_("Songs")) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render = Gtk.CellRendererPixbuf() render.set_property('stock_id', Gtk.STOCK_FILE) render.props.xpad = 3 column.pack_start(render, False) render = Gtk.CellRendererText() column.pack_start(render, True) def cell_data(column, cell, model, iter_, userdata): value = model.get_value(iter_) cell.set_property('text', fsdecode(os.path.basename(value))) column.set_cell_data_func(render, cell_data) filelist.append_column(column) filelist.set_rules_hint(True) filelist.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) filelist.set_search_equal_func(search_func, False) # Allow to drag and drop files from outside filelist.enable_model_drag_dest([], Gdk.DragAction.COPY) filelist.drag_dest_add_uri_targets() filelist.connect('drag-data-received', self.__file_dropped) self.__sig = filelist.get_selection().connect('changed', self.__changed) dirlist.get_selection().connect('changed', self.__dir_selection_changed, filelist) dirlist.get_selection().emit('changed') def select_all_files(view, path, col, fileselection): view.expand_row(path, False) fileselection.select_all() dirlist.connect('row-activated', select_all_files, filelist.get_selection()) sw = ScrolledWindow() sw.add(dirlist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack1(sw, resize=True) sw = ScrolledWindow() sw.add(filelist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack2(sw, resize=True)
def __init__(self, parent, library): super(RenameFiles, self).__init__(spacing=6) self.set_border_width(12) hbox = Gtk.HBox(spacing=6) cbes_defaults = NBP_EXAMPLES.split("\n") self.combo = ComboBoxEntrySave(NBP, cbes_defaults, title=_("Path Patterns"), edit_title=_(u"Edit saved patterns…")) self.combo.show_all() hbox.pack_start(self.combo, True, True, 0) self.preview = qltk.Button(_("_Preview"), Icons.VIEW_REFRESH) self.preview.show() hbox.pack_start(self.preview, False, True, 0) self.pack_start(hbox, False, True, 0) self.combo.get_child().connect('changed', self._changed) model = ObjectStore() self.view = Gtk.TreeView(model=model) self.view.show() sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(self.view) self.pack_start(sw, True, True, 0) self.pack_start(Gtk.VBox(), False, True, 0) filter_box = FilterPluginBox(self.handler, self.FILTERS) filter_box.connect("preview", self.__filter_preview) filter_box.connect("changed", self.__filter_changed) self.filter_box = filter_box self.pack_start(filter_box, False, True, 0) # Save button self.save = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.save.show() bbox = Gtk.HButtonBox() bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.save, True, True, 0) self.pack_start(bbox, False, True, 0) render = Gtk.CellRendererText() column = TreeViewColumn(_('File'), render) def cell_data_file(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.name) column.set_cell_data_func(render, cell_data_file) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.view.append_column(column) render = Gtk.CellRendererText() render.set_property('editable', True) column = TreeViewColumn(_('New Name'), render) def cell_data_new_name(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.new_name or u"") column.set_cell_data_func(render, cell_data_new_name) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.view.append_column(column) connect_obj(self.preview, 'clicked', self.__preview, None) connect_obj(parent, 'changed', self.__class__.__preview, self) connect_obj(self.save, 'clicked', self.__rename, library) render.connect('edited', self.__row_edited) for child in self.get_children(): child.show()
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 __init__(self, parent, library): super(EditTags, self).__init__(spacing=12) self.title = _("Edit Tags") self.set_border_width(12) model = ObjectStore() view = RCMHintedTreeView(model=model) self._view = view selection = view.get_selection() render = Gtk.CellRendererPixbuf() column = TreeViewColumn() column.pack_start(render, True) column.set_fixed_width(24) column.set_expand(False) def cdf_write(col, rend, model, iter_, *args): entry = model.get_value(iter_) rend.set_property('sensitive', entry.edited or entry.deleted) if entry.canedit or entry.deleted: if entry.deleted: rend.set_property('icon-name', Icons.EDIT_DELETE) else: rend.set_property('icon-name', Icons.EDIT) else: rend.set_property('icon-name', Icons.CHANGES_PREVENT) column.set_cell_data_func(render, cdf_write) view.append_column(column) render = Gtk.CellRendererText() column = TreeViewColumn(title=_('Tag')) column.pack_start(render, True) def cell_data_tag(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.tag) cell.set_property("strikethrough", entry.deleted) column.set_cell_data_func(render, cell_data_tag) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render.set_property('editable', True) render.connect('edited', self.__edit_tag_name, model) render.connect( 'editing-started', self.__tag_editing_started, model, library) view.append_column(column) render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('editable', True) render.connect('edited', self.__edit_tag, model) render.connect( 'editing-started', self.__value_editing_started, model, library) column = TreeViewColumn(title=_('Value')) column.pack_start(render, True) def cell_data_value(column, cell, model, iter_, data): entry = model.get_value(iter_) markup = entry.value.get_markup() cell.markup = markup cell.set_property("markup", markup) cell.set_property("editable", entry.canedit) cell.set_property("strikethrough", entry.deleted) column.set_cell_data_func(render, cell_data_value) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.pack_start(sw, True, True, 0) cb = ConfigCheckButton( _("Show _programmatic tags"), 'editing', 'alltags', populate=True, tooltip=_("Access all tags, including machine-generated " "ones e.g. MusicBrainz or Replay Gain tags")) cb.connect('toggled', self.__all_tags_toggled) self.pack_start(cb, False, True, 0) # Add and Remove [tags] buttons buttonbox = Gtk.HBox(spacing=18) bbox1 = Gtk.HButtonBox() bbox1.set_spacing(6) bbox1.set_layout(Gtk.ButtonBoxStyle.START) add = qltk.Button(_("_Add"), Icons.LIST_ADD) add.set_focus_on_click(False) self._add = add add.connect('clicked', self.__add_tag, model, library) bbox1.pack_start(add, True, True, 0) # Remove button remove = qltk.Button(_("_Remove"), Icons.LIST_REMOVE) remove.set_focus_on_click(False) remove.connect('clicked', self.__remove_tag, view) remove.set_sensitive(False) self._remove = remove bbox1.pack_start(remove, True, True, 0) # Revert and save buttons # Both can have customised translated text (and thus accels) bbox2 = Gtk.HButtonBox() bbox2.set_spacing(6) bbox2.set_layout(Gtk.ButtonBoxStyle.END) # Translators: Revert button in the tag editor revert = Button(C_("edittags", "_Revert"), Icons.DOCUMENT_REVERT) self._revert = revert revert.set_sensitive(False) # Translators: Save button in the tag editor save = Button(C_("edittags", "_Save"), Icons.DOCUMENT_SAVE) save.set_sensitive(False) self._save = save bbox2.pack_start(revert, True, True, 0) bbox2.pack_start(save, True, True, 0) buttonbox.pack_start(bbox1, True, True, 0) buttonbox.pack_start(bbox2, True, True, 0) self.pack_start(buttonbox, False, True, 0) self._buttonbox = buttonbox parent.connect('changed', self.__parent_changed) revert.connect('clicked', lambda *x: self._update()) connect_obj(revert, 'clicked', parent.set_pending, None) save.connect('clicked', self.__save_files, revert, model, library) connect_obj(save, 'clicked', parent.set_pending, None) for sig in ['row-inserted', 'row-deleted', 'row-changed']: model.connect(sig, self.__enable_save, [save, revert]) connect_obj(model, sig, parent.set_pending, save) view.connect('popup-menu', self.__popup_menu, parent) view.connect('button-press-event', self.__button_press) view.connect('key-press-event', self.__view_key_press_event) selection.connect('changed', self.__tag_select, remove) selection.set_mode(Gtk.SelectionMode.MULTIPLE) self._parent = parent for child in self.get_children(): child.show_all()
def __init__(self, initial=None, filter=filesel_filter, folders=None): """ initial -- a path to a file which should be shown initially filter -- a function which filters paths shown in the file list folders -- list of shown folders in the directory tree """ super(FileSelector, self).__init__(orientation=Gtk.Orientation.VERTICAL) self.__filter = filter if initial is not None: assert isinstance(initial, fsnative) if initial and os.path.isfile(initial): initial = os.path.dirname(initial) dirlist = DirectoryTree(initial, folders=folders) model = ObjectStore() filelist = AllTreeView(model=model) filelist.connect("draw", self.__restore_scroll_pos_on_draw) column = TreeViewColumn(title=_("Songs")) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render = Gtk.CellRendererPixbuf() render.props.xpad = 3 def cell_icon(column, cell, model, iter_, userdata): value = model.get_value(iter_) if is_image(value): cell.set_property('icon-name', Icons.IMAGE_X_GENERIC) else: cell.set_property('icon-name', Icons.AUDIO_X_GENERIC) column.set_cell_data_func(render, cell_icon) column.pack_start(render, False) render = Gtk.CellRendererText() if filelist.supports_hints(): render.set_property('ellipsize', Pango.EllipsizeMode.END) column.pack_start(render, True) def cell_data(column, cell, model, iter_, userdata): value = model.get_value(iter_) cell.set_property('text', fsn2text(os.path.basename(value))) column.set_cell_data_func(render, cell_data) filelist.append_column(column) filelist.set_rules_hint(True) filelist.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) filelist.set_search_equal_func(search_func, False) filelist.set_search_column(0) self.__sig = filelist.get_selection().connect('changed', self.__changed) dirlist.get_selection().connect('changed', self.__dir_selection_changed, filelist) dirlist.get_selection().emit('changed') def select_all_files(view, path, col, fileselection): view.expand_row(path, False) fileselection.select_all() dirlist.connect('row-activated', select_all_files, filelist.get_selection()) sw = ScrolledWindow() sw.add(dirlist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack1(sw, resize=True) sw = ScrolledWindow() sw.add(filelist) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) self.pack2(sw, resize=True)
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))
def __init__(self): super().__init__(spacing=6) self.model = model = ObjectStore() self.view = view = RCMHintedTreeView(model=model) view.set_fixed_height_mode(True) view.set_headers_visible(False) menu = Gtk.Menu() remove_item = MenuItem(_("_Remove"), Icons.LIST_REMOVE) menu.append(remove_item) menu.show_all() view.connect('popup-menu', self.__popup, menu) connect_obj(remove_item, 'activate', self.__remove, view) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) sw.add(view) sw.set_size_request(-1, max(sw.size_request().height, 80)) sw.set_tooltip_text( _("Songs in the listed folders will be added " "to the library during a library refresh")) render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) def cdf(column, cell, model, iter_, data): path = model.get_value(iter_) cell.set_property('text', fsn2text(unexpand(path))) column = Gtk.TreeViewColumn(None, render) column.set_cell_data_func(render, cdf) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) view.append_column(column) add = Button(_("_Add"), Icons.LIST_ADD) add.set_tooltip_text( _("The new directory will be scanned after adding")) add.connect("clicked", self.__add) remove = Button(_("_Remove"), Icons.LIST_REMOVE) remove.set_tooltip_text( _("All songs in the selected directories " "will also be removed from the library")) move = Button(_("_Move"), Icons.EDIT_REDO) move.connect("clicked", self.__move) move.set_tooltip_text( _("Move a scan root (but not the files), " "migrating metadata for all included tracks.")) selection = view.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.connect("changed", self.__select_changed, remove, move) selection.emit("changed") connect_obj(remove, "clicked", self.__remove, view) vbox = Gtk.VBox(spacing=6) vbox.pack_start(add, False, True, 0) vbox.pack_start(remove, False, True, 0) vbox.pack_start(move, False, True, 0) self.pack_start(sw, True, True, 0) self.pack_start(vbox, False, True, 0) for path in get_scan_dirs(): model.append(row=[path]) for child in self.get_children(): child.show_all()
def __init__(self, parent, library): super(EditTags, self).__init__(spacing=12) self.title = _("Edit Tags") self.set_border_width(12) model = ObjectStore() view = RCMHintedTreeView(model=model) self._view = view selection = view.get_selection() render = Gtk.CellRendererPixbuf() column = TreeViewColumn() column.pack_start(render, True) column.set_fixed_width(24) column.set_expand(False) def cdf_write(col, rend, model, iter_, *args): entry = model.get_value(iter_) rend.set_property('sensitive', entry.edited or entry.deleted) if entry.canedit or entry.deleted: if entry.deleted: rend.set_property('icon-name', Icons.EDIT_DELETE) else: rend.set_property('icon-name', Icons.EDIT) else: rend.set_property('icon-name', Icons.CHANGES_PREVENT) column.set_cell_data_func(render, cdf_write) view.append_column(column) render = Gtk.CellRendererText() column = TreeViewColumn(title=_('Tag')) column.pack_start(render, True) def cell_data_tag(column, cell, model, iter_, data): entry = model.get_value(iter_) cell.set_property("text", entry.tag) cell.set_property("strikethrough", entry.deleted) column.set_cell_data_func(render, cell_data_tag) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) render.set_property('editable', True) render.connect('edited', self.__edit_tag_name, model) render.connect('editing-started', self.__tag_editing_started, model, library) view.append_column(column) render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) render.set_property('editable', True) render.connect('edited', self.__edit_tag, model) render.connect('editing-started', self.__value_editing_started, model, library) column = TreeViewColumn(title=_('Value')) column.pack_start(render, True) def cell_data_value(column, cell, model, iter_, data): entry = model.get_value(iter_) markup = entry.value.get_markup() cell.markup = markup cell.set_property("markup", markup) cell.set_property("editable", entry.canedit) cell.set_property("strikethrough", entry.deleted) column.set_cell_data_func(render, cell_data_value) column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.pack_start(sw, True, True, 0) cb = ConfigCheckButton( _("Show _programmatic tags"), 'editing', 'alltags', populate=True, tooltip=_("Access all tags, including machine-generated " "ones e.g. MusicBrainz or Replay Gain tags")) cb.connect('toggled', self.__all_tags_toggled) self.pack_start(cb, False, True, 0) # Add and Remove [tags] buttons buttonbox = Gtk.HBox(spacing=18) bbox1 = Gtk.HButtonBox() bbox1.set_spacing(6) bbox1.set_layout(Gtk.ButtonBoxStyle.START) add = qltk.Button(_("_Add"), Icons.LIST_ADD) add.set_focus_on_click(False) self._add = add add.connect('clicked', self.__add_tag, model, library) bbox1.pack_start(add, True, True, 0) # Remove button remove = qltk.Button(_("_Remove"), Icons.LIST_REMOVE) remove.set_focus_on_click(False) remove.connect('clicked', self.__remove_tag, view) remove.set_sensitive(False) self._remove = remove bbox1.pack_start(remove, True, True, 0) # Revert and save buttons # Both can have customised translated text (and thus accels) bbox2 = Gtk.HButtonBox() bbox2.set_spacing(6) bbox2.set_layout(Gtk.ButtonBoxStyle.END) # Translators: Revert button in the tag editor revert = Button(C_("edittags", "_Revert"), Icons.DOCUMENT_REVERT) self._revert = revert revert.set_sensitive(False) # Translators: Save button in the tag editor save = Button(C_("edittags", "_Save"), Icons.DOCUMENT_SAVE) save.set_sensitive(False) self._save = save bbox2.pack_start(revert, True, True, 0) bbox2.pack_start(save, True, True, 0) buttonbox.pack_start(bbox1, True, True, 0) buttonbox.pack_start(bbox2, True, True, 0) self.pack_start(buttonbox, False, True, 0) self._buttonbox = buttonbox parent.connect('changed', self.__parent_changed) revert.connect('clicked', lambda *x: self._update()) connect_obj(revert, 'clicked', parent.set_pending, None) save.connect('clicked', self.__save_files, revert, model, library) connect_obj(save, 'clicked', parent.set_pending, None) for sig in ['row-inserted', 'row-deleted', 'row-changed']: model.connect(sig, self.__enable_save, [save, revert]) connect_obj(model, sig, parent.set_pending, save) view.connect('popup-menu', self.__popup_menu, parent) view.connect('button-press-event', self.__button_press) view.connect('key-press-event', self.__view_key_press_event) selection.connect('changed', self.__tag_select, remove) selection.set_mode(Gtk.SelectionMode.MULTIPLE) self._parent = parent for child in self.get_children(): child.show_all()