def tag_editing_vbox(self): """Returns a new VBox containing all tag editing widgets""" vbox = Gtk.VBox(spacing=6) cb = CCB(_("Auto-save tag changes"), 'editing', 'auto_save_changes', populate=True, tooltip=_("Save changes to tags without confirmation " "when editing multiple files")) vbox.pack_start(cb, False, True, 0) hb = Gtk.HBox(spacing=6) e = UndoEntry() e.set_text(config.get("editing", "split_on")) e.connect('changed', self.__changed, 'editing', 'split_on') e.set_tooltip_text( _("A list of separators to use when splitting tag values. " "The list is space-separated")) def do_revert_split(button, section, option): config.reset(section, option) e.set_text(config.get(section, option)) split_revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT) split_revert.connect("clicked", do_revert_split, "editing", "split_on") l = Gtk.Label(label=_("Split _on:")) l.set_use_underline(True) l.set_mnemonic_widget(e) hb.pack_start(l, False, True, 0) hb.pack_start(e, True, True, 0) hb.pack_start(split_revert, False, True, 0) vbox.pack_start(hb, False, True, 0) return vbox
def __init__(self): super(ScanBox, self).__init__(spacing=6) self.model = model = Gtk.ListStore(str) view = RCMHintedTreeView(model=model) view.set_fixed_height_mode(True) view.set_headers_visible(False) view.set_tooltip_text(_("Songs in the listed folders will be added " "to the library during a library refresh")) 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)) render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) def cdf(column, cell, model, iter, data): row = model[iter] cell.set_property('text', unexpand(row[0])) 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.connect("clicked", self.__add) remove = Button(_("_Remove"), Icons.LIST_REMOVE) selection = view.get_selection() selection.set_mode(Gtk.SelectionMode.MULTIPLE) selection.connect("changed", self.__select_changed, remove) 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) self.pack_start(sw, True, True, 0) self.pack_start(vbox, False, True, 0) paths = map(fsdecode, get_scan_dirs()) for path in paths: model.append(row=[path]) for child in self.get_children(): child.show_all()
def add_icon_button(self, label, icon_name, response_id): """Like add_button() but allows to pass an icon name""" button = Button(label, icon_name) # file chooser uses grab_default() on this button.set_can_default(True) button.show() self.add_action_widget(button, response_id) return button
def __init__(self, parent): title = _("Uninitialized iPod") description = _("Do you want to create an empty database on this iPod?") super(ConfirmDBCreate, self).__init__(parent, title, description, buttons=Gtk.ButtonsType.NONE) self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) save_button = Button(_("_Create Database"), "system-run") save_button.show() self.add_action_widget(save_button, self.RESPONSE_CREATE) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, parent, failures): if self.is_not_unique(): return super(PluginErrorWindow, self).__init__() self.set_title(_("Plugin Errors")) self.set_border_width(12) self.set_transient_for(parent) self.set_default_size(520, 300) scrolledwin = Gtk.ScrolledWindow() vbox = Gtk.VBox(spacing=6) vbox.set_border_width(6) scrolledwin.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) scrolledwin.add_with_viewport(vbox) keys = failures.keys() show_expanded = len(keys) <= 3 for key in sorted(keys): expander = Gtk.Expander(label="<b>%s</b>" % util.escape(key)) expander.set_use_markup(True) if show_expanded: expander.set_expanded(True) # second line is always the __rescan line; don't show it message = failures[key][0:1] + failures[key][3:] failure = Gtk.Label(label=''.join(message).strip()) failure.set_alignment(0, 0) failure.set_padding(12, 6) failure.set_selectable(True) failure.set_line_wrap(True) vbox.pack_start(expander, False, True, 0) expander.add(failure) self.use_header_bar() if not self.has_close_button(): vbox2 = Gtk.VBox(spacing=12) close = Button(_("_Close"), Icons.WINDOW_CLOSE) close.connect('clicked', lambda *x: self.destroy()) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(close, True, True, 0) vbox2.pack_start(scrolledwin, True, True, 0) vbox2.pack_start(b, False, True, 0) self.add(vbox2) close.grab_focus() else: self.add(scrolledwin) self.get_child().show_all()
def __init__(self, filename=None, button=True, completion=None, accel_group=None): super(SearchBarBox, self).__init__(spacing=6) if filename is None: filename = os.path.join(const.USERDIR, "lists", "queries") combo = ComboBoxEntrySave(filename, count=8, validator=Query.is_valid_color, title=_("Saved Searches"), edit_title=_("Edit saved searches...")) combo.enable_clear_button() self.__refill_id = None self.__combo = combo entry = combo.child self.__entry = entry if completion: entry.set_completion(completion) self.connect('destroy', lambda w: w.__remove_timeout()) self.__sig = combo.connect('changed', self.__text_changed) entry.connect('clear', self.__filter_changed) entry.connect('backspace', self.__text_changed) entry.connect('populate-popup', self.__menu) entry.connect('activate', self.__filter_changed) entry.connect('activate', self.__save_search) entry.connect('focus-out-event', self.__save_search) label = gtk.Label(_("_Search:")) label.set_use_underline(True) label.connect('mnemonic-activate', self.__mnemonic_activate) label.set_mnemonic_widget(entry) self.pack_start(label, expand=False) self.pack_start(combo) # search button if button: search = Button(_("Search"), gtk.STOCK_FIND, size=gtk.ICON_SIZE_MENU) search.connect('clicked', self.__filter_changed) search.set_tooltip_text(_("Search your library")) self.pack_start(search, expand=False) if accel_group: key, mod = gtk.accelerator_parse("<ctrl>L") accel_group.connect_group(key, mod, 0, lambda *x: label.mnemonic_activate(True)) self.show_all()
def __init__(self, parent, path): title = _("File exists") fn_format = "<b>%s</b>" % util.escape(fsdecode(path)) description = _("Replace %(file-name)s?") % {"file-name": fn_format} super(ConfirmFileReplace, self).__init__( parent, title, description, buttons=Gtk.ButtonsType.NONE) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) save_button = Button(_("_Replace File"), "document-save") save_button.show() self.add_action_widget(save_button, self.RESPONSE_REPLACE) self.set_default_response(Gtk.ResponseType.CANCEL)
def create_apply_button(): vbox = Gtk.VBox(spacing=12) apply = Button(_("_Apply")) apply.set_tooltip_text( _("Apply current configuration to song list, " "adding new columns to the end")) apply.connect('clicked', self.__apply, buttons) # Apply on destroy, else config gets mangled self.connect('destroy', self.__apply, buttons) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(apply, True, True, 0) vbox.pack_start(b, True, True, 0) return vbox
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_transient_for(qltk.get_top_parent(browser)) self.set_default_size(350, 300) self.set_border_width(12) self.set_title(_("Paned Browser Preferences")) vbox = Gtk.VBox(spacing=12) column_modes = ColumnModes(browser) column_mode_frame = qltk.Frame(_("Column layout"), child=column_modes) editor = PatternEditor() editor.headers = get_headers() editor_frame = qltk.Frame(_("Column content"), child=editor) equal_width = ConfigCheckButton(_("Equal pane width"), "browsers", "equal_pane_width", populate=True) apply_ = Button(_("_Apply")) connect_obj(apply_, "clicked", self.__apply, editor, browser, False, equal_width) cancel = Button(_("_Cancel")) cancel.connect("clicked", lambda x: self.destroy()) box = Gtk.HButtonBox() box.set_spacing(6) box.set_layout(Gtk.ButtonBoxStyle.EDGE) box.pack_start(equal_width, True, True, 0) box.pack_start(apply_, False, False, 0) self.use_header_bar() if not self.has_close_button(): box.pack_start(cancel, True, True, 0) vbox.pack_start(column_mode_frame, False, False, 0) vbox.pack_start(editor_frame, True, True, 0) vbox.pack_start(box, False, True, 0) self.add(vbox) cancel.grab_focus() self.get_child().show_all()
def __init__(self, parent, plugin_name, count): title = ngettext("Run the plugin \"%(name)s\" on %(count)d album?", "Run the plugin \"%(name)s\" on %(count)d albums?", count) % (plugin_name, count) super(ConfirmMultiAlbumInvoke, self).__init__( get_top_parent(parent), title, "", buttons=Gtk.ButtonsType.NONE) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) delete_button = Button(_("_Run Plugin"), Gtk.STOCK_EXECUTE) delete_button.show() self.add_action_widget(delete_button, self.RESPONSE_INVOKE) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, parent): title = _("Set up library directories?") description = _("You don't have any music library set up. " "Would you like to do that now?") super(ConfirmLibDirSetup, self).__init__( parent, title, description, buttons=Gtk.ButtonsType.NONE) cancel_button = Button(_("_Not Now"), Gtk.STOCK_CANCEL) cancel_button.show() self.add_action_widget(cancel_button, Gtk.ResponseType.CANCEL) save_button = Gtk.Button(label=_("_Set Up"), use_underline=True) save_button.show() self.add_action_widget(save_button, self.RESPONSE_SETUP) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, parent, song): title = _("Tag may not be accurate") fn_format = "<b>%s</b>" % util.escape(fsdecode(song("~basename"))) description = _("%(file-name)s changed while the program was running. " "Saving without refreshing your library may " "overwrite other changes to the song.") % {"file-name": fn_format} super(OverwriteWarning, self).__init__( parent, title, description, buttons=Gtk.ButtonsType.NONE) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) save_button = Button(_("_Save"), "document-save") save_button.show() self.add_action_widget(save_button, self.RESPONSE_SAVE) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, library): super(AudioFeeds, self).__init__(spacing=6) self.set_orientation(Gtk.Orientation.VERTICAL) self.__view = view = AllTreeView() self.__render = render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) col = Gtk.TreeViewColumn("Audio Feeds", render) col.set_cell_data_func(render, AudioFeeds.cell_data) view.append_column(col) view.set_model(self.__feeds) view.set_rules_hint(True) view.set_headers_visible(False) 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) new = Button(_("_New"), Icons.LIST_ADD, Gtk.IconSize.MENU) new.connect('clicked', self.__new_feed) view.get_selection().connect('changed', self.__changed) view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) view.connect('popup-menu', self.__popup_menu) targets = [ ("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.connect('drag-data-received', self.__drag_data_received) view.connect('drag-motion', self.__drag_motion) view.connect('drag-leave', self.__drag_leave) connect_obj(self, 'destroy', self.__save, view) self.pack_start(Align(new, left=3, bottom=3), False, True, 0) for child in self.get_children(): child.show_all()
def __init__(self, parent, paths, description): title = ngettext( "Delete %(file_count)d file permanently?", "Delete %(file_count)d files permanently?", len(paths)) % { "file_count": len(paths), } super(DeleteDialog, self).__init__( get_top_parent(parent), title, description, buttons=Gtk.ButtonsType.NONE) area = self.get_message_area() exp = FileListExpander(paths) exp.show() area.pack_start(exp, False, True, 0) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) delete_button = Button(_("_Delete Files"), Gtk.STOCK_DELETE) delete_button.show() self.add_action_widget(delete_button, self.RESPONSE_DELETE) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_transient_for(qltk.get_top_parent(browser)) self.set_default_size(350, 300) self.set_border_width(12) self.set_title(_("Paned Browser Preferences")) vbox = Gtk.VBox(spacing=12) editor = PatternEditor() editor.headers = get_headers() apply_ = Button(_("_Apply")) connect_obj(apply_, "clicked", self.__apply, editor, browser, False) cancel = Button(_("_Cancel")) cancel.connect("clicked", lambda x: self.destroy()) box = Gtk.HButtonBox() box.set_spacing(6) box.set_layout(Gtk.ButtonBoxStyle.END) box.pack_start(apply_, True, True, 0) self.use_header_bar() if not self.has_close_button(): box.pack_start(cancel, True, True, 0) vbox.pack_start(editor, True, True, 0) vbox.pack_start(box, False, True, 0) self.add(vbox) cancel.grab_focus() self.get_child().show_all()
def __init__(self, parent, paths, description): title = ngettext( "Move %(file_count)d file to the trash?", "Move %(file_count)d files to the trash?", len(paths)) % { "file_count": len(paths), } super(TrashDialog, self).__init__( get_top_parent(parent), title, description, buttons=Gtk.ButtonsType.NONE) area = self.get_message_area() exp = FileListExpander(paths) exp.show() area.pack_start(exp, False, True, 0) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) trash_button = Button(_("_Move to Trash"), "user-trash") trash_button.show() self.add_action_widget(trash_button, self.RESPONSE_TRASH) self.set_default_response(Gtk.ResponseType.CANCEL)
def __init__(self, parent): if self.is_not_unique(): return super(PreferencesWindow, self).__init__() self.current_scan_dirs = get_scan_dirs() self.set_title(_("Preferences")) self.set_resizable(False) self.set_transient_for(qltk.get_top_parent(parent)) self.__notebook = notebook = qltk.Notebook() for Page in [self.SongList, self.Browsers, self.Player, self.Library, self.Tagging]: page = Page() page.show() notebook.append_page(page) page_name = config.get("memory", "prefs_page", "") self.set_page(page_name) def on_switch_page(notebook, page, page_num): config.set("memory", "prefs_page", page.name) notebook.connect("switch-page", on_switch_page) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', lambda x: x.destroy(), self) button_box = Gtk.HButtonBox() button_box.set_layout(Gtk.ButtonBoxStyle.END) button_box.pack_start(close, True, True, 0) self.use_header_bar() if self.has_close_button(): self.set_border_width(0) notebook.set_show_border(False) self.add(notebook) else: self.set_border_width(12) vbox = Gtk.VBox(spacing=12) vbox.pack_start(notebook, True, True, 0) vbox.pack_start(button_box, False, True, 0) self.add(vbox) connect_obj(self, 'destroy', PreferencesWindow.__destroy, self) self.get_child().show_all()
def set_plugin(self, plugin): label = self.desc if plugin is None: label.set_markup("") else: name = util.escape(plugin.name) category = category_of(plugin).lower() text = (f"<big><b>{name}</b> " f"<span alpha='40%'> – {category}</span>" f"</big>") markup = plugin.description_markup if markup: text += f"<span font='4'>\n\n</span>{markup}" label.set_markup(text) label.connect("activate-link", show_uri) frame = self.prefs if frame.get_child(): frame.get_child().destroy() if plugin is None: frame.hide() else: instance_or_cls = plugin.get_instance() or plugin.cls if plugin and hasattr(instance_or_cls, 'PluginPreferences'): try: prefs = instance_or_cls.PluginPreferences(self) except: util.print_exc() frame.hide() else: if isinstance(prefs, Gtk.Window): b = Button(_("_Preferences"), Icons.PREFERENCES_SYSTEM) connect_obj(b, 'clicked', Gtk.Window.show, prefs) connect_obj(b, 'destroy', Gtk.Window.destroy, prefs) frame.add(b) frame.get_child().set_border_width(6) else: frame.add(prefs) frame.show_all()
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_transient_for(qltk.get_top_parent(browser)) self.set_default_size(350, 300) self.set_border_width(12) self.set_title(_("Paned Browser Preferences")) vbox = Gtk.VBox(spacing=12) editor = PatternEditor() editor.headers = get_headers() equal_width = ConfigCheckButton(_("Equal pane width"), "browsers", "equal_pane_width", populate=True) apply_ = Button(_("_Apply")) connect_obj(apply_, "clicked", self.__apply, editor, browser, False, equal_width) cancel = Button(_("_Cancel")) cancel.connect("clicked", lambda x: self.destroy()) box = Gtk.HButtonBox() box.set_spacing(6) box.set_layout(Gtk.ButtonBoxStyle.EDGE) box.pack_start(equal_width, True, True, 0) box.pack_start(apply_, False, False, 0) self.use_header_bar() if not self.has_close_button(): box.pack_start(cancel, True, True, 0) vbox.pack_start(editor, True, True, 0) vbox.pack_start(box, False, True, 0) self.add(vbox) cancel.grab_focus() self.get_child().show_all()
def set_plugin(self, plugin): label = self.desc if plugin is None: label.set_markup("") else: name = util.escape(plugin.name) text = "<big><b>%s</b></big>" % name if plugin.description: text += "<span font='4'>\n\n</span>" text += util.escape(plugin.description) label.set_markup(text) frame = self.prefs if frame.get_child(): frame.get_child().destroy() if plugin is not None: instance_or_cls = plugin.get_instance() or plugin.cls if plugin and hasattr(instance_or_cls, 'PluginPreferences'): try: prefs = instance_or_cls.PluginPreferences(self) except: util.print_exc() frame.hide() else: if isinstance(prefs, Gtk.Window): b = Button(_("_Preferences"), Icons.PREFERENCES_SYSTEM) connect_obj(b, 'clicked', Gtk.Window.show, prefs) connect_obj(b, 'destroy', Gtk.Window.destroy, prefs) frame.add(b) frame.get_child().set_border_width(6) else: frame.add(prefs) frame.show_all() else: frame.hide()
def __init__(self, parent): if self.is_not_unique(): return super(PreferencesWindow, self).__init__() self.set_title(_("Ex Falso Preferences")) self.set_border_width(12) self.set_resizable(False) self.set_transient_for(parent) vbox = Gtk.VBox(spacing=6) hb = Gtk.HBox(spacing=6) e = UndoEntry() e.set_text(config.get("editing", "split_on")) e.connect('changed', self.__changed, 'editing', 'split_on') l = Gtk.Label(label=_("Split _on:")) l.set_use_underline(True) l.set_mnemonic_widget(e) hb.pack_start(l, False, True, 0) hb.pack_start(e, True, True, 0) vbox.pack_start(hb, False, True, 0) f = qltk.Frame(_("Tag Editing"), child=vbox) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', lambda x: x.destroy(), self) button_box = Gtk.HButtonBox() button_box.set_layout(Gtk.ButtonBoxStyle.END) button_box.pack_start(close, True, True, 0) main_vbox = Gtk.VBox(spacing=12) main_vbox.pack_start(f, True, True, 0) self.use_header_bar() if not self.has_close_button(): main_vbox.pack_start(button_box, False, True, 0) self.add(main_vbox) connect_obj(self, 'destroy', PreferencesWindow.__destroy, self) self.get_child().show_all()
class AlbumArtWindow(qltk.Window, PluginConfigMixin): """The main window including the search list""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION THUMB_SIZE = 50 def __init__(self, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set( Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): pbosf = model[iter_][0] set_renderer_from_pbosf(cell, pbosf) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = get_scale_factor(self) * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source']))}) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) self.search_field = Gtk.Entry() self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) self.search_field.connect('activate', self.start_search) widget_space = 5 search_hbox = Gtk.HBox(spacing=widget_space) search_hbox.pack_start(self.search_field, True, True, 0) search_hbox.pack_start(self.search_button, False, True, 0) self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_hbox, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) if songs[0]('albumartist'): text = songs[0]('albumartist') else: text = songs[0]('artist') text += ' - ' + songs[0]('album') self.set_text(text) self.start_search() def __drag_data_get(self, view, ctx, sel, tid, etime, treeselection): model, iter = treeselection.get_selected() if not iter: return cover = model.get_value(iter, 1) sel.set_uris([cover['cover']]) def start_search(self, *data): """Start the search using the text from the text entry""" text = self.search_field.get_text() if not text or self.search_lock: return self.search_lock = True self.search_button.set_sensitive(False) self.progress.set_fraction(0) self.progress.set_text(_(u'Searching…')) self.progress.show() self.liststore.clear() self.search = search = CoverSearch(self.__search_callback) for eng in engines: if self.config_get(CONFIG_ENG_PREFIX + eng['config_id'], True): search.add_engine(eng['class'], eng['replace']) search.start(text) # Focus the list self.treeview.grab_focus() self.connect("destroy", self.__destroy) def __destroy(self, *args): self.search.stop() def set_text(self, text): """set the text and move the cursor to the end""" self.search_field.set_text(text) self.search_field.emit('move-cursor', Gtk.MovementStep.BUFFER_ENDS, 0, False) def __select_callback(self, selection, image): model, iter = selection.get_selected() if not iter: return cover = model.get_value(iter, 1) image.set_cover(cover['cover']) def __add_cover_to_list(self, cover): try: pbloader = GdkPixbuf.PixbufLoader() pbloader.write(get_url(cover['thumbnail'])[0]) pbloader.close() scale_factor = get_scale_factor(self) size = self.THUMB_SIZE * scale_factor - scale_factor * 2 pixbuf = pbloader.get_pixbuf().scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR) pixbuf = add_border_widget(pixbuf, self, None, round=True) thumb = get_pbosf_for_pixbuf(self, pixbuf) except (GLib.GError, IOError): pass else: def append(data): self.liststore.append(data) GLib.idle_add(append, [thumb, cover]) def __search_callback(self, covers, progress): for cover in covers: self.__add_cover_to_list(cover) if self.progress.get_fraction() < progress: self.progress.set_fraction(progress) if progress >= 1: self.progress.set_text(_('Done')) GLib.timeout_add(700, self.progress.hide) self.search_button.set_sensitive(True) self.search_lock = False
def __init__(self): super(PatternEditor, self).__init__(spacing=6) self.__headers = headers = {} buttons = [] group = None for tags in self.PRESETS: tied = "~" + "~".join(tags) group = Gtk.RadioButton(group=group, label="_" + util.tag(tied), use_underline=True) headers[group] = tags buttons.append(group) group = Gtk.RadioButton(group=group, label=_("_Custom"), use_underline=True) self.__custom = group headers[group] = [] buttons.append(group) button_box = Gtk.HBox(spacing=6) self.__model = model = Gtk.ListStore(str) radio_box = Gtk.VBox(spacing=6) for button in buttons: radio_box.pack_start(button, False, True, 0) button.connect('toggled', self.__toggled, button_box, model) self.pack_start(radio_box, False, True, 0) tooltip = _("Tag pattern with optional markup " "e.g. <tt>composer</tt> or\n<tt>%s</tt>" % escape(self._COMPLEX_PATTERN_EXAMPLE)) cb = TagsComboBoxEntry(self.COMPLETION, tooltip_markup=tooltip) view = BaseView(model=model) view.set_reorderable(True) view.set_headers_visible(False) ctrl_box = Gtk.VBox(spacing=6) add = Button(_("_Add"), Icons.LIST_ADD) ctrl_box.pack_start(add, False, True, 0) add.connect('clicked', self.__add, model, cb) remove = Button(_("_Remove"), Icons.LIST_REMOVE) ctrl_box.pack_start(remove, False, True, 0) remove.connect('clicked', self.__remove, view) selection = view.get_selection() selection.connect('changed', self.__selection_changed, remove) selection.emit('changed') sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.set_shadow_type(Gtk.ShadowType.IN) sw.add(view) edit_box = Gtk.VBox(spacing=6) edit_box.pack_start(cb, False, True, 0) edit_box.pack_start(sw, True, True, 0) button_box.pack_start(edit_box, True, True, 0) button_box.pack_start(ctrl_box, False, True, 0) self.pack_start(button_box, True, True, 0) render = Gtk.CellRendererText() render.set_property("editable", True) def edited_cb(render, path, text, model): model[path][0] = text render.connect("edited", edited_cb, model) column = Gtk.TreeViewColumn(None, render, text=0) view.append_column(column)
class AlbumArtWindow(qltk.Window, PluginConfigMixin): """The main window including the search list""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION THUMB_SIZE = 50 def __init__(self, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source'])) }) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) self.search_field = Gtk.Entry() self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) self.search_field.connect('activate', self.start_search) widget_space = 5 search_hbox = Gtk.HBox(spacing=widget_space) search_hbox.pack_start(self.search_field, True, True, 0) search_hbox.pack_start(self.search_button, False, True, 0) self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_hbox, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search() def __drag_data_get(self, view, ctx, sel, tid, etime, treeselection): model, iter = treeselection.get_selected() if not iter: return cover = model.get_value(iter, 1) sel.set_uris([cover['cover']]) def start_search(self, *data): """Start the search using the text from the text entry""" text = self.search_field.get_text() if not text or self.search_lock: return self.search_lock = True self.search_button.set_sensitive(False) self.progress.set_fraction(0) self.progress.set_text(_(u'Searching…')) self.progress.show() self.liststore.clear() self.search = search = CoverSearch(self.__search_callback) for eng in engines: if self.config_get_bool(CONFIG_ENG_PREFIX + eng['config_id'], True): search.add_engine(eng['class'], eng['replace']) search.start(text) # Focus the list self.treeview.grab_focus() self.connect("destroy", self.__destroy) def __destroy(self, *args): self.search.stop() def set_text(self, text): """set the text and move the cursor to the end""" self.search_field.set_text(text) self.search_field.emit('move-cursor', Gtk.MovementStep.BUFFER_ENDS, 0, False) def __select_callback(self, selection, image): model, iter = selection.get_selected() if not iter: return cover = model.get_value(iter, 1) image.set_cover(cover['cover']) def __add_cover_to_list(self, cover): try: pbloader = GdkPixbuf.PixbufLoader() pbloader.write(get_url(cover['thumbnail'])) pbloader.close() scale_factor = self.get_scale_factor() size = self.THUMB_SIZE * scale_factor - scale_factor * 2 pixbuf = pbloader.get_pixbuf().scale_simple( size, size, GdkPixbuf.InterpType.BILINEAR) pixbuf = add_border_widget(pixbuf, self) surface = get_surface_for_pixbuf(self, pixbuf) except (GLib.GError, IOError): pass else: def append(data): self.liststore.append(data) GLib.idle_add(append, [surface, cover]) def __search_callback(self, covers, progress): for cover in covers: self.__add_cover_to_list(cover) if self.progress.get_fraction() < progress: self.progress.set_fraction(progress) if progress >= 1: self.progress.set_text(_('Done')) GLib.timeout_add(700, self.progress.hide) self.search_button.set_sensitive(True) self.search_lock = False
def __init__(self, parent, song): super(CoverArea, self).__init__() self.song = song self.dirname = song("~dirname") self.main_win = parent self.data_cache = [] self.current_data = None self.current_pixbuf = None self.image = Gtk.Image() self.button = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.button.set_sensitive(False) self.button.connect('clicked', self.__save) close_button = Button(_("_Close"), Icons.WINDOW_CLOSE) close_button.connect('clicked', lambda x: self.main_win.destroy()) self.window_fit = self.ConfigCheckButton(_('Fit image to _window'), 'fit', True) self.window_fit.connect('toggled', self.__scale_pixbuf) self.name_combo = Gtk.ComboBoxText() self.cmd = qltk.entry.ValidatingEntry(iscommand) # Both labels label_open = Gtk.Label(label=_('_Program:')) label_open.set_use_underline(True) label_open.set_mnemonic_widget(self.cmd) label_open.set_justify(Gtk.Justification.LEFT) self.open_check = self.ConfigCheckButton(_('_Edit image after saving'), 'edit', False) label_name = Gtk.Label(label=_('File_name:'), use_underline=True) label_name.set_use_underline(True) label_name.set_mnemonic_widget(self.name_combo) label_name.set_justify(Gtk.Justification.LEFT) self.cmd.set_text(self.config_get('edit_cmd', 'gimp')) # Create the filename combo box fn_list = ['cover.jpg', 'folder.jpg', '.folder.jpg'] # Issue 374 - add dynamic file names artist = song("artist") alartist = song("albumartist") album = song("album") labelid = song("labelid") if album: fn_list.append("<album>.jpg") if alartist: fn_list.append("<albumartist> - <album>.jpg") else: fn_list.append("<artist> - <album>.jpg") else: print_w(u"No album for \"%s\". Could be difficult " u"finding art…" % song("~filename")) title = song("title") if title and artist: fn_list.append("<artist> - <title>.jpg") if labelid: fn_list.append("<labelid>.jpg") set_fn = self.config_get('fn', fn_list[0]) for i, fn in enumerate(fn_list): self.name_combo.append_text(fn) if fn == set_fn: self.name_combo.set_active(i) if self.name_combo.get_active() < 0: self.name_combo.set_active(0) table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False) table.set_row_spacing(0, 5) table.set_row_spacing(1, 5) table.set_col_spacing(0, 5) table.set_col_spacing(1, 5) table.attach(label_open, 0, 1, 0, 1) table.attach(label_name, 0, 1, 1, 2) table.attach(self.cmd, 1, 2, 0, 1) table.attach(self.name_combo, 1, 2, 1, 2) self.scrolled = Gtk.ScrolledWindow() self.scrolled.add_with_viewport(self.image) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.button, True, True, 0) bbox.pack_start(close_button, True, True, 0) bb_align = Align(valign=Gtk.Align.END, right=6) bb_align.add(bbox) main_hbox = Gtk.HBox() main_hbox.pack_start(table, False, True, 6) main_hbox.pack_start(bb_align, True, True, 0) top_hbox = Gtk.HBox() top_hbox.pack_start(self.open_check, True, True, 0) top_hbox.pack_start(self.window_fit, False, True, 0) main_vbox = Gtk.VBox() main_vbox.pack_start(top_hbox, True, True, 2) main_vbox.pack_start(main_hbox, True, True, 0) self.pack_start(self.scrolled, True, True, 0) self.pack_start(main_vbox, False, True, 5) # 5 MB image cache size self.max_cache_size = 1024 * 1024 * 5 # For managing fast selection switches of covers.. self.stop_loading = False self.loading = False self.current_job = 0 self.connect('destroy', self.__save_config)
def __init__(self, Prototype, values, filename, title): if self.is_not_unique(): return super(JSONBasedEditor, self).__init__() self.Prototype = Prototype self.current = None self.filename = filename self.name = Prototype.NAME or Prototype.__name__ self.input_entries = {} self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) self.add(Gtk.HBox(spacing=6)) self.get_child().set_homogeneous(True) self.accels = Gtk.AccelGroup() # Set up the model for this widget self.model = Gtk.ListStore(object) self._fill_values(values) # The browser for existing data self.view = view = RCMHintedTreeView(model=self.model) view.set_headers_visible(False) view.set_reorderable(True) view.set_rules_hint(True) render = Gtk.CellRendererText() render.set_padding(3, 6) render.props.ellipsize = Pango.EllipsizeMode.END column = Gtk.TreeViewColumn("", render) column.set_cell_data_func(render, self.__cdf) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.get_child().pack_start(sw, True, True, 0) vbox = Gtk.VBox(spacing=6) # Input for new ones. frame = self.__build_input_frame() vbox.pack_start(frame, False, True, 0) # Add context menu menu = Gtk.Menu() rem = MenuItem(_("_Remove"), Icons.LIST_REMOVE) keyval, mod = Gtk.accelerator_parse("Delete") rem.add_accelerator('activate', self.accels, keyval, mod, Gtk.AccelFlags.VISIBLE) connect_obj(rem, 'activate', self.__remove, view) menu.append(rem) menu.show_all() view.connect('popup-menu', self.__popup, menu) view.connect('key-press-event', self.__view_key_press) connect_obj(self, 'destroy', Gtk.Menu.destroy, menu) # New and Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) self.new_but = Button(_("_New"), Icons.DOCUMENT_NEW) self.new_but.connect('clicked', self._new_item) bbox.pack_start(self.new_but, True, True, 0) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.pack_start(close, True, True, 0) vbox.pack_end(bbox, False, True, 0) self.get_child().pack_start(vbox, True, True, 0) # Initialise self.selection = view.get_selection() self.selection.connect('changed', self.__select) self.connect('destroy', self.__finish) self.get_child().show_all()
def __init__(self): super(PreferencesWindow.SongList, self).__init__(spacing=12) self.set_border_width(12) self.title = _("Song List") # Behaviour vbox = Gtk.VBox(spacing=6) c = CCB(_("_Jump to playing song automatically"), 'settings', 'jump', populate=True, tooltip=_("When the playing song changes, " "scroll to it in the song list")) vbox.pack_start(c, False, True, 0) frame = qltk.Frame(_("Behavior"), child=vbox) self.pack_start(frame, False, True, 0) # Columns vbox = Gtk.VBox(spacing=12) buttons = {} table = Gtk.Table.new(3, 3, True) for i, (k, t) in enumerate(self.PREDEFINED_TAGS): x, y = i % 3, i / 3 buttons[k] = Gtk.CheckButton(label=t, use_underline=True) table.attach(buttons[k], x, x + 1, y, y + 1) vbox.pack_start(table, False, True, 0) # Other columns hbox = Gtk.HBox(spacing=6) l = Gtk.Label(label=_("_Others:"), use_underline=True) hbox.pack_start(l, False, True, 0) self.others = others = UndoEntry() others.set_sensitive(False) # Stock edit doesn't have ellipsis chars. edit_button = Gtk.Button( label=_(u"_Edit…"), use_underline=True) edit_button.connect("clicked", self.__config_cols, buttons) edit_button.set_tooltip_text(_("Add or remove additional column " "headers")) l.set_mnemonic_widget(edit_button) l.set_use_underline(True) hbox.pack_start(others, True, True, 0) vbox.pack_start(hbox, False, True, 0) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(edit_button, True, True, 0) vbox.pack_start(b, True, True, 0) frame = qltk.Frame(_("Visible Columns"), child=vbox) self.pack_start(frame, False, True, 0) # Column preferences tiv = Gtk.CheckButton(label=_("Title includes _version"), use_underline=True) aio = Gtk.CheckButton(label=_("Artist includes all _people"), use_underline=True) aip = Gtk.CheckButton(label=_("Album includes _disc subtitle"), use_underline=True) fip = Gtk.CheckButton(label=_("Filename includes _folder"), use_underline=True) self._toggle_data = [ (tiv, "title", "~title~version"), (aip, "album", "~album~discsubtitle"), (fip, "~basename", "~filename"), (aio, "artist", "~people") ] self.__update(buttons, self._toggle_data, get_columns()) t = Gtk.Table.new(2, 2, True) t.attach(tiv, 0, 1, 0, 1) t.attach(aip, 0, 1, 1, 2) t.attach(aio, 1, 2, 0, 1) t.attach(fip, 1, 2, 1, 2) frame = qltk.Frame(_("Column Preferences"), child=t) self.pack_start(frame, False, True, 0) # Apply button vbox = Gtk.VBox(spacing=12) apply = Button(_("_Apply")) apply.set_tooltip_text(_("Apply current configuration to song " "list, adding new columns to the end")) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(apply, True, True, 0) vbox.pack_start(b, True, True, 0) self.pack_start(vbox, True, True, 0) apply.connect('clicked', self.__apply, buttons) # Apply on destroy, else config gets mangled self.connect('destroy', self.__apply, buttons) for child in self.get_children(): child.show_all()
class JSONBasedEditor(qltk.UniqueWindow): """ Flexible editor for objects extending `JSONObject` (held in a `JSONObjectDict`) TODO: validation, especially for name. """ _WIDTH = 800 _HEIGHT = 400 def __init__(self, Prototype, values, filename, title): if self.is_not_unique(): return super(JSONBasedEditor, self).__init__() self.Prototype = Prototype self.current = None self.filename = filename self.name = Prototype.NAME or Prototype.__name__ self.input_entries = {} self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) self.add(Gtk.HBox(spacing=6)) self.get_child().set_homogeneous(True) self.accels = Gtk.AccelGroup() # Set up the model for this widget self.model = Gtk.ListStore(object) self._fill_values(values) # The browser for existing data self.view = view = RCMHintedTreeView(model=self.model) view.set_headers_visible(False) view.set_reorderable(True) view.set_rules_hint(True) render = Gtk.CellRendererText() render.set_padding(3, 6) render.props.ellipsize = Pango.EllipsizeMode.END column = Gtk.TreeViewColumn("", render) column.set_cell_data_func(render, self.__cdf) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.get_child().pack_start(sw, True, True, 0) vbox = Gtk.VBox(spacing=6) # Input for new ones. frame = self.__build_input_frame() vbox.pack_start(frame, False, True, 0) # Add context menu menu = Gtk.Menu() rem = MenuItem(_("_Remove"), Icons.LIST_REMOVE) keyval, mod = Gtk.accelerator_parse("Delete") rem.add_accelerator( 'activate', self.accels, keyval, mod, Gtk.AccelFlags.VISIBLE) connect_obj(rem, 'activate', self.__remove, view) menu.append(rem) menu.show_all() view.connect('popup-menu', self.__popup, menu) view.connect('key-press-event', self.__view_key_press) connect_obj(self, 'destroy', Gtk.Menu.destroy, menu) # New and Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) self.new_but = Button(_("_New"), Icons.DOCUMENT_NEW) self.new_but.connect('clicked', self._new_item) bbox.pack_start(self.new_but, True, True, 0) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.pack_start(close, True, True, 0) vbox.pack_end(bbox, False, True, 0) self.get_child().pack_start(vbox, True, True, 0) # Initialise self.selection = view.get_selection() self.selection.connect('changed', self.__select) self.connect('destroy', self.__finish) self.get_child().show_all() def _find(self, name): for row in self.model: if row[0].name == name: return row[0] def _new_item(self, button): current_name = name = _("New %s") % self.name n = 2 while True: if self._find(current_name): current_name = "%s (%d)" % (name, n) n += 1 continue break self.model.append(row=(self.Prototype(name=current_name),)) def _new_widget(self, key, val): """ Creates a Gtk.Entry subclass appropriate for a field named `key` with value `val` """ callback = signal = None if isinstance(val, bool): entry = Gtk.CheckButton() callback = self.__toggled_widget signal = "toggled" elif isinstance(val, int): adj = Gtk.Adjustment.new(0, 0, 9999, 1, 10, 0) entry = Gtk.SpinButton(adjustment=adj) entry.set_numeric(True) callback = self.__changed_numeric_widget elif "pattern" in key: entry = ValidatingEntry(validator=Query.validator) else: entry = UndoEntry() entry.connect(signal or "changed", callback or self.__changed_widget, key) return entry def __refresh_view(self): model, iter = self.selection.get_selected() self.model.emit("row-changed", model[iter].path, iter) def __changed_widget(self, entry, key): if self.current: setattr(self.current, key, str(entry.get_text())) self.__refresh_view() def __changed_numeric_widget(self, entry, key): if self.current: setattr(self.current, key, int(entry.get_text() or 0)) self.__refresh_view() def __toggled_widget(self, entry, key): if self.current: setattr(self.current, key, bool(entry.get_active())) self.__refresh_view() def _populate_fields(self, obj): """Populates the input fields based on the `JSONData` object `obj`""" for fn, val in obj.data: widget = self.input_entries[fn] widget.set_sensitive(True) # TODO: link this logic better with the creational stuff if isinstance(val, bool): widget.set_active(val) elif isinstance(val, int): widget.set_value(int(val)) elif isinstance(val, basestring): widget.set_text(val or "") def __build_input_frame(self): t = Gtk.Table(n_rows=2, n_columns=3) t.set_row_spacings(6) t.set_col_spacing(0, 3) t.set_col_spacing(1, 12) empty = self.Prototype("empty") for i, (key, val) in enumerate(empty.data): field = empty.field(key) field_name = self.get_field_name(field, key) l = Gtk.Label(label=field_name + ":") entry = self._new_widget(key, val) entry.set_sensitive(False) if field.doc: entry.set_tooltip_text(field.doc) # Store these away in a map for later access self.input_entries[key] = entry l.set_mnemonic_widget(entry) l.set_use_underline(True) l.set_alignment(0.0, 0.5) if isinstance(val, int) or isinstance(val, bool): align = Align(entry, halign=Gtk.Align.START) t.attach(align, 1, 2, i, i + 1) else: t.attach(entry, 1, 2, i, i + 1) t.attach(l, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL) frame = qltk.Frame(label=self.name, child=t) self.input_entries["name"].grab_focus() return frame @staticmethod def get_field_name(field, key): field_name = (field.human_name or (key and key.replace("_", " "))) return field_name and field_name.title() or _("(unknown)") def _fill_values(self, data): if not data: return for (name, obj) in data.items(): self.model.prepend(row=[obj]) def _update_current(self, new_selection=None): if new_selection: self.selection = new_selection model, iter = self.selection.get_selected() if iter: self.current = model[iter][0] def __select(self, selection): self._update_current(selection) self.remove_but.set_sensitive(bool(iter)) if iter is not None: self._populate_fields(self.current) def __remove(self, view): view.remove_selection() def __popup(self, view, menu): return view.popup_menu(menu, 0, Gtk.get_current_event_time()) def __view_key_press(self, view, event): if event.keyval == Gtk.accelerator_parse("Delete")[0]: self.__remove(view) def __cdf(self, column, cell, model, iter, data): row = model[iter] obj = row[0] obj_name = util.escape(obj.name) obj_description = util.escape(str(obj)) markup = '<b>%s</b>\n%s' % (obj_name, obj_description) cell.markup = markup cell.set_property('markup', markup) def __finish(self, widget): # TODO: Warn about impending deletion of nameless items, or something all = JSONObjectDict.from_list( [row[0] for row in self.model if row[0].name]) all.save(filename=self.filename)
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()
class CoverArea(Gtk.VBox, PluginConfigMixin): """The image display and saving part.""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION def __init__(self, parent, song): super(CoverArea, self).__init__() self.song = song self.dirname = song("~dirname") self.main_win = parent self.data_cache = [] self.current_data = None self.current_pixbuf = None self.image = Gtk.Image() self.button = Button(_("_Save"), Icons.DOCUMENT_SAVE) self.button.set_sensitive(False) self.button.connect('clicked', self.__save) close_button = Button(_("_Close"), Icons.WINDOW_CLOSE) close_button.connect('clicked', lambda x: self.main_win.destroy()) self.window_fit = self.ConfigCheckButton(_('Fit image to _window'), 'fit', True) self.window_fit.connect('toggled', self.__scale_pixbuf) self.name_combo = Gtk.ComboBoxText() self.cmd = qltk.entry.ValidatingEntry(iscommand) # Both labels label_open = Gtk.Label(label=_('_Program:')) label_open.set_use_underline(True) label_open.set_mnemonic_widget(self.cmd) label_open.set_justify(Gtk.Justification.LEFT) self.open_check = self.ConfigCheckButton(_('_Edit image after saving'), 'edit', False) label_name = Gtk.Label(label=_('File_name:'), use_underline=True) label_name.set_use_underline(True) label_name.set_mnemonic_widget(self.name_combo) label_name.set_justify(Gtk.Justification.LEFT) self.cmd.set_text(self.config_get('edit_cmd', 'gimp')) # Create the filename combo box fn_list = ['cover.jpg', 'folder.jpg', '.folder.jpg'] # Issue 374 - add dynamic file names artist = song("artist") alartist = song("albumartist") album = song("album") labelid = song("labelid") if album: fn_list.append("<album>.jpg") if alartist: fn_list.append("<albumartist> - <album>.jpg") else: fn_list.append("<artist> - <album>.jpg") else: print_w(u"No album for \"%s\". Could be difficult " u"finding art…" % song("~filename")) title = song("title") if title and artist: fn_list.append("<artist> - <title>.jpg") if labelid: fn_list.append("<labelid>.jpg") set_fn = self.config_get('fn', fn_list[0]) for i, fn in enumerate(fn_list): self.name_combo.append_text(fn) if fn == set_fn: self.name_combo.set_active(i) if self.name_combo.get_active() < 0: self.name_combo.set_active(0) table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False) table.set_row_spacing(0, 5) table.set_row_spacing(1, 5) table.set_col_spacing(0, 5) table.set_col_spacing(1, 5) table.attach(label_open, 0, 1, 0, 1) table.attach(label_name, 0, 1, 1, 2) table.attach(self.cmd, 1, 2, 0, 1) table.attach(self.name_combo, 1, 2, 1, 2) self.scrolled = Gtk.ScrolledWindow() self.scrolled.add_with_viewport(self.image) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.button, True, True, 0) bbox.pack_start(close_button, True, True, 0) bb_align = Align(valign=Gtk.Align.END, right=6) bb_align.add(bbox) main_hbox = Gtk.HBox() main_hbox.pack_start(table, False, True, 6) main_hbox.pack_start(bb_align, True, True, 0) top_hbox = Gtk.HBox() top_hbox.pack_start(self.open_check, True, True, 0) top_hbox.pack_start(self.window_fit, False, True, 0) main_vbox = Gtk.VBox() main_vbox.pack_start(top_hbox, True, True, 2) main_vbox.pack_start(main_hbox, True, True, 0) self.pack_start(self.scrolled, True, True, 0) self.pack_start(main_vbox, False, True, 5) # 5 MB image cache size self.max_cache_size = 1024 * 1024 * 5 # For managing fast selection switches of covers.. self.stop_loading = False self.loading = False self.current_job = 0 self.connect('destroy', self.__save_config) def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" save_format = self.name_combo.get_active_text() # Allow use of patterns in creating cover filenames pattern = ArbitraryExtensionFileFromPattern( save_format.decode("utf-8")) filename = pattern.format(self.song) print_d("Using '%s' as filename based on %s" % (filename, save_format)) file_path = os.path.join(self.dirname, filename) if os.path.exists(file_path): resp = ConfirmFileReplace(self, file_path).run() if resp != ConfirmFileReplace.RESPONSE_REPLACE: return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.cover_manager.cover_changed([self.song]) self.main_win.destroy() def __save_config(self, widget): self.config_set('edit_cmd', self.cmd.get_text()) self.config_set('fn', self.name_combo.get_active_text()) def __update(self, loader, *data): """Update the picture while it's loading""" if self.stop_loading: return pixbuf = loader.get_pixbuf() def idle_set(): set_image_from_pbosf(self.image, pixbuf) GLib.idle_add(idle_set) def __scale_pixbuf(self, *data): if not self.current_pixbuf: return pixbuf = self.current_pixbuf if not self.window_fit.get_active(): pbosf = pixbuf else: alloc = self.scrolled.get_allocation() width = alloc.width height = alloc.height scale_factor = get_scale_factor(self) boundary = (width * scale_factor, height * scale_factor) pixbuf = scale(pixbuf, boundary, scale_up=False) pbosf = get_pbosf_for_pixbuf(self, pixbuf) set_image_from_pbosf(self.image, pbosf) def __close(self, loader, *data): if self.stop_loading: return self.current_pixbuf = loader.get_pixbuf() GLib.idle_add(self.__scale_pixbuf) def set_cover(self, url): thr = threading.Thread(target=self.__set_async, args=(url,)) thr.setDaemon(True) thr.start() def __set_async(self, url): """Manages various things: Fast switching of covers (aborting old HTTP requests), The image cache, etc.""" self.current_job += 1 job = self.current_job self.stop_loading = True while self.loading: time.sleep(0.05) self.stop_loading = False if job != self.current_job: return self.loading = True GLib.idle_add(self.button.set_sensitive, False) self.current_pixbuf = None pbloader = GdkPixbuf.PixbufLoader() pbloader.connect('closed', self.__close) # Look for cached images raw_data = None for entry in self.data_cache: if entry[0] == url: raw_data = entry[1] break if not raw_data: pbloader.connect('area-updated', self.__update) data_store = StringIO() try: request = urllib2.Request(url) request.add_header('User-Agent', USER_AGENT) url_sock = urllib2.urlopen(request) except urllib2.HTTPError: print_w(_("[albumart] HTTP Error: %s") % url) else: while not self.stop_loading: tmp = url_sock.read(1024 * 10) if not tmp: break pbloader.write(tmp) data_store.write(tmp) url_sock.close() if not self.stop_loading: raw_data = data_store.getvalue() self.data_cache.insert(0, (url, raw_data)) while 1: cache_sizes = [len(data[1]) for data in self.data_cache] if sum(cache_sizes) > self.max_cache_size: del self.data_cache[-1] else: break data_store.close() else: # Sleep for fast switching of cached images time.sleep(0.05) if not self.stop_loading: pbloader.write(raw_data) try: pbloader.close() except GLib.GError: pass self.current_data = raw_data if not self.stop_loading: GLib.idle_add(self.button.set_sensitive, True) self.loading = False
def __init__(self): super(PreferencesWindow.SongList, self).__init__(spacing=12) self.set_border_width(12) self.title = _("Song List") # Behaviour vbox = Gtk.VBox(spacing=6) c = CCB(_("_Jump to playing song automatically"), 'settings', 'jump', populate=True, tooltip=_("When the playing song changes, " "scroll to it in the song list")) vbox.pack_start(c, False, True, 0) frame = qltk.Frame(_("Behavior"), child=vbox) self.pack_start(frame, False, True, 0) # Columns vbox = Gtk.VBox(spacing=12) buttons = {} table = Gtk.Table.new(3, 3, True) for i, (k, t) in enumerate(self.PREDEFINED_TAGS): x, y = i % 3, i / 3 buttons[k] = Gtk.CheckButton(label=t, use_underline=True) table.attach(buttons[k], x, x + 1, y, y + 1) vbox.pack_start(table, False, True, 0) # Other columns hbox = Gtk.HBox(spacing=6) l = Gtk.Label(label=_("_Others:"), use_underline=True) hbox.pack_start(l, False, True, 0) self.others = others = UndoEntry() others.set_sensitive(False) # Stock edit doesn't have ellipsis chars. edit_button = Gtk.Button(label=_(u"_Edit…"), use_underline=True) edit_button.connect("clicked", self.__config_cols, buttons) edit_button.set_tooltip_text( _("Add or remove additional column " "headers")) l.set_mnemonic_widget(edit_button) l.set_use_underline(True) hbox.pack_start(others, True, True, 0) vbox.pack_start(hbox, False, True, 0) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(edit_button, True, True, 0) vbox.pack_start(b, True, True, 0) frame = qltk.Frame(_("Visible Columns"), child=vbox) self.pack_start(frame, False, True, 0) # Column preferences tiv = Gtk.CheckButton(label=_("Title includes _version"), use_underline=True) aio = Gtk.CheckButton(label=_("Artist includes all _people"), use_underline=True) aip = Gtk.CheckButton(label=_("Album includes _disc subtitle"), use_underline=True) fip = Gtk.CheckButton(label=_("Filename includes _folder"), use_underline=True) self._toggle_data = [(tiv, "title", "~title~version"), (aip, "album", "~album~discsubtitle"), (fip, "~basename", "~filename"), (aio, "artist", "~people")] self.__update(buttons, self._toggle_data, get_columns()) t = Gtk.Table.new(2, 2, True) t.attach(tiv, 0, 1, 0, 1) t.attach(aip, 0, 1, 1, 2) t.attach(aio, 1, 2, 0, 1) t.attach(fip, 1, 2, 1, 2) frame = qltk.Frame(_("Column Preferences"), child=t) self.pack_start(frame, False, True, 0) # Apply button vbox = Gtk.VBox(spacing=12) apply = Button(_("_Apply")) apply.set_tooltip_text( _("Apply current configuration to song " "list, adding new columns to the end")) b = Gtk.HButtonBox() b.set_layout(Gtk.ButtonBoxStyle.END) b.pack_start(apply, True, True, 0) vbox.pack_start(b, True, True, 0) self.pack_start(vbox, True, True, 0) apply.connect('clicked', self.__apply, buttons) # Apply on destroy, else config gets mangled self.connect('destroy', self.__apply, buttons) for child in self.get_children(): child.show_all()
def __init__(self, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set( Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): pbosf = model[iter_][0] set_renderer_from_pbosf(cell, pbosf) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = get_scale_factor(self) * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source']))}) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) self.search_field = Gtk.Entry() self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) self.search_field.connect('activate', self.start_search) widget_space = 5 search_hbox = Gtk.HBox(spacing=widget_space) search_hbox.pack_start(self.search_field, True, True, 0) search_hbox.pack_start(self.search_button, False, True, 0) self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_hbox, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) if songs[0]('albumartist'): text = songs[0]('albumartist') else: text = songs[0]('artist') text += ' - ' + songs[0]('album') self.set_text(text) self.start_search()
def __init__(self, Prototype, values, filename, title): if self.is_not_unique(): return super(JSONBasedEditor, self).__init__() self.Prototype = Prototype self.current = None self.filename = filename self.name = Prototype.NAME or Prototype.__name__ self.input_entries = {} self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) self.add(Gtk.HBox(spacing=6)) self.get_child().set_homogeneous(True) self.accels = Gtk.AccelGroup() # Set up the model for this widget self.model = Gtk.ListStore(object) self._fill_values(values) # The browser for existing data self.view = view = RCMHintedTreeView(model=self.model) view.set_headers_visible(False) view.set_reorderable(True) view.set_rules_hint(True) render = Gtk.CellRendererText() render.set_padding(3, 6) render.props.ellipsize = Pango.EllipsizeMode.END column = Gtk.TreeViewColumn("", render) column.set_cell_data_func(render, self.__cdf) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.get_child().pack_start(sw, True, True, 0) vbox = Gtk.VBox(spacing=6) # Input for new ones. frame = self.__build_input_frame() vbox.pack_start(frame, False, True, 0) # Add context menu menu = Gtk.Menu() rem = MenuItem(_("_Remove"), Icons.LIST_REMOVE) keyval, mod = Gtk.accelerator_parse("Delete") rem.add_accelerator( 'activate', self.accels, keyval, mod, Gtk.AccelFlags.VISIBLE) connect_obj(rem, 'activate', self.__remove, view) menu.append(rem) menu.show_all() view.connect('popup-menu', self.__popup, menu) view.connect('key-press-event', self.__view_key_press) connect_obj(self, 'destroy', Gtk.Menu.destroy, menu) # New and Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) self.new_but = Button(_("_New"), Icons.DOCUMENT_NEW) self.new_but.connect('clicked', self._new_item) bbox.pack_start(self.new_but, True, True, 0) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.pack_start(close, True, True, 0) vbox.pack_end(bbox, False, True, 0) self.get_child().pack_start(vbox, True, True, 0) # Initialise self.selection = view.get_selection() self.selection.connect('changed', self.__select) self.connect('destroy', self.__finish) self.get_child().show_all()
def __init__(self, title, values=None): super(TagListEditor, self).__init__() self.use_header_bar() self.data = values or [] self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) vbox = Gtk.VBox(spacing=12) hbox = Gtk.HBox(spacing=12) # Set up the model for this widget self.model = Gtk.ListStore(str) self.__fill_values() # Main view view = self.view = HintedTreeView(model=self.model) view.set_fixed_height_mode(True) view.set_headers_visible(False) 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, 100)) hbox.pack_start(sw, True, True, 0) self.__setup_column(view) # Context menu 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) # Add and Remove buttons vbbox = Gtk.VButtonBox() vbbox.set_layout(Gtk.ButtonBoxStyle.START) vbbox.set_spacing(6) add = Button(_("_Add"), Icons.LIST_ADD) add.connect("clicked", self.__add) vbbox.pack_start(add, False, True, 0) remove = Button(_("_Remove"), Icons.LIST_REMOVE) remove.connect("clicked", self.__remove) vbbox.pack_start(remove, False, True, 0) hbox.pack_start(vbbox, False, True, 0) vbox.pack_start(hbox, True, True, 0) # Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.set_layout(Gtk.ButtonBoxStyle.END) if not self.has_close_button(): bbox.pack_start(close, True, True, 0) vbox.pack_start(bbox, False, True, 0) # Finish up self.add(vbox) self.get_child().show_all()
class TagListEditor(qltk.Window): """Dialog to edit a list of tag names.""" _WIDTH = 600 _HEIGHT = 300 def __init__(self, title, values=None): super(TagListEditor, self).__init__() self.use_header_bar() self.data = values or [] self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) vbox = Gtk.VBox(spacing=12) hbox = Gtk.HBox(spacing=12) # Set up the model for this widget self.model = Gtk.ListStore(str) self.__fill_values() # Main view view = self.view = HintedTreeView(model=self.model) view.set_fixed_height_mode(True) view.set_headers_visible(False) 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, 100)) hbox.pack_start(sw, True, True, 0) self.__setup_column(view) # Context menu 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) # Add and Remove buttons vbbox = Gtk.VButtonBox() vbbox.set_layout(Gtk.ButtonBoxStyle.START) vbbox.set_spacing(6) add = Button(_("_Add"), Icons.LIST_ADD) add.connect("clicked", self.__add) vbbox.pack_start(add, False, True, 0) remove = Button(_("_Remove"), Icons.LIST_REMOVE) remove.connect("clicked", self.__remove) vbbox.pack_start(remove, False, True, 0) hbox.pack_start(vbbox, False, True, 0) vbox.pack_start(hbox, True, True, 0) # Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.set_layout(Gtk.ButtonBoxStyle.END) if not self.has_close_button(): bbox.pack_start(close, True, True, 0) vbox.pack_start(bbox, False, True, 0) # Finish up self.add(vbox) self.get_child().show_all() def __setup_column(self, view): def tag_cdf(column, cell, model, iter, data): row = model[iter] if row: cell.set_property('text', row[0]) def desc_cdf(column, cell, model, iter, data): row = model[iter] if row: name = re.sub(':[a-z]+$', '', row[0].strip('~#')) try: t = _TAGS[name] valid = (not t.hidden and t.numeric == row[0].startswith('~#')) val = t.desc if valid else name except KeyError: val = name cell.set_property('text', util.title(val.replace('~', ' / '))) render = Gtk.CellRendererText() column = Gtk.TreeViewColumn(_("Tag expression"), render) column.set_cell_data_func(render, tag_cdf) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_expand(True) view.append_column(column) render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) column = Gtk.TreeViewColumn(_("Description"), render) column.set_cell_data_func(render, desc_cdf) column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) column.set_expand(True) view.append_column(column) view.set_headers_visible(True) def __fill_values(self): for s in self.data: self.model.append(row=[s]) def get_strings(self): strings = [row[0] for row in self.model if row] return strings def __remove(self, *args): self.view.remove_selection() def __add(self, *args): tooltip = _('Tag expression e.g. people:real or ~album~year.') dialog = GetStringDialog(self, _("Enter new tag"), "", button_icon=None, tooltip=tooltip) new = dialog.run() if new: self.model.append(row=[new]) def __popup(self, view, menu): return view.popup_menu(menu, 0, Gtk.get_current_event_time()).show()
def __init__(self, library): super(MediaDevices, self).__init__(spacing=6) self.set_orientation(Gtk.Orientation.VERTICAL) self._register_instance() self.__cache = {} # Device list on the left pane swin = ScrolledWindow() swin.set_shadow_type(Gtk.ShadowType.IN) swin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.pack_start(swin, True, True, 0) self.__view = view = AllTreeView() view.set_model(self.__devices) view.set_rules_hint(True) view.set_headers_visible(False) view.get_selection().set_mode(Gtk.SelectionMode.BROWSE) connect_obj(view.get_selection(), 'changed', self.__refresh, False) view.connect('popup-menu', self.__popup_menu, library) view.connect('row-activated', lambda *a: self.songs_activated()) swin.add(view) col = Gtk.TreeViewColumn("Devices") view.append_column(col) render = Gtk.CellRendererPixbuf() col.pack_start(render, False) col.add_attribute(render, 'icon-name', 1) self.__render = render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) render.connect('edited', self.__edited) col.pack_start(render, True) col.set_cell_data_func(render, MediaDevices.cell_data) hbox = Gtk.HBox(spacing=6) hbox.set_homogeneous(True) self.pack_start(Align(hbox, left=3, bottom=3), False, True, 0) # refresh button refresh = Button(_("_Refresh"), Gtk.STOCK_REFRESH, Gtk.IconSize.MENU) self.__refresh_button = refresh connect_obj(refresh, 'clicked', self.__refresh, True) refresh.set_sensitive(False) hbox.pack_start(refresh, True, True, 0) # eject button eject = Button(_("_Eject"), "media-eject", Gtk.IconSize.MENU) self.__eject_button = eject eject.connect('clicked', self.__eject) eject.set_sensitive(False) hbox.pack_start(eject, True, True, 0) # Device info on the right pane self.__header = table = Gtk.Table() table.set_col_spacings(8) self.__device_icon = icon = Gtk.Image() icon.set_size_request(48, 48) table.attach(icon, 0, 1, 0, 2, 0) self.__device_name = label = Gtk.Label() label.set_ellipsize(Pango.EllipsizeMode.END) label.set_alignment(0, 0) table.attach(label, 1, 3, 0, 1) self.__device_space = label = Gtk.Label() label.set_ellipsize(Pango.EllipsizeMode.END) label.set_alignment(0, 0.5) table.attach(label, 1, 2, 1, 2) self.__progress = progress = Gtk.ProgressBar() progress.set_size_request(150, -1) table.attach(progress, 2, 3, 1, 2, xoptions=0, yoptions=0) self.accelerators = Gtk.AccelGroup() key, mod = Gtk.accelerator_parse('F2') self.accelerators.connect(key, mod, 0, self.__rename) self.__statusbar = WaitLoadBar() for child in self.get_children(): child.show_all()
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 __init__(self, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source'])) }) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) self.search_field = Gtk.Entry() self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) self.search_field.connect('activate', self.start_search) widget_space = 5 search_hbox = Gtk.HBox(spacing=widget_space) search_hbox.pack_start(self.search_field, True, True, 0) search_hbox.pack_start(self.search_button, False, True, 0) self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_hbox, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search()
def __init__(self, player, debug=False): super(GstPlayerPreferences, self).__init__(spacing=6) e = UndoEntry() e.set_tooltip_text( _("The GStreamer output pipeline used for " "playback, such as 'alsasink device=default'. " "Leave blank for default pipeline.")) e.set_text(config.get('player', 'gst_pipeline')) def changed(entry): config.set('player', 'gst_pipeline', entry.get_text()) e.connect('changed', changed) pipe_label = Gtk.Label(label=_('_Output pipeline:')) pipe_label.set_use_underline(True) pipe_label.set_mnemonic_widget(e) apply_button = Gtk.Button(stock=Gtk.STOCK_APPLY) def format_buffer(scale, value): return _("%.1f seconds") % value def scale_changed(scale): duration_msec = int(scale.get_value() * 1000) player._set_buffer_duration(duration_msec) duration = config.getfloat("player", "gst_buffer") scale = Gtk.HScale.new(Gtk.Adjustment(duration, 0.2, 10)) scale.set_value_pos(Gtk.PositionType.RIGHT) scale.set_show_fill_level(True) scale.connect('format-value', format_buffer) scale.connect('value-changed', scale_changed) buffer_label = Gtk.Label(label=_('_Buffer duration:')) buffer_label.set_use_underline(True) buffer_label.set_mnemonic_widget(scale) def rebuild_pipeline(*args): player._rebuild_pipeline() apply_button.connect('clicked', rebuild_pipeline) gapless_button = ConfigCheckButton(_('Disable _gapless playback'), "player", "gst_disable_gapless", populate=True) gapless_button.set_alignment(0.0, 0.5) gapless_button.set_tooltip_text( _("Disabling gapless playback can avoid track changing problems " "with some GStreamer versions.")) widgets = [ (pipe_label, e, apply_button), (buffer_label, scale, None), ] table = Gtk.Table(len(widgets), 3) table.set_col_spacings(6) table.set_row_spacings(6) for i, (left, middle, right) in enumerate(widgets): left.set_alignment(0.0, 0.5) table.attach(left, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK) if right: table.attach(middle, 1, 2, i, i + 1) table.attach(right, 2, 3, i, i + 1, xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK) else: table.attach(middle, 1, 3, i, i + 1) table.attach(gapless_button, 0, 3, 2, 3) self.pack_start(table, True, True, 0) if debug: def print_bin(player): player._print_pipeline() b = Button("Print Pipeline", Gtk.STOCK_DIALOG_INFO) b.connect_object('clicked', print_bin, player) self.pack_start(b, True, True, 0)
def __init__(self, songs, title=None): super(SearchWindow, self).__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 = FingerPrintThreadPool() 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"), Gtk.STOCK_SAVE) save.connect("clicked", self.__on_save) save.set_sensitive(False) cancel = Gtk.Button(stock=Gtk.STOCK_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", populate=True) inner_box.pack_start(ccb, False, True, 0) outer_box.pack_start(inner_box, True, True, 0) bottom_box = Gtk.HBox(spacing=6) 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(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._release_counts = {} self.__done = 0 self.connect("destroy", self.__destroy)
def __init__(self, library): super(MediaDevices, self).__init__(spacing=6) self.set_orientation(Gtk.Orientation.VERTICAL) self._register_instance() self.__cache = {} # Device list on the left pane swin = ScrolledWindow() swin.set_shadow_type(Gtk.ShadowType.IN) swin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self.pack_start(swin, True, True, 0) self.__view = view = AllTreeView() view.set_model(self.__devices) view.set_rules_hint(True) view.set_headers_visible(False) view.get_selection().set_mode(Gtk.SelectionMode.BROWSE) connect_obj(view.get_selection(), 'changed', self.__refresh, False) view.connect('popup-menu', self.__popup_menu, library) view.connect('row-activated', lambda *a: self.songs_activated()) swin.add(view) col = Gtk.TreeViewColumn("Devices") view.append_column(col) render = Gtk.CellRendererPixbuf() col.pack_start(render, False) col.add_attribute(render, 'icon-name', 1) self.__render = render = Gtk.CellRendererText() render.set_property('ellipsize', Pango.EllipsizeMode.END) render.connect('edited', self.__edited) col.pack_start(render, True) col.set_cell_data_func(render, MediaDevices.cell_data) hbox = Gtk.HBox(spacing=6) hbox.set_homogeneous(True) self.pack_start(Align(hbox, left=3, bottom=3), False, True, 0) # refresh button refresh = Button(_("_Refresh"), Icons.VIEW_REFRESH, Gtk.IconSize.MENU) self.__refresh_button = refresh connect_obj(refresh, 'clicked', self.__refresh, True) refresh.set_sensitive(False) hbox.pack_start(refresh, True, True, 0) # eject button eject = Button(_("_Eject"), Icons.MEDIA_EJECT, Gtk.IconSize.MENU) self.__eject_button = eject eject.connect('clicked', self.__eject) eject.set_sensitive(False) hbox.pack_start(eject, True, True, 0) # Device info on the right pane self.__header = table = Gtk.Table() table.set_col_spacings(8) self.__device_icon = icon = Gtk.Image() icon.set_size_request(48, 48) table.attach(icon, 0, 1, 0, 2, 0) self.__device_name = label = Gtk.Label() label.set_ellipsize(Pango.EllipsizeMode.END) label.set_alignment(0, 0) table.attach(label, 1, 3, 0, 1) self.__device_space = label = Gtk.Label() label.set_ellipsize(Pango.EllipsizeMode.END) label.set_alignment(0, 0.5) table.attach(label, 1, 2, 1, 2) self.__progress = progress = Gtk.ProgressBar() progress.set_size_request(150, -1) table.attach(progress, 2, 3, 1, 2, xoptions=0, yoptions=0) self.accelerators = Gtk.AccelGroup() key, mod = Gtk.accelerator_parse('F2') self.accelerators.connect(key, mod, 0, self.__rename) self.__statusbar = WaitLoadBar() for child in self.get_children(): child.show_all()
def tag_editing_vbox(self): """Returns a new VBox containing all tag editing widgets""" vbox = Gtk.VBox(spacing=6) cb = CCB(_("Auto-save tag changes"), 'editing', 'auto_save_changes', populate=True, tooltip=_("Save changes to tags without confirmation " "when editing multiple files")) vbox.pack_start(cb, False, True, 0) split_entry = UndoEntry() split_entry.set_text(config.get("editing", "split_on")) split_entry.connect('changed', self.__changed, 'editing', 'split_on') split_entry.set_tooltip_text( _("A set of separators to use when splitting tag values " "in the tag editor. " "The list is space-separated.")) split_entry.props.expand = True sub_entry = UndoEntry() sub_entry.set_text(config.get("editing", "sub_split_on")) sub_entry.connect('changed', self.__changed, 'editing', 'sub_split_on') sub_entry.set_tooltip_text( _("A set of separators to use when extracting subtags from " "tags in the tag editor. " "The list is space-separated, and each entry must only " "contain two characters.")) sub_entry.props.expand = True def do_revert_split(button, entry, section, option): config.reset(section, option) entry.set_text(config.get(section, option)) split_revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT) split_revert.connect("clicked", do_revert_split, split_entry, "editing", "split_on") split_label = Gtk.Label(label=_("Split _tag on:")) split_label.set_use_underline(True) split_label.set_mnemonic_widget(split_entry) sub_revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT) sub_revert.connect("clicked", do_revert_split, sub_entry, "editing", "sub_split_on") sub_label = Gtk.Label(label=_("Split _subtag on:")) sub_label.set_use_underline(True) sub_label.set_mnemonic_widget(split_entry) split_align = Align(halign=Gtk.Align.START) split_align.add(split_label) sub_align = Align(halign=Gtk.Align.START) sub_align.add(sub_label) grid = Gtk.Grid(column_spacing=6, row_spacing=6) grid.add(split_align) grid.add(split_entry) grid.add(split_revert) grid.attach(sub_align, 0, 1, 1, 1) grid.attach(sub_entry, 1, 1, 1, 1) grid.attach(sub_revert, 2, 1, 1, 1) grid.props.expand = False vbox.pack_start(grid, False, False, 6) return vbox
class CoverArea(Gtk.VBox, PluginConfigMixin): """The image display and saving part.""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION def __init__(self, parent, song): super().__init__() self.song = song self.dirname = song("~dirname") self.main_win = parent self.data_cache = [] self.current_data = None self.current_pixbuf = None self.image = Gtk.Image() self.button = Button(_("_Save"), Icons.DOCUMENT_SAVE_AS) self.button.set_sensitive(False) self.button.connect('clicked', self.__save) close_button = Button(_("_Close"), Icons.WINDOW_CLOSE) close_button.connect('clicked', lambda x: self.main_win.destroy()) self.window_fit = self.ConfigCheckButton(_('Fit image to _window'), 'fit', True) self.window_fit.connect('toggled', self.__scale_pixbuf) self.name_combo = Gtk.ComboBoxText() self.name_combo.set_tooltip_text( _("See '[plugins] cover_filenames' config entry " + "for image filename strings")) self.cmd = qltk.entry.ValidatingEntry(iscommand) # Both labels label_open = Gtk.Label(label=_('_Program:')) label_open.set_use_underline(True) label_open.set_mnemonic_widget(self.cmd) label_open.set_justify(Gtk.Justification.LEFT) self.open_check = self.ConfigCheckButton(_('_Edit image after saving'), 'edit', False) label_name = Gtk.Label(label=_('File_name:'), use_underline=True) label_name.set_use_underline(True) label_name.set_mnemonic_widget(self.name_combo) label_name.set_justify(Gtk.Justification.LEFT) self.cmd.set_text(self.config_get('edit_cmd', 'gimp')) # populate the filename combo box fn_list = self.config_get_stringlist( 'filenames', ["cover.jpg", "folder.jpg", ".folder.jpg"]) # Issue 374 - add dynamic file names fn_dynlist = [] artist = song("artist") alartist = song("albumartist") album = song("album") labelid = song("labelid") if album: fn_dynlist.append("<album>.jpg") if alartist: fn_dynlist.append("<albumartist> - <album>.jpg") else: fn_dynlist.append("<artist> - <album>.jpg") else: print_w(u"No album for \"%s\". Could be difficult " u"finding art…" % song("~filename")) title = song("title") if title and artist: fn_dynlist.append("<artist> - <title>.jpg") if labelid: fn_dynlist.append("<labelid>.jpg") # merge unique fn_list.extend(s for s in fn_dynlist if s not in fn_list) set_fn = self.config_get('filename', fn_list[0]) for i, fn in enumerate(fn_list): self.name_combo.append_text(fn) if fn == set_fn: self.name_combo.set_active(i) if self.name_combo.get_active() < 0: self.name_combo.set_active(0) self.config_set('filename', self.name_combo.get_active_text()) table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False) table.props.expand = False table.set_row_spacing(0, 5) table.set_row_spacing(1, 5) table.set_col_spacing(0, 5) table.set_col_spacing(1, 5) table.attach(label_open, 0, 1, 0, 1) table.attach(label_name, 0, 1, 1, 2) table.attach(self.cmd, 1, 2, 0, 1) table.attach(self.name_combo, 1, 2, 1, 2) self.scrolled = Gtk.ScrolledWindow() self.scrolled.add_with_viewport(self.image) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.button, True, True, 0) bbox.pack_start(close_button, True, True, 0) bb_align = Align(valign=Gtk.Align.END, right=6) bb_align.add(bbox) main_hbox = Gtk.HBox() main_hbox.pack_start(table, False, True, 6) main_hbox.pack_start(bb_align, True, True, 0) top_hbox = Gtk.HBox() top_hbox.pack_start(self.open_check, True, True, 0) top_hbox.pack_start(self.window_fit, False, True, 0) main_vbox = Gtk.VBox() main_vbox.pack_start(top_hbox, True, True, 2) main_vbox.pack_start(main_hbox, True, True, 0) self.pack_start(self.scrolled, True, True, 0) self.pack_start(main_vbox, False, True, 5) # 5 MB image cache size self.max_cache_size = 1024 * 1024 * 5 # For managing fast selection switches of covers.. self.stop_loading = False self.loading = False self.current_job = 0 self.connect('destroy', self.__save_config) def __save(self, *data): """Save the cover and spawn the program to edit it if selected""" save_format = self.name_combo.get_active_text() # Allow use of patterns in creating cover filenames pattern = ArbitraryExtensionFileFromPattern(save_format) filename = pattern.format(self.song) print_d("Using '%s' as filename based on %s" % (filename, save_format)) file_path = os.path.join(self.dirname, filename) if os.path.exists(file_path): resp = ConfirmFileReplace(self, file_path).run() if resp != ConfirmFileReplace.RESPONSE_REPLACE: return try: f = open(file_path, 'wb') f.write(self.current_data) f.close() except IOError: qltk.ErrorMessage(None, _('Saving failed'), _('Unable to save "%s".') % file_path).run() else: if self.open_check.get_active(): try: util.spawn([self.cmd.get_text(), file_path]) except: pass app.cover_manager.cover_changed([self.song._song]) self.main_win.destroy() def __save_config(self, widget): self.config_set('edit_cmd', self.cmd.get_text()) self.config_set('filename', self.name_combo.get_active_text()) def __update(self, loader, *data): """Update the picture while it's loading""" if self.stop_loading: return pixbuf = loader.get_pixbuf() def idle_set(): if pixbuf is not None: surface = get_surface_for_pixbuf(self, pixbuf) self.image.set_from_surface(surface) GLib.idle_add(idle_set) def __scale_pixbuf(self, *data): if not self.current_pixbuf: return pixbuf = self.current_pixbuf if self.window_fit.get_active(): alloc = self.scrolled.get_allocation() width = alloc.width height = alloc.height scale_factor = self.get_scale_factor() boundary = (width * scale_factor, height * scale_factor) pixbuf = scale(pixbuf, boundary, scale_up=False) surface = get_surface_for_pixbuf(self, pixbuf) self.image.set_from_surface(surface) def __close(self, loader, *data): if self.stop_loading: return self.current_pixbuf = loader.get_pixbuf() GLib.idle_add(self.__scale_pixbuf) def set_cover(self, url): thr = threading.Thread(target=self.__set_async, args=(url, )) thr.setDaemon(True) thr.start() def __set_async(self, url): """Manages various things: Fast switching of covers (aborting old HTTP requests), The image cache, etc.""" self.current_job += 1 job = self.current_job self.stop_loading = True while self.loading: time.sleep(0.05) self.stop_loading = False if job != self.current_job: return self.loading = True GLib.idle_add(self.button.set_sensitive, False) self.current_pixbuf = None pbloader = GdkPixbuf.PixbufLoader() pbloader.connect('closed', self.__close) # Look for cached images raw_data = None for entry in self.data_cache: if entry[0] == url: raw_data = entry[1] break if not raw_data: pbloader.connect('area-updated', self.__update) data_store = BytesIO() try: request = Request(url) request.add_header('User-Agent', USER_AGENT) url_sock = urlopen(request) except EnvironmentError: print_w(_("[albumart] HTTP Error: %s") % url) else: while not self.stop_loading: tmp = url_sock.read(1024 * 10) if not tmp: break pbloader.write(tmp) data_store.write(tmp) url_sock.close() if not self.stop_loading: raw_data = data_store.getvalue() self.data_cache.insert(0, (url, raw_data)) while 1: cache_sizes = [ len(data[1]) for data in self.data_cache ] if sum(cache_sizes) > self.max_cache_size: del self.data_cache[-1] else: break data_store.close() else: # Sleep for fast switching of cached images time.sleep(0.05) if not self.stop_loading: pbloader.write(raw_data) try: pbloader.close() except GLib.GError: pass self.current_data = raw_data if not self.stop_loading: GLib.idle_add(self.button.set_sensitive, True) self.loading = False
class AlbumArtWindow(qltk.Window, PluginConfigMixin): """The main window including the search list""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION THUMB_SIZE = 50 def __init__(self, songs): super().__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source'])) }) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) search_labelraw = Gtk.Label('raw') search_labelraw.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldraw = Gtk.Entry() self.search_fieldraw.connect('activate', self.start_search) self.search_fieldraw.connect('changed', self.__searchfieldchanged) search_labelclean = Gtk.Label('clean') search_labelclean.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldclean = Gtk.Label() self.search_fieldclean.set_can_focus(False) self.search_fieldclean.set_alignment(xalign=0.0, yalign=0.5) self.search_radioraw = Gtk.RadioButton(group=None, label=None) self.search_radioraw.connect("toggled", self.__searchtypetoggled, "raw") self.search_radioclean = Gtk.RadioButton(group=self.search_radioraw, label=None) self.search_radioclean.connect("toggled", self.__searchtypetoggled, "clean") #note: set_active(False) appears to have no effect #self.search_radioraw.set_active( # self.config_get_bool('searchraw', False)) if self.config_get_bool('searchraw', False): self.search_radioraw.set_active(True) else: self.search_radioclean.set_active(True) search_labelresultsmax = Gtk.Label('limit') search_labelresultsmax.set_alignment(xalign=1.0, yalign=0.5) search_labelresultsmax.set_tooltip_text( _("Per engine 'at best' results limit")) search_adjresultsmax = Gtk.Adjustment(value=int( self.config_get("resultsmax", 3)), lower=1, upper=REQUEST_LIMIT_MAX, step_incr=1, page_incr=0, page_size=0) self.search_spinresultsmax = Gtk.SpinButton( adjustment=search_adjresultsmax, climb_rate=0.2, digits=0) self.search_spinresultsmax.set_alignment(xalign=0.5) self.search_spinresultsmax.set_can_focus(False) self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) search_button_box = Gtk.Alignment() search_button_box.set(1, 0, 0, 0) search_button_box.add(self.search_button) search_table = Gtk.Table(rows=3, columns=4, homogeneous=False) search_table.attach(search_labelraw, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioraw, 1, 2, 0, 1, xoptions=0, xpadding=0) search_table.attach(self.search_fieldraw, 2, 4, 0, 1) search_table.attach(search_labelclean, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioclean, 1, 2, 1, 2, xoptions=0, xpadding=0) search_table.attach(self.search_fieldclean, 2, 4, 1, 2, xpadding=4) search_table.attach(search_labelresultsmax, 0, 2, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_spinresultsmax, 2, 3, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=0) search_table.attach(search_button_box, 3, 4, 2, 3) widget_space = 5 self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_table, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) self.connect('destroy', self.__save_config) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search() def __save_config(self, widget): self.config_set('searchraw', self.search_radioraw.get_active()) self.config_set('resultsmax', self.search_spinresultsmax.get_value_as_int()) def __drag_data_get(self, view, ctx, sel, tid, etime, treeselection): model, iter = treeselection.get_selected() if not iter: return cover = model.get_value(iter, 1) sel.set_uris([cover['cover']]) def __searchfieldchanged(self, *data): search = data[0].get_text() clean = cleanup_query(search, ' ') self.search_fieldclean.set_text('<b>' + clean + '</b>') self.search_fieldclean.set_use_markup(True) def __searchtypetoggled(self, *data): self.config_set('searchraw', self.search_radioraw.get_active()) def start_search(self, *data): """Start the search using the text from the text entry""" text = self.search_fieldraw.get_text() if not text or self.search_lock: return self.search_lock = True self.search_button.set_sensitive(False) self.progress.set_fraction(0) self.progress.set_text(_(u'Searching…')) self.progress.show() self.liststore.clear() self.search = search = CoverSearch(self.__search_callback) for eng in ENGINES: if self.config_get_bool(CONFIG_ENG_PREFIX + eng['config_id'], True): search.add_engine(eng['class'], eng['replace']) raw = self.search_radioraw.get_active() limit = self.search_spinresultsmax.get_value_as_int() search.start(text, raw, limit) # Focus the list self.treeview.grab_focus() self.connect("destroy", self.__destroy) def __destroy(self, *args): self.search.stop() def set_text(self, text): """set the text and move the cursor to the end""" self.search_fieldraw.set_text(text) self.search_fieldraw.emit('move-cursor', Gtk.MovementStep.BUFFER_ENDS, 0, False) def __select_callback(self, selection, image): model, iter = selection.get_selected() if not iter: return cover = model.get_value(iter, 1) image.set_cover(cover['cover']) def __add_cover_to_list(self, cover): try: pbloader = GdkPixbuf.PixbufLoader() pbloader.write(get_url(cover['thumbnail'])) pbloader.close() scale_factor = self.get_scale_factor() size = self.THUMB_SIZE * scale_factor - scale_factor * 2 pixbuf = pbloader.get_pixbuf().scale_simple( size, size, GdkPixbuf.InterpType.BILINEAR) pixbuf = add_border_widget(pixbuf, self) surface = get_surface_for_pixbuf(self, pixbuf) except (GLib.GError, IOError): pass else: def append(data): self.liststore.append(data) GLib.idle_add(append, [surface, cover]) def __search_callback(self, covers, progress): for cover in covers: self.__add_cover_to_list(cover) if self.progress.get_fraction() < progress: self.progress.set_fraction(progress) if progress >= 1: self.progress.set_text(_('Done')) GLib.timeout_add(700, self.progress.hide) self.search_button.set_sensitive(True) self.search_lock = False
def __init__(self, player, debug=False): super().__init__(spacing=6) e = UndoEntry() e.set_tooltip_text( _("The GStreamer output pipeline used for " "playback. Leave blank for the default pipeline. " "In case the pipeline contains a sink, " "it will be used instead of the default one.")) e.set_text(config.get('player', 'gst_pipeline')) def changed(entry): config.set('player', 'gst_pipeline', entry.get_text()) e.connect('changed', changed) pipe_label = Gtk.Label(label=_('_Output pipeline:')) pipe_label.set_use_underline(True) pipe_label.set_mnemonic_widget(e) apply_button = Button(_("_Apply")) def format_buffer(scale, value): return _("%.1f seconds") % value def scale_changed(scale): duration_msec = int(scale.get_value() * 1000) player._set_buffer_duration(duration_msec) duration = config.getfloat("player", "gst_buffer") scale = Gtk.HScale.new( Gtk.Adjustment(value=duration, lower=0.2, upper=10)) scale.set_value_pos(Gtk.PositionType.RIGHT) scale.set_show_fill_level(True) scale.connect('format-value', format_buffer) scale.connect('value-changed', scale_changed) buffer_label = Gtk.Label(label=_('_Buffer duration:')) buffer_label.set_use_underline(True) buffer_label.set_mnemonic_widget(scale) def rebuild_pipeline(*args): player._rebuild_pipeline() apply_button.connect('clicked', rebuild_pipeline) gapless_button = ConfigCheckButton( _('Disable _gapless playback'), "player", "gst_disable_gapless", populate=True, tooltip=_( "Disabling gapless playback can avoid track changing problems " "with some GStreamer versions")) jack_button = ConfigCheckButton( _('Use JACK for playback if available'), "player", "gst_use_jack", populate=True, tooltip=_( "Uses `jackaudiosink` for playbin sink if it can be detected")) jack_connect = ConfigCheckButton( _('Auto-connect to JACK output devices'), "player", "gst_jack_auto_connect", populate=True, tooltip=_("Tells `jackaudiosink` to auto-connect")) def _jack_toggled(widget: ConfigCheckButton) -> None: jack_connect.set_sensitive(widget.get_active()) jack_button.connect("clicked", _jack_toggled) _jack_toggled(jack_button) widgets = [(pipe_label, e, apply_button), (buffer_label, scale, None)] table = Gtk.Table(n_rows=len(widgets) + 3, n_columns=3) table.set_col_spacings(6) table.set_row_spacings(6) for i, (left, middle, right) in enumerate(widgets): left.set_alignment(0.0, 0.5) table.attach(left, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK) if right: table.attach(middle, 1, 2, i, i + 1) table.attach(right, 2, 3, i, i + 1, xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK) else: table.attach(middle, 1, 3, i, i + 1) table.attach(gapless_button, 0, 3, 2, 3) table.attach(jack_button, 0, 3, 3, 4) table.attach(jack_connect, 0, 3, 4, 5) self.pack_start(table, True, True, 0) if debug: def print_bin(player): player._print_pipeline() b = Button("Print Pipeline", Icons.DIALOG_INFORMATION) connect_obj(b, 'clicked', print_bin, player) self.pack_start(b, True, True, 0)
class AlbumArtWindow(qltk.Window, PluginConfigMixin): """The main window including the search list""" CONFIG_SECTION = PLUGIN_CONFIG_SECTION THUMB_SIZE = 50 def __init__(self, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set( Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source']))}) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) search_labelraw = Gtk.Label('raw') search_labelraw.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldraw = Gtk.Entry() self.search_fieldraw.connect('activate', self.start_search) self.search_fieldraw.connect('changed', self.__searchfieldchanged) search_labelclean = Gtk.Label('clean') search_labelclean.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldclean = Gtk.Label() self.search_fieldclean.set_can_focus(False) self.search_fieldclean.set_alignment(xalign=0.0, yalign=0.5) self.search_radioraw = Gtk.RadioButton(group=None, label=None) self.search_radioraw.connect("toggled", self.__searchtypetoggled, "raw") self.search_radioclean = Gtk.RadioButton(group=self.search_radioraw, label=None) self.search_radioclean.connect("toggled", self.__searchtypetoggled, "clean") #note: set_active(False) appears to have no effect #self.search_radioraw.set_active( # self.config_get_bool('searchraw', False)) if self.config_get_bool('searchraw', False): self.search_radioraw.set_active(True) else: self.search_radioclean.set_active(True) search_labelresultsmax = Gtk.Label('limit') search_labelresultsmax.set_alignment(xalign=1.0, yalign=0.5) search_labelresultsmax.set_tooltip_text( _("Per engine 'at best' results limit")) search_adjresultsmax = Gtk.Adjustment( value=int(self.config_get("resultsmax", 3)), lower=1, upper=REQUEST_LIMIT_MAX, step_incr=1, page_incr=0, page_size=0) self.search_spinresultsmax = Gtk.SpinButton( adjustment=search_adjresultsmax, climb_rate=0.2, digits=0) self.search_spinresultsmax.set_alignment(xalign=0.5) self.search_spinresultsmax.set_can_focus(False) self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) search_button_box = Gtk.Alignment() search_button_box.set(1, 0, 0, 0) search_button_box.add(self.search_button) search_table = Gtk.Table(rows=3, columns=4, homogeneous=False) search_table.attach(search_labelraw, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioraw, 1, 2, 0, 1, xoptions=0, xpadding=0) search_table.attach(self.search_fieldraw, 2, 4, 0, 1) search_table.attach(search_labelclean, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioclean, 1, 2, 1, 2, xoptions=0, xpadding=0) search_table.attach(self.search_fieldclean, 2, 4, 1, 2, xpadding=4) search_table.attach(search_labelresultsmax, 0, 2, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_spinresultsmax, 2, 3, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=0) search_table.attach(search_button_box, 3, 4, 2, 3) widget_space = 5 self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_table, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) self.connect('destroy', self.__save_config) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search() def __save_config(self, widget): self.config_set('searchraw', self.search_radioraw.get_active()) self.config_set('resultsmax', self.search_spinresultsmax.get_value_as_int()) def __drag_data_get(self, view, ctx, sel, tid, etime, treeselection): model, iter = treeselection.get_selected() if not iter: return cover = model.get_value(iter, 1) sel.set_uris([cover['cover']]) def __searchfieldchanged(self, *data): search = data[0].get_text() clean = cleanup_query(search, ' ') self.search_fieldclean.set_text('<b>' + clean + '</b>') self.search_fieldclean.set_use_markup(True) def __searchtypetoggled(self, *data): self.config_set('searchraw', self.search_radioraw.get_active()) def start_search(self, *data): """Start the search using the text from the text entry""" text = self.search_fieldraw.get_text() if not text or self.search_lock: return self.search_lock = True self.search_button.set_sensitive(False) self.progress.set_fraction(0) self.progress.set_text(_(u'Searching…')) self.progress.show() self.liststore.clear() self.search = search = CoverSearch(self.__search_callback) for eng in ENGINES: if self.config_get_bool( CONFIG_ENG_PREFIX + eng['config_id'], True): search.add_engine(eng['class'], eng['replace']) raw = self.search_radioraw.get_active() limit = self.search_spinresultsmax.get_value_as_int() search.start(text, raw, limit) # Focus the list self.treeview.grab_focus() self.connect("destroy", self.__destroy) def __destroy(self, *args): self.search.stop() def set_text(self, text): """set the text and move the cursor to the end""" self.search_fieldraw.set_text(text) self.search_fieldraw.emit('move-cursor', Gtk.MovementStep.BUFFER_ENDS, 0, False) def __select_callback(self, selection, image): model, iter = selection.get_selected() if not iter: return cover = model.get_value(iter, 1) image.set_cover(cover['cover']) def __add_cover_to_list(self, cover): try: pbloader = GdkPixbuf.PixbufLoader() pbloader.write(get_url(cover['thumbnail'])) pbloader.close() scale_factor = self.get_scale_factor() size = self.THUMB_SIZE * scale_factor - scale_factor * 2 pixbuf = pbloader.get_pixbuf().scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR) pixbuf = add_border_widget(pixbuf, self) surface = get_surface_for_pixbuf(self, pixbuf) except (GLib.GError, IOError): pass else: def append(data): self.liststore.append(data) GLib.idle_add(append, [surface, cover]) def __search_callback(self, covers, progress): for cover in covers: self.__add_cover_to_list(cover) if self.progress.get_fraction() < progress: self.progress.set_fraction(progress) if progress >= 1: self.progress.set_text(_('Done')) GLib.timeout_add(700, self.progress.hide) self.search_button.set_sensitive(True) self.search_lock = False
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()
class JSONBasedEditor(qltk.UniqueWindow): """ Flexible editor for objects extending `JSONObject` (held in a `JSONObjectDict`) TODO: validation, especially for name. """ _WIDTH = 800 _HEIGHT = 400 def __init__(self, Prototype, values, filename, title): if self.is_not_unique(): return super(JSONBasedEditor, self).__init__() self.Prototype = Prototype self.current = None self.filename = filename self.name = Prototype.NAME or Prototype.__name__ self.input_entries = {} self.set_border_width(12) self.set_title(title) self.set_default_size(self._WIDTH, self._HEIGHT) self.add(Gtk.HBox(spacing=6)) self.get_child().set_homogeneous(True) self.accels = Gtk.AccelGroup() # Set up the model for this widget self.model = Gtk.ListStore(object) self._fill_values(values) # The browser for existing data self.view = view = RCMHintedTreeView(model=self.model) view.set_headers_visible(False) view.set_reorderable(True) view.set_rules_hint(True) render = Gtk.CellRendererText() render.set_padding(3, 6) render.props.ellipsize = Pango.EllipsizeMode.END column = Gtk.TreeViewColumn("", render) column.set_cell_data_func(render, self.__cdf) view.append_column(column) sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.IN) sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(view) self.get_child().pack_start(sw, True, True, 0) vbox = Gtk.VBox(spacing=6) # Input for new ones. frame = self.__build_input_frame() vbox.pack_start(frame, False, True, 0) # Add context menu menu = Gtk.Menu() rem = MenuItem(_("_Remove"), Icons.LIST_REMOVE) keyval, mod = Gtk.accelerator_parse("Delete") rem.add_accelerator('activate', self.accels, keyval, mod, Gtk.AccelFlags.VISIBLE) connect_obj(rem, 'activate', self.__remove, view) menu.append(rem) menu.show_all() view.connect('popup-menu', self.__popup, menu) view.connect('key-press-event', self.__view_key_press) connect_obj(self, 'destroy', Gtk.Menu.destroy, menu) # New and Close buttons bbox = Gtk.HButtonBox() self.remove_but = Button(_("_Remove"), Icons.LIST_REMOVE) self.remove_but.set_sensitive(False) self.new_but = Button(_("_New"), Icons.DOCUMENT_NEW) self.new_but.connect('clicked', self._new_item) bbox.pack_start(self.new_but, True, True, 0) close = Button(_("_Close"), Icons.WINDOW_CLOSE) connect_obj(close, 'clicked', qltk.Window.destroy, self) bbox.pack_start(close, True, True, 0) vbox.pack_end(bbox, False, True, 0) self.get_child().pack_start(vbox, True, True, 0) # Initialise self.selection = view.get_selection() self.selection.connect('changed', self.__select) self.connect('destroy', self.__finish) self.get_child().show_all() def _find(self, name): for row in self.model: if row[0].name == name: return row[0] def _new_item(self, button): current_name = name = _("New %s") % self.name n = 2 while True: if self._find(current_name): current_name = "%s (%d)" % (name, n) n += 1 continue break self.model.append(row=(self.Prototype(name=current_name), )) def _new_widget(self, key, val): """ Creates a Gtk.Entry subclass appropriate for a field named `key` with value `val` """ callback = signal = None if isinstance(val, bool): entry = Gtk.CheckButton() callback = self.__toggled_widget signal = "toggled" elif isinstance(val, int): adj = Gtk.Adjustment.new(0, 0, 9999, 1, 10, 0) entry = Gtk.SpinButton(adjustment=adj) entry.set_numeric(True) callback = self.__changed_numeric_widget elif "pattern" in key: entry = ValidatingEntry(validator=Query.validator) else: entry = UndoEntry() entry.connect(signal or "changed", callback or self.__changed_widget, key) return entry def __refresh_view(self): model, iter = self.selection.get_selected() self.model.emit("row-changed", model[iter].path, iter) def __changed_widget(self, entry, key): if self.current: setattr(self.current, key, str(entry.get_text())) self.__refresh_view() def __changed_numeric_widget(self, entry, key): if self.current: setattr(self.current, key, int(entry.get_text() or 0)) self.__refresh_view() def __toggled_widget(self, entry, key): if self.current: setattr(self.current, key, bool(entry.get_active())) self.__refresh_view() def _populate_fields(self, obj): """Populates the input fields based on the `JSONData` object `obj`""" for fn, val in obj.data: widget = self.input_entries[fn] widget.set_sensitive(True) # TODO: link this logic better with the creational stuff if isinstance(val, bool): widget.set_active(val) elif isinstance(val, int): widget.set_value(int(val)) elif isinstance(val, basestring): widget.set_text(val or "") def __build_input_frame(self): t = Gtk.Table(n_rows=2, n_columns=3) t.set_row_spacings(6) t.set_col_spacing(0, 3) t.set_col_spacing(1, 12) empty = self.Prototype("empty") for i, (key, val) in enumerate(empty.data): field = empty.field(key) field_name = self.get_field_name(field, key) l = Gtk.Label(label=field_name + ":") entry = self._new_widget(key, val) entry.set_sensitive(False) if field.doc: entry.set_tooltip_text(field.doc) # Store these away in a map for later access self.input_entries[key] = entry l.set_mnemonic_widget(entry) l.set_use_underline(True) l.set_alignment(0.0, 0.5) if isinstance(val, int) or isinstance(val, bool): align = Align(entry, halign=Gtk.Align.START) t.attach(align, 1, 2, i, i + 1) else: t.attach(entry, 1, 2, i, i + 1) t.attach(l, 0, 1, i, i + 1, xoptions=Gtk.AttachOptions.FILL) frame = qltk.Frame(label=self.name, child=t) self.input_entries["name"].grab_focus() return frame @staticmethod def get_field_name(field, key): field_name = (field.human_name or (key and key.replace("_", " "))) return field_name and field_name.title() or _("(unknown)") def _fill_values(self, data): if not data: return for (name, obj) in data.items(): self.model.prepend(row=[obj]) def _update_current(self, new_selection=None): if new_selection: self.selection = new_selection model, iter = self.selection.get_selected() if iter: self.current = model[iter][0] def __select(self, selection): self._update_current(selection) self.remove_but.set_sensitive(bool(iter)) if iter is not None: self._populate_fields(self.current) def __remove(self, view): view.remove_selection() def __popup(self, view, menu): return view.popup_menu(menu, 0, Gtk.get_current_event_time()) def __view_key_press(self, view, event): if event.keyval == Gtk.accelerator_parse("Delete")[0]: self.__remove(view) def __cdf(self, column, cell, model, iter, data): row = model[iter] obj = row[0] obj_name = util.escape(obj.name) obj_description = util.escape(str(obj)) markup = '<b>%s</b>\n%s' % (obj_name, obj_description) cell.markup = markup cell.set_property('markup', markup) def __finish(self, widget): # TODO: Warn about impending deletion of nameless items, or something all = JSONObjectDict.from_list( [row[0] for row in self.model if row[0].name]) all.save(filename=self.filename)
def __init__(self, parent, song): super().__init__() self.song = song self.dirname = song("~dirname") self.main_win = parent self.data_cache = [] self.current_data = None self.current_pixbuf = None self.image = Gtk.Image() self.button = Button(_("_Save"), Icons.DOCUMENT_SAVE_AS) self.button.set_sensitive(False) self.button.connect('clicked', self.__save) close_button = Button(_("_Close"), Icons.WINDOW_CLOSE) close_button.connect('clicked', lambda x: self.main_win.destroy()) self.window_fit = self.ConfigCheckButton(_('Fit image to _window'), 'fit', True) self.window_fit.connect('toggled', self.__scale_pixbuf) self.name_combo = Gtk.ComboBoxText() self.name_combo.set_tooltip_text( _("See '[plugins] cover_filenames' config entry " + "for image filename strings")) self.cmd = qltk.entry.ValidatingEntry(iscommand) # Both labels label_open = Gtk.Label(label=_('_Program:')) label_open.set_use_underline(True) label_open.set_mnemonic_widget(self.cmd) label_open.set_justify(Gtk.Justification.LEFT) self.open_check = self.ConfigCheckButton(_('_Edit image after saving'), 'edit', False) label_name = Gtk.Label(label=_('File_name:'), use_underline=True) label_name.set_use_underline(True) label_name.set_mnemonic_widget(self.name_combo) label_name.set_justify(Gtk.Justification.LEFT) self.cmd.set_text(self.config_get('edit_cmd', 'gimp')) # populate the filename combo box fn_list = self.config_get_stringlist( 'filenames', ["cover.jpg", "folder.jpg", ".folder.jpg"]) # Issue 374 - add dynamic file names fn_dynlist = [] artist = song("artist") alartist = song("albumartist") album = song("album") labelid = song("labelid") if album: fn_dynlist.append("<album>.jpg") if alartist: fn_dynlist.append("<albumartist> - <album>.jpg") else: fn_dynlist.append("<artist> - <album>.jpg") else: print_w(u"No album for \"%s\". Could be difficult " u"finding art…" % song("~filename")) title = song("title") if title and artist: fn_dynlist.append("<artist> - <title>.jpg") if labelid: fn_dynlist.append("<labelid>.jpg") # merge unique fn_list.extend(s for s in fn_dynlist if s not in fn_list) set_fn = self.config_get('filename', fn_list[0]) for i, fn in enumerate(fn_list): self.name_combo.append_text(fn) if fn == set_fn: self.name_combo.set_active(i) if self.name_combo.get_active() < 0: self.name_combo.set_active(0) self.config_set('filename', self.name_combo.get_active_text()) table = Gtk.Table(n_rows=2, n_columns=2, homogeneous=False) table.props.expand = False table.set_row_spacing(0, 5) table.set_row_spacing(1, 5) table.set_col_spacing(0, 5) table.set_col_spacing(1, 5) table.attach(label_open, 0, 1, 0, 1) table.attach(label_name, 0, 1, 1, 2) table.attach(self.cmd, 1, 2, 0, 1) table.attach(self.name_combo, 1, 2, 1, 2) self.scrolled = Gtk.ScrolledWindow() self.scrolled.add_with_viewport(self.image) self.scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) bbox = Gtk.HButtonBox() bbox.set_spacing(6) bbox.set_layout(Gtk.ButtonBoxStyle.END) bbox.pack_start(self.button, True, True, 0) bbox.pack_start(close_button, True, True, 0) bb_align = Align(valign=Gtk.Align.END, right=6) bb_align.add(bbox) main_hbox = Gtk.HBox() main_hbox.pack_start(table, False, True, 6) main_hbox.pack_start(bb_align, True, True, 0) top_hbox = Gtk.HBox() top_hbox.pack_start(self.open_check, True, True, 0) top_hbox.pack_start(self.window_fit, False, True, 0) main_vbox = Gtk.VBox() main_vbox.pack_start(top_hbox, True, True, 2) main_vbox.pack_start(main_hbox, True, True, 0) self.pack_start(self.scrolled, True, True, 0) self.pack_start(main_vbox, False, True, 5) # 5 MB image cache size self.max_cache_size = 1024 * 1024 * 5 # For managing fast selection switches of covers.. self.stop_loading = False self.loading = False self.current_job = 0 self.connect('destroy', self.__save_config)
def __init__(self, songs): super().__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source'])) }) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) search_labelraw = Gtk.Label('raw') search_labelraw.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldraw = Gtk.Entry() self.search_fieldraw.connect('activate', self.start_search) self.search_fieldraw.connect('changed', self.__searchfieldchanged) search_labelclean = Gtk.Label('clean') search_labelclean.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldclean = Gtk.Label() self.search_fieldclean.set_can_focus(False) self.search_fieldclean.set_alignment(xalign=0.0, yalign=0.5) self.search_radioraw = Gtk.RadioButton(group=None, label=None) self.search_radioraw.connect("toggled", self.__searchtypetoggled, "raw") self.search_radioclean = Gtk.RadioButton(group=self.search_radioraw, label=None) self.search_radioclean.connect("toggled", self.__searchtypetoggled, "clean") #note: set_active(False) appears to have no effect #self.search_radioraw.set_active( # self.config_get_bool('searchraw', False)) if self.config_get_bool('searchraw', False): self.search_radioraw.set_active(True) else: self.search_radioclean.set_active(True) search_labelresultsmax = Gtk.Label('limit') search_labelresultsmax.set_alignment(xalign=1.0, yalign=0.5) search_labelresultsmax.set_tooltip_text( _("Per engine 'at best' results limit")) search_adjresultsmax = Gtk.Adjustment(value=int( self.config_get("resultsmax", 3)), lower=1, upper=REQUEST_LIMIT_MAX, step_incr=1, page_incr=0, page_size=0) self.search_spinresultsmax = Gtk.SpinButton( adjustment=search_adjresultsmax, climb_rate=0.2, digits=0) self.search_spinresultsmax.set_alignment(xalign=0.5) self.search_spinresultsmax.set_can_focus(False) self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) search_button_box = Gtk.Alignment() search_button_box.set(1, 0, 0, 0) search_button_box.add(self.search_button) search_table = Gtk.Table(rows=3, columns=4, homogeneous=False) search_table.attach(search_labelraw, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioraw, 1, 2, 0, 1, xoptions=0, xpadding=0) search_table.attach(self.search_fieldraw, 2, 4, 0, 1) search_table.attach(search_labelclean, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioclean, 1, 2, 1, 2, xoptions=0, xpadding=0) search_table.attach(self.search_fieldclean, 2, 4, 1, 2, xpadding=4) search_table.attach(search_labelresultsmax, 0, 2, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_spinresultsmax, 2, 3, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=0) search_table.attach(search_button_box, 3, 4, 2, 3) widget_space = 5 self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_table, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) self.connect('destroy', self.__save_config) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search()
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, songs): super(AlbumArtWindow, self).__init__() self.image_cache = [] self.image_cache_size = 10 self.search_lock = False self.set_title(_('Album Art Downloader')) self.set_icon_name(Icons.EDIT_FIND) self.set_default_size(800, 550) image = CoverArea(self, songs[0]) self.liststore = Gtk.ListStore(object, object) self.treeview = treeview = AllTreeView(model=self.liststore) self.treeview.set_headers_visible(False) self.treeview.set_rules_hint(True) targets = [("text/uri-list", 0, 0)] targets = [Gtk.TargetEntry.new(*t) for t in targets] treeview.drag_source_set( Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY) treeselection = self.treeview.get_selection() treeselection.set_mode(Gtk.SelectionMode.SINGLE) treeselection.connect('changed', self.__select_callback, image) self.treeview.connect("drag-data-get", self.__drag_data_get, treeselection) rend_pix = Gtk.CellRendererPixbuf() img_col = Gtk.TreeViewColumn('Thumb') img_col.pack_start(rend_pix, False) def cell_data_pb(column, cell, model, iter_, *args): surface = model[iter_][0] cell.set_property("surface", surface) img_col.set_cell_data_func(rend_pix, cell_data_pb, None) treeview.append_column(img_col) rend_pix.set_property('xpad', 2) rend_pix.set_property('ypad', 2) border_width = self.get_scale_factor() * 2 rend_pix.set_property('width', self.THUMB_SIZE + 4 + border_width) rend_pix.set_property('height', self.THUMB_SIZE + 4 + border_width) def escape_data(data): for rep in ('\n', '\t', '\r', '\v'): data = data.replace(rep, ' ') return util.escape(' '.join(data.split())) def cell_data(column, cell, model, iter, data): cover = model[iter][1] esc = escape_data txt = '<b><i>%s</i></b>' % esc(cover['name']) txt += "\n<small>%s</small>" % ( _('from %(source)s') % { "source": util.italic(esc(cover['source']))}) if 'resolution' in cover: txt += "\n" + _('Resolution: %s') % util.italic( esc(cover['resolution'])) if 'size' in cover: txt += "\n" + _('Size: %s') % util.italic(esc(cover['size'])) cell.markup = txt cell.set_property('markup', cell.markup) rend = Gtk.CellRendererText() rend.set_property('ellipsize', Pango.EllipsizeMode.END) info_col = Gtk.TreeViewColumn('Info', rend) info_col.set_cell_data_func(rend, cell_data) treeview.append_column(info_col) sw_list = Gtk.ScrolledWindow() sw_list.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw_list.set_shadow_type(Gtk.ShadowType.IN) sw_list.add(treeview) search_labelraw = Gtk.Label('raw') search_labelraw.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldraw = Gtk.Entry() self.search_fieldraw.connect('activate', self.start_search) self.search_fieldraw.connect('changed', self.__searchfieldchanged) search_labelclean = Gtk.Label('clean') search_labelclean.set_alignment(xalign=1.0, yalign=0.5) self.search_fieldclean = Gtk.Label() self.search_fieldclean.set_can_focus(False) self.search_fieldclean.set_alignment(xalign=0.0, yalign=0.5) self.search_radioraw = Gtk.RadioButton(group=None, label=None) self.search_radioraw.connect("toggled", self.__searchtypetoggled, "raw") self.search_radioclean = Gtk.RadioButton(group=self.search_radioraw, label=None) self.search_radioclean.connect("toggled", self.__searchtypetoggled, "clean") #note: set_active(False) appears to have no effect #self.search_radioraw.set_active( # self.config_get_bool('searchraw', False)) if self.config_get_bool('searchraw', False): self.search_radioraw.set_active(True) else: self.search_radioclean.set_active(True) search_labelresultsmax = Gtk.Label('limit') search_labelresultsmax.set_alignment(xalign=1.0, yalign=0.5) search_labelresultsmax.set_tooltip_text( _("Per engine 'at best' results limit")) search_adjresultsmax = Gtk.Adjustment( value=int(self.config_get("resultsmax", 3)), lower=1, upper=REQUEST_LIMIT_MAX, step_incr=1, page_incr=0, page_size=0) self.search_spinresultsmax = Gtk.SpinButton( adjustment=search_adjresultsmax, climb_rate=0.2, digits=0) self.search_spinresultsmax.set_alignment(xalign=0.5) self.search_spinresultsmax.set_can_focus(False) self.search_button = Button(_("_Search"), Icons.EDIT_FIND) self.search_button.connect('clicked', self.start_search) search_button_box = Gtk.Alignment() search_button_box.set(1, 0, 0, 0) search_button_box.add(self.search_button) search_table = Gtk.Table(rows=3, columns=4, homogeneous=False) search_table.attach(search_labelraw, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioraw, 1, 2, 0, 1, xoptions=0, xpadding=0) search_table.attach(self.search_fieldraw, 2, 4, 0, 1) search_table.attach(search_labelclean, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_radioclean, 1, 2, 1, 2, xoptions=0, xpadding=0) search_table.attach(self.search_fieldclean, 2, 4, 1, 2, xpadding=4) search_table.attach(search_labelresultsmax, 0, 2, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=6) search_table.attach(self.search_spinresultsmax, 2, 3, 2, 3, xoptions=Gtk.AttachOptions.FILL, xpadding=0) search_table.attach(search_button_box, 3, 4, 2, 3) widget_space = 5 self.progress = Gtk.ProgressBar() left_vbox = Gtk.VBox(spacing=widget_space) left_vbox.pack_start(search_table, False, True, 0) left_vbox.pack_start(sw_list, True, True, 0) hpaned = Paned() hpaned.set_border_width(widget_space) hpaned.pack1(left_vbox, shrink=False) hpaned.pack2(image, shrink=False) hpaned.set_position(275) self.add(hpaned) self.show_all() left_vbox.pack_start(self.progress, False, True, 0) self.connect('destroy', self.__save_config) song = songs[0] text = SEARCH_PATTERN.format(song) self.set_text(text) self.start_search()