Пример #1
0
        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
Пример #2
0
    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()
Пример #3
0
    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
Пример #4
0
    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)
Пример #5
0
    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()
Пример #6
0
    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()
Пример #7
0
    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)
Пример #8
0
 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
Пример #9
0
    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()
Пример #10
0
    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)
Пример #11
0
    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)
Пример #12
0
    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)
Пример #13
0
    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()
Пример #14
0
    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)
Пример #15
0
    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()
Пример #16
0
    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)
Пример #17
0
    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()
Пример #18
0
    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()
Пример #19
0
    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()
Пример #20
0
 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
Пример #21
0
    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()
Пример #22
0
    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()
Пример #23
0
    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()
Пример #24
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):
            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
Пример #25
0
    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)
Пример #26
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)

        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
Пример #27
0
    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)
Пример #28
0
    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()
Пример #29
0
        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()
Пример #30
0
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)
Пример #31
0
    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()
Пример #32
0
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
Пример #33
0
        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()
Пример #34
0
    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()
Пример #35
0
    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()
Пример #36
0
    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()
Пример #37
0
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()
Пример #38
0
    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()
Пример #39
0
    def __init__(self, prop, library):
        super().__init__(spacing=6)
        self.title = _("Track Numbers")
        self.set_border_width(12)

        label_start = Gtk.Label(label=_("Start fro_m:"), halign=Gtk.Align.END)
        label_start.set_use_underline(True)
        spin_start = Gtk.SpinButton()
        spin_start.set_range(0, 999)
        spin_start.set_increments(1, 10)
        spin_start.set_value(1)
        label_start.set_mnemonic_widget(spin_start)

        label_total = Gtk.Label(label=_("_Total tracks:"),
                                halign=Gtk.Align.END)
        label_total.set_use_underline(True)
        spin_total = Gtk.SpinButton()
        spin_total.set_range(0, 999)
        spin_total.set_increments(1, 10)
        label_total.set_mnemonic_widget(spin_total)

        preview = qltk.Button(_("_Preview"), Icons.VIEW_REFRESH)

        grid = Gtk.Grid(row_spacing=4, column_spacing=4)
        grid.add(label_start)
        grid.attach_next_to(spin_start, label_start, Gtk.PositionType.RIGHT, 1,
                            1)
        grid.attach_next_to(label_total, label_start, Gtk.PositionType.BOTTOM,
                            1, 1)
        grid.attach_next_to(spin_total, label_total, Gtk.PositionType.RIGHT, 1,
                            1)
        grid.attach_next_to(Align(preview, halign=Gtk.Align.END), spin_start,
                            Gtk.PositionType.RIGHT, 1, 1)
        preview.props.hexpand = True

        model = ObjectStore()
        view = HintedTreeView(model=model)

        self.pack_start(grid, False, True, 0)

        render = Gtk.CellRendererText()
        column = TreeViewColumn(title=_('File'))
        column.pack_start(render, True)
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)

        def cell_data_file(column, cell, model, iter_, data):
            entry = model.get_value(iter_)
            cell.set_property("text", entry.name)

        column.set_cell_data_func(render, cell_data_file)

        view.append_column(column)
        render = Gtk.CellRendererText()
        render.set_property('editable', True)
        column = TreeViewColumn(title=_('Track'))
        column.pack_start(render, True)
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)

        def cell_data_track(column, cell, model, iter_, data):
            entry = model.get_value(iter_)
            cell.set_property("text", entry.tracknumber)

        column.set_cell_data_func(render, cell_data_track)

        view.append_column(column)
        view.set_reorderable(True)
        w = Gtk.ScrolledWindow()
        w.set_shadow_type(Gtk.ShadowType.IN)
        w.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        w.add(view)
        self.pack_start(w, True, True, 0)

        bbox = Gtk.HButtonBox()
        bbox.set_spacing(6)
        bbox.set_layout(Gtk.ButtonBoxStyle.END)
        save = Button(_("_Save"), Icons.DOCUMENT_SAVE)
        self.save = save
        connect_obj(save, 'clicked', self.__save_files, prop, model, library)
        revert = Button(_("_Revert"), Icons.DOCUMENT_REVERT)
        self.revert = revert
        bbox.pack_start(revert, True, True, 0)
        bbox.pack_start(save, True, True, 0)
        self.pack_start(bbox, False, True, 0)

        preview_args = [spin_start, spin_total, model, save, revert]
        preview.connect('clicked', self.__preview_tracks, *preview_args)
        connect_obj(revert, 'clicked', self.__update, None, *preview_args[1:])
        spin_total.connect('value-changed', self.__preview_tracks,
                           *preview_args)
        spin_start.connect('value-changed', self.__preview_tracks,
                           *preview_args)
        connect_obj(view, 'drag-end', self.__class__.__preview_tracks, self,
                    *preview_args)
        render.connect('edited', self.__row_edited, model, preview, save)

        connect_obj(prop, 'changed', self.__class__.__update, self, spin_total,
                    model, save, revert)

        for child in self.get_children():
            child.show_all()
Пример #40
0
    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()
Пример #41
0
    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)
Пример #42
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)
Пример #43
0
    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()
Пример #44
0
        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
Пример #45
0
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
Пример #46
0
    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)
Пример #47
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().__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
Пример #48
0
    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)
Пример #49
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
Пример #50
0
    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()
Пример #51
0
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)
Пример #52
0
    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)
Пример #53
0
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()
Пример #54
0
    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()
Пример #55
0
    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()
Пример #56
0
    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()
Пример #57
0
    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()