Esempio n. 1
0
def _do_trash_songs(parent, songs, librarian):
    dialog = TrashDialog.for_songs(parent, songs)
    resp = dialog.run()
    if resp != TrashDialog.RESPONSE_TRASH:
        return

    window_title = _("Moving %(current)d/%(total)d.")

    w = WaitLoadWindow(parent, len(songs), window_title)
    w.show()

    ok = []
    failed = []
    for song in songs:
        filename = song("~filename")
        try:
            trash.trash(filename)
        except trash.TrashError as e:
            print_w("Couldn't trash file (%s)" % e)
            failed.append(song)
        else:
            ok.append(song)
        w.step()
    w.destroy()

    if failed:
        ErrorMessage(parent,
            _("Unable to move to trash"),
            _("Moving one or more files to the trash failed.")
        ).run()

    if ok:
        librarian.remove(ok)
Esempio n. 2
0
def MenuItems(marks, player, seekable):
    sizes = Gtk.SizeGroup(mode=Gtk.SizeGroupMode.HORIZONTAL)
    items = []
    if not marks or marks[0][0] != 0:
        # Translators: Refers to the beginning of the playing song.
        marks.insert(0, (0, _("Beginning")))
    for time, mark in marks:
        i = Gtk.MenuItem()
        # older pygobject (~3.2) added a child on creation
        if i.get_child():
            i.remove(i.get_child())
        connect_obj(i, 'activate', player.seek, time * 1000)
        i.set_sensitive(time >= 0 and seekable)
        hbox = Gtk.HBox(spacing=12)
        i.add(hbox)
        if time < 0:
            l = Gtk.Label(label=_("N/A"))
        else:
            l = Gtk.Label(label=util.format_time(time))
        l.set_alignment(0.0, 0.5)
        sizes.add_widget(l)
        hbox.pack_start(l, False, True, 0)
        text = Gtk.Label(mark)
        text.set_max_width_chars(80)
        text.set_ellipsize(Pango.EllipsizeMode.END)
        text.set_alignment(0.0, 0.5)
        hbox.pack_start(text, True, True, 0)
        i.show_all()
        items.append(i)
    return items
Esempio n. 3
0
    def __search(self, song, buffer, refresh, add):
        artist = song.comma("artist")
        title = song.comma("title")

        try:
            sock = urlopen(
                "http://lyricwiki.org/api.php?"
                "client=QuodLibet&func=getSong&artist=%s&song=%s&fmt=text" % (
                quote(artist.encode('utf-8')),
                quote(title.encode('utf-8'))))
            text = sock.read()
        except Exception as err:
            encoding = util.get_locale_encoding()
            try:
                err = err.strerror.decode(encoding, 'replace')
            except:
                err = _("Unable to download lyrics.")
            GLib.idle_add(buffer.set_text, err)
            return

        sock.close()

        if text == 'Not found':
            GLib.idle_add(
                buffer.set_text, _("No lyrics found for this song."))
            return
        else:
            GLib.idle_add(buffer.set_text, text)
            GLib.idle_add(refresh.set_sensitive, True)
Esempio n. 4
0
def _do_trash_files(parent, paths):
    dialog = TrashDialog.for_files(parent, paths)
    resp = dialog.run()
    if resp != TrashDialog.RESPONSE_TRASH:
        return

    window_title = _("Moving %(current)d/%(total)d.")
    w = WaitLoadWindow(parent, len(paths), window_title)
    w.show()

    ok = []
    failed = []
    for path in paths:
        try:
            trash.trash(path)
        except trash.TrashError:
            failed.append(path)
        else:
            ok.append(path)
        w.step()
    w.destroy()

    if failed:
        ErrorMessage(parent,
            _("Unable to move to trash"),
            _("Moving one or more files to the trash failed.")
        ).run()
Esempio n. 5
0
    def __set_inhibit_play(self, inhibit):
        """Change the inhibit state"""

        if inhibit == self._inhibit_play:
            return
        self._inhibit_play = inhibit

        # task management
        if inhibit:
            if not self._task:
                def stop_buf(*args):
                    self._player.paused = True

                self._task = Task(_("Stream"), _("Buffering"), stop=stop_buf)
        elif self._task:
            self._task.finish()
            self._task = None

        # state management
        if inhibit:
            # save the current state
            status, state, pending = self.bin.get_state(
                timeout=STATE_CHANGE_TIMEOUT)
            if status == Gst.StateChangeReturn.SUCCESS and \
                    state == Gst.State.PLAYING:
                self._wanted_state = state
            else:
                # no idea, at least don't play
                self._wanted_state = Gst.State.PAUSED

            self.bin.set_state(Gst.State.PAUSED)
        else:
            # restore the old state
            self.bin.set_state(self._wanted_state)
            self._wanted_state = None
Esempio n. 6
0
def check_wrapper_changed(library, parent, songs):
    needs_write = filter(lambda s: s._needs_write, songs)

    if needs_write:
        win = WritingWindow(parent, len(needs_write))
        win.show()
        for song in needs_write:
            try:
                song._song.write()
            except AudioFileError as e:
                qltk.ErrorMessage(
                    None, _("Unable to edit song"),
                    _("Saving <b>%s</b> failed. The file "
                      "may be read-only, corrupted, or you "
                      "do not have permission to edit it.") %
                    util.escape(song('~basename'))).run()
                print_d("Couldn't save song %s (%s)" % (song("~filename"), e))

            if win.step():
                break
        win.destroy()

    changed = []
    for song in songs:
        if song._was_updated():
            changed.append(song._song)
        elif not song.valid() and song.exists():
            library.reload(song._song)
    library.changed(changed)
Esempio n. 7
0
    def __init__(self, parent, song):
        super(Gtk.VBox, self).__init__()

        self.dialog = parent
        self.song = song
        self.original_bpm = song["bpm"] if "bpm" in song else _("n/a")

        self.set_margin_bottom(6)
        self.set_spacing(6)

        box = Gtk.HBox()
        box.set_spacing(6)
        # TRANSLATORS: BPM mean "beats per minute"
        box.pack_start(Gtk.Label(_("BPM:")), False, True, 0)
        self.bpm_label = Gtk.Label(_("n/a"))
        self.bpm_label.set_xalign(0.5)
        box.pack_start(self.bpm_label, True, True, 0)

        self.reset_btn = Gtk.Button(label=_("Reset"))
        self.reset_btn.connect('clicked', lambda *x: self.reset())
        box.pack_end(self.reset_btn, False, True, 0)

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

        self.tap_btn = Gtk.Button(label=_("Tap"))
        self.tap_btn.connect('button-press-event', self.tap)
        self.tap_btn.connect('key-press-event', self.key_tap)
        self.pack_start(self.tap_btn, True, True, 0)

        self.init_tap()
        self.update()

        self.show_all()
Esempio n. 8
0
 def create_visible_columns_frame():
     buttons = {}
     vbox = Gtk.VBox(spacing=12)
     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)
     return qltk.Frame(_("Visible Columns"), child=vbox), buttons
Esempio n. 9
0
    def plugin_songs(self, songs):
        value = -1
        while not 0 <= value <= 1:
            input_string = GetStringDialog(
                self.plugin_window,
                self.PLUGIN_NAME,
                _("Please give your desired rating on a scale "
                  "from 0.0 to 1.0"),
                _("_Apply"),
                Icons.NONE
            ).run()

            if input_string is None:
                return

            try:
                value = float(input_string)
            except ValueError:
                continue

        count = len(songs)
        if (count > 1 and config.getboolean("browsers",
                "rating_confirm_multiple")):
            confirm_dialog = ConfirmRateMultipleDialog(
                self.plugin_window, count, value)
            if confirm_dialog.run() != Gtk.ResponseType.YES:
                return

        for song in songs:
            song["~#rating"] = value
Esempio n. 10
0
    def __drag_data_received(self, view, ctx, x, y, sel, tid, etime):
        view.emit_stop_by_name('drag-data-received')
        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)
        if tid == DND_URI_LIST:
            uri = sel.get_uris()[0]
        elif tid == DND_MOZ_URL:
            uri = sel.data.decode('utf16', 'replace').split('\n')[0]
        else:
            ctx.finish(False, False, etime)
            return

        ctx.finish(True, False, etime)

        feed = Feed(uri.encode("ascii", "replace"))
        feed.changed = feed.parse()
        if feed:
            self.__feeds.append(row=[feed])
            AudioFeeds.write()
        else:
            ErrorMessage(
                self, _("Unable to add feed"),
                _("%s could not be added. The server may be down, "
                  "or the location may not be an audio feed.") %
                util.bold(util.escape(feed.uri))).run()
Esempio n. 11
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 set of separators to use when splitting tag values "
                  "in the tag editor. "
                  "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
Esempio n. 12
0
    def __configure_buttons(self, library):
        new_pl = qltk.Button(None, Icons.DOCUMENT_NEW, Gtk.IconSize.MENU)
        new_pl.set_tooltip_text(_("New"))
        new_pl.connect('clicked', self.__new_playlist, library)
        import_pl = qltk.Button(None, Icons.LIST_ADD,
                                Gtk.IconSize.MENU)
        import_pl.set_tooltip_text(_("Import"))
        import_pl.connect('clicked', self.__import, library)

        fb = Gtk.FlowBox()
        fb.set_selection_mode(Gtk.SelectionMode.NONE)
        fb.set_homogeneous(True)
        fb.insert(new_pl, 0)
        fb.insert(import_pl, 1)
        fb.set_max_children_per_line(2)

        # The pref button is in its own flowbox instead of directly under the
        # HBox to make it the same height as the other buttons
        pref = PreferencesButton(self)
        fb2 = Gtk.FlowBox()
        fb2.insert(pref, 0)

        hb = Gtk.HBox()
        hb.pack_start(fb, True, True, 0)
        hb.pack_start(fb2, False, False, 0)
        self.pack_start(hb, False, False, 0)
Esempio n. 13
0
    def __popup_menu(self, view, library):
        model, itr = view.get_selection().get_selected()
        if itr is None:
            return
        songs = list(model[itr][0])
        songs = [s for s in songs if isinstance(s, AudioFile)]
        menu = SongsMenu(library, songs,
                         playlists=False, remove=False,
                         ratings=False)
        menu.preseparate()

        def _remove(model, itr):
            playlist = model[itr][0]
            dialog = ConfirmRemovePlaylistDialog(self, playlist)
            if dialog.run() == Gtk.ResponseType.YES:
                playlist.delete()
                model.get_model().remove(
                    model.convert_iter_to_child_iter(itr))

        rem = MenuItem(_("_Delete"), Icons.EDIT_DELETE)
        connect_obj(rem, 'activate', _remove, model, itr)
        menu.prepend(rem)

        def _rename(path):
            self._start_rename(path)

        ren = qltk.MenuItem(_("_Rename"), Icons.EDIT)
        qltk.add_fake_accel(ren, "F2")
        connect_obj(ren, 'activate', _rename, model.get_path(itr))
        menu.prepend(ren)

        playlist = model[itr][0]
        PLAYLIST_HANDLER.populate_menu(menu, library, self, [playlist])
        menu.show_all()
        return view.popup_menu(menu, 0, Gtk.get_current_event_time())
Esempio n. 14
0
    def __edit_tag(self, renderer, path, new_value, model):
        new_value = gdecode(new_value)
        new_value = ', '.join(new_value.splitlines())
        path = Gtk.TreePath.new_from_string(path)
        entry = model[path][0]
        error_dialog = None

        if not massagers.is_valid(entry.tag, new_value):
            error_dialog = qltk.WarningMessage(
                self, _("Invalid value"),
                _("Invalid value: <b>%(value)s</b>\n\n%(error)s") % {
                "value": new_value,
                "error": massagers.error_message(entry.tag, new_value)})
        else:
            new_value = massagers.validate(entry.tag, new_value)

        comment = entry.value
        changed = comment.text != new_value
        if (changed and ((comment.shared and comment.complete) or new_value)) \
                or (new_value and comment.shared and not comment.complete):
            # only give an error if we would have applied the value
            if error_dialog is not None:
                error_dialog.run()
                return
            entry.value = Comment(new_value)
            entry.edited = True
            entry.deleted = False
            model.path_changed(path)
Esempio n. 15
0
    def __mkdir(self, button):
        model, paths = self.get_selection().get_selected_rows()
        if len(paths) != 1:
            return

        path = paths[0]
        directory = model[path][0]

        dir_ = GetStringDialog(
            None, _("New Folder"), _("Enter a name for the new folder:")).run()

        if not dir_:
            return

        dir_ = glib2fsn(dir_)
        fullpath = os.path.realpath(os.path.join(directory, dir_))

        try:
            os.makedirs(fullpath)
        except EnvironmentError as err:
            error = "<b>%s</b>: %s" % (err.filename, err.strerror)
            qltk.ErrorMessage(
                None, _("Unable to create folder"), error).run()
            return

        self.emit('test-expand-row', model.get_iter(path), path)
        self.expand_row(path, False)
Esempio n. 16
0
    def __init__(self, browser):
        super(PreferencesButton, self).__init__()

        self._menu = menu = Gtk.Menu()

        wide_mode = ConfigCheckMenuItem(
            _("_Wide Mode"), "browsers", "pane_wide_mode", True)
        wide_mode.connect("toggled", self.__wide_mode_changed, browser)
        menu.append(wide_mode)

        pref_item = MenuItem(_("_Preferences"), Icons.PREFERENCES_SYSTEM)

        def preferences_cb(menu_item):
            window = Preferences(browser)
            window.show()
        pref_item.connect("activate", preferences_cb)
        menu.append(pref_item)

        menu.show_all()

        button = MenuButton(
                SymbolicIconImage(Icons.EMBLEM_SYSTEM, Gtk.IconSize.MENU),
                arrow=True)
        button.set_menu(menu)
        button.show()
        self.pack_start(button, True, True, 0)
Esempio n. 17
0
    def __create_children(self, menu, songs):
        self.__remove_children(menu)
        for song in songs:
            marks = song.bookmarks
            if marks:
                fake_player = self.FakePlayer(song)

                song_item = Gtk.MenuItem(song.comma("title"))
                song_menu = Gtk.Menu()
                song_item.set_submenu(song_menu)
                menu.append(song_item)

                items = qltk.bookmarks.MenuItems(marks, fake_player, True)
                for item in items:
                    song_menu.append(item)

                song_menu.append(SeparatorMenuItem())
                i = qltk.MenuItem(_(u"_Edit Bookmarks…"), Icons.EDIT)

                def edit_bookmarks_cb(menu_item):
                    window = EditBookmarks(self.plugin_window, app.library,
                                           fake_player)
                    window.show()
                i.connect('activate', edit_bookmarks_cb)
                song_menu.append(i)

        if menu.get_active() is None:
            no_marks = Gtk.MenuItem(_("No Bookmarks"))
            no_marks.set_sensitive(False)
            menu.append(no_marks)

        menu.show_all()
Esempio n. 18
0
    def __init__(self, album):
        self.album = album
        self._release = None
        self.model = ObjectStore()
        self.model.append_many(album)

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

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

        for title, func, resize, mode in cols:
            render = Gtk.CellRendererText()
            render.set_property('ellipsize', mode)
            col = Gtk.TreeViewColumn(title, render)
            col.set_cell_data_func(render, func)
            col.set_resizable(resize)
            col.set_expand(resize)
            self.append_column(col)
Esempio n. 19
0
    def __init__(self, parent, default="", **kwargs):
        if self.is_not_unique():
            return
        super(TextEdit, self).__init__()
        self.set_title(_("Edit Display"))
        self.set_transient_for(qltk.get_top_parent(parent))
        self.set_border_width(12)
        self.set_default_size(420, 190)

        vbox = 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)

        self.box = box = self.Box(default, **kwargs)
        vbox.pack_start(box, True, True, 0)
        self.use_header_bar()
        if not self.has_close_button():
            vbox.pack_start(b, False, True, 0)

        self.add(vbox)
        self.apply = box.apply
        self.revert = box.revert

        close.grab_focus()
        self.get_child().show_all()
Esempio n. 20
0
    def __init__(self, browser):
        if self.is_not_unique():
            return
        super(Preferences, self).__init__()
        self.set_border_width(12)
        self.set_title(_("Playlist Browser Preferences"))
        self.set_default_size(420, 240)
        self.set_transient_for(qltk.get_top_parent(browser))

        box = Gtk.VBox(spacing=6)
        edit_frame = self.edit_display_pane(browser, _("Playlist display"))
        box.pack_start(edit_frame, False, True, 12)

        main_box = 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)

        main_box.pack_start(box, True, True, 0)
        self.use_header_bar()

        if not self.has_close_button():
            main_box.pack_start(b, False, True, 0)
        self.add(main_box)

        close.grab_focus()
        self.show_all()
Esempio n. 21
0
    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)
Esempio n. 22
0
 def _description(self, songs, box):
     text = []
     cur_disc = songs[0]("~#disc", 1) - 1
     cur_part = None
     cur_track = songs[0]("~#track", 1) - 1
     for song in songs:
         track = song("~#track", 0)
         disc = song("~#disc", 0)
         part = song.get("part")
         if disc != cur_disc:
             if cur_disc:
                 text.append("")
             cur_track = song("~#track", 1) - 1
             cur_part = None
             cur_disc = disc
             if disc:
                 text.append("<b>%s</b>" % (_("Disc %s") % disc))
         if part != cur_part:
             ts = "    " * bool(disc)
             cur_part = part
             if part:
                 text.append("%s<b>%s</b>" % (ts, util.escape(part)))
         cur_track += 1
         ts = "    " * (bool(disc) + bool(part))
         while cur_track < track:
             text.append("%s<b>%d.</b> <i>%s</i>" % (
                 ts, cur_track, _("Track unavailable")))
             cur_track += 1
         text.append("%s<b>%d.</b> %s" % (
             ts, track, util.escape(song.comma("~title~version"))))
     l = Label()
     l.set_markup("\n".join(text))
     l.set_ellipsize(Pango.EllipsizeMode.END)
     box.pack_start(Frame(_("Track List"), l), False, False, 0)
Esempio n. 23
0
 def _description(self, songs, box):
     text = []
     cur_disc = songs[0]("~#disc", 1) - 1
     cur_part = None
     cur_track = songs[0]("~#track", 1) - 1
     for song in songs:
         track = song("~#track", 0)
         disc = song("~#disc", 0)
         part = song.get("part")
         if disc != cur_disc:
             if cur_disc:
                 text.append("")
             cur_track = song("~#track", 1) - 1
             cur_part = None
             cur_disc = disc
             if disc:
                 text.append("%s" % (_("Disc %s") % disc))
         if part != cur_part:
             ts = "    " * bool(disc)
             cur_part = part
             if part:
                 text.append("%s%s" % (ts, util.escape(part)))
         cur_track += 1
         ts = "    " * (bool(disc) + bool(part))
         while cur_track < track:
             text.append("{ts}{cur: >2}. {text}".format(
                 ts=ts, cur=cur_track, text=_("Track unavailable")))
             cur_track += 1
         markup = util.escape(song.comma("~title~version"))
         text.append("{ts}{cur: >2}. <i>{text}</i>".format(
                 ts=ts, cur=track, text=markup))
     l = Label(markup="\n".join(text), ellipsize=True)
     box.pack_start(Frame(_("Track List"), l), False, False, 0)
Esempio n. 24
0
    def __init__(self, library, song, lyrics=True, bookmarks=True):
        super(OneSong, self).__init__()
        vbox = Gtk.VBox(spacing=12)
        vbox.set_border_width(12)
        self._title(song, vbox)
        self._album(song, vbox)
        self._people(song, vbox)
        self._library(song, vbox)
        self._file(song, vbox)
        self._additional(song, vbox)
        sw = SW()
        sw.title = _("Information")
        sw.add_with_viewport(vbox)
        self.append_page(sw)
        if lyrics:
            lyrics = LyricsPane(song)
            lyrics.title = _("Lyrics")
            self.append_page(lyrics)

        if bookmarks:
            bookmarks = EditBookmarksPane(None, song)
            bookmarks.title = _("Bookmarks")
            bookmarks.set_border_width(12)
            self.append_page(bookmarks)

        connect_destroy(library, 'changed', self.__check_changed, vbox, song)
Esempio n. 25
0
    def _album(self, song, box):
        if "album" not in song:
            return
        text = ["<span size='x-large'><i>%s</i></span>"
                % util.escape(song.comma("album"))]
        secondary = []
        if "discnumber" in song:
            secondary.append(_("Disc %s") % song["discnumber"])
        if "discsubtitle" in song:
            secondary.append("<i>%s</i>" %
                             util.escape(song.comma("discsubtitle")))
        if "tracknumber" in song:
            secondary.append(_("Track %s") % song["tracknumber"])
        if secondary:
            text.append(" - ".join(secondary))

        if "date" in song:
            text.append(util.escape(song.comma("date")))

        if "organization" in song or "labelid" in song:
            t = util.escape(song.comma("~organization~labelid"))
            text.append(t)

        if "producer" in song:
            text.append(_("Produced by %s") % (
                util.escape(song.comma("producer"))))

        w = Label(markup="\n".join(text), ellipsize=True)
        hb = Gtk.HBox(spacing=12)

        hb.pack_start(w, True, True, 0)
        box.pack_start(Frame(tag("album"), hb), False, False, 0)

        cover = ReactiveCoverImage(song=song)
        hb.pack_start(cover, False, True, 0)
Esempio n. 26
0
def _do_delete_files(parent, paths):
    dialog = DeleteDialog.for_files(parent, paths)
    resp = dialog.run()
    if resp != DeleteDialog.RESPONSE_DELETE:
        return

    window_title = _("Deleting %(current)d/%(total)d.")

    w = WaitLoadWindow(parent, len(paths), window_title)
    w.show()

    ok = []
    failed = []
    for path in paths:
        try:
            os.unlink(path)
        except EnvironmentError:
            failed.append(path)
        else:
            ok.append(path)
        w.step()
    w.destroy()

    if failed:
        ErrorMessage(parent,
            _("Unable to delete files"),
            _("Deleting one or more files failed.")
        ).run()
Esempio n. 27
0
    def Menu(self, songs, library, items):
        in_fav = False
        in_all = False
        for song in songs:
            if song in self.__fav_stations:
                in_fav = True
            elif song in self.__stations:
                in_all = True
            if in_fav and in_all:
                break

        iradio_items = []
        button = MenuItem(_("Add to Favorites"), Icons.LIST_ADD)
        button.set_sensitive(in_all)
        connect_obj(button, 'activate', self.__add_fav, songs)
        iradio_items.append(button)
        button = MenuItem(_("Remove from Favorites"), Icons.LIST_REMOVE)
        button.set_sensitive(in_fav)
        connect_obj(button, 'activate', self.__remove_fav, songs)
        iradio_items.append(button)

        items.append(iradio_items)
        menu = SongsMenu(self.__librarian, songs, playlists=False, remove=True,
                         queue=False, devices=False, items=items)
        return menu
Esempio n. 28
0
    def _people(self, songs, box):
        artists = set()
        performers = set()
        for song in songs:
            artists.update(song.list("artist"))
            performers.update(song.list("performer"))

        artists = sorted(artists)
        performers = sorted(performers)

        if artists:
            if len(artists) == 1:
                title = _("artist")
            else:
                title = _("artists")
            title = util.capitalize(title)
            box.pack_start(Frame(title, Label("\n".join(artists))),
                           False, False, 0)
        if performers:
            if len(artists) == 1:
                title = _("performer")
            else:
                title = _("performers")
            title = util.capitalize(title)
            box.pack_start(Frame(title, Label("\n".join(performers))),
                           False, False, 0)
Esempio n. 29
0
def _do_delete_songs(parent, songs, librarian):
    dialog = DeleteDialog.for_songs(parent, songs)
    resp = dialog.run()
    if resp != DeleteDialog.RESPONSE_DELETE:
        return

    window_title = _("Deleting %(current)d/%(total)d.")

    w = WaitLoadWindow(parent, len(songs), window_title)
    w.show()

    ok = []
    failed = []
    for song in songs:
        filename = song("~filename")
        try:
            os.unlink(filename)
        except EnvironmentError:
            failed.append(song)
        else:
            ok.append(song)
        w.step()
    w.destroy()

    if failed:
        ErrorMessage(parent,
            _("Unable to delete files"),
            _("Deleting one or more files failed.")
        ).run()

    if ok:
        librarian.remove(ok)
Esempio n. 30
0
    def __init__(self, parent, app):
        super(AboutDialog, self).__init__()
        self.set_transient_for(parent)
        self.set_program_name(app.name)
        self.set_version(quodlibet.get_build_description())
        self.set_authors(const.AUTHORS)
        self.set_artists(const.ARTISTS)
        self.set_logo_icon_name(app.icon_name)

        def chunks(l, n):
            return [l[i : i + n] for i in range(0, len(l), n)]

        is_real_player = app.player.name != "Null"

        format_names = sorted([t.format for t in formats.types])
        fmts = ",\n".join(", ".join(c) for c in chunks(format_names, 4))
        text = []
        text.append(_("Supported formats: %s") % fmts)
        text.append("")
        if is_real_player:
            text.append(_("Audio device: %s") % app.player.name)
        text.append("Python: %s" % platform.python_version())
        text.append("Mutagen: %s" % fver(mutagen.version))
        text.append("GTK+: %s (%s)" % (fver(gtk_version), get_backend_name()))
        text.append("PyGObject: %s" % fver(pygobject_version))
        if is_real_player:
            text.append(app.player.version_info)
        self.set_comments("\n".join(text))
        self.set_license_type(Gtk.License.GPL_2_0)
        self.set_translator_credits("\n".join(const.TRANSLATORS))
        self.set_website(const.WEBSITE)
        self.set_copyright(const.COPYRIGHT + "\n" + "<%s>" % const.SUPPORT_EMAIL)
Esempio n. 31
0
 def add_edit_item(cls, submenu):
     config = Gtk.MenuItem(label=_("Edit Custom Commands") + "…")
     connect_obj(config, 'activate', cls.edit_patterns, config)
     config.set_sensitive(not JSONBasedEditor.is_not_unique())
     submenu.append(SeparatorMenuItem())
     submenu.append(config)
Esempio n. 32
0
        def __init__(self):
            def create_behaviour_frame():
                vbox = Gtk.VBox(spacing=6)
                jump_button = CCB(_("_Jump to playing song automatically"),
                                  'settings',
                                  'jump',
                                  populate=True,
                                  tooltip=_("When the playing song changes, "
                                            "scroll to it in the song list"))
                autosort_button = CCB(
                    _("Sort songs when tags are modified"),
                    'song_list',
                    'auto_sort',
                    populate=True,
                    tooltip=_(
                        "Automatically re-sort songs in the song list when "
                        "tags are modified"))
                vbox.pack_start(jump_button, False, True, 0)
                vbox.pack_start(autosort_button, False, True, 0)
                return qltk.Frame(_("Behavior"), child=vbox)

            def create_visible_columns_frame():
                buttons = {}
                vbox = Gtk.VBox(spacing=12)
                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)
                return qltk.Frame(_("Visible Columns"), child=vbox), buttons

            def create_columns_prefs_frame():
                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")]
                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)
                return qltk.Frame(_("Column Preferences"), child=t)

            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

            super().__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Song List")
            self.pack_start(create_behaviour_frame(), False, True, 0)
            columns_frame, buttons = create_visible_columns_frame()
            self.pack_start(columns_frame, False, True, 0)
            self.pack_start(create_columns_prefs_frame(), False, True, 0)
            self.pack_start(create_apply_button(), True, True, 0)
            self.__update(buttons, self._toggle_data, get_columns())

            for child in self.get_children():
                child.show_all()
Esempio n. 33
0
        def ratings_vbox(self):
            """Returns a new VBox containing all ratings widgets"""
            vb = Gtk.VBox(spacing=6)

            # Default Rating
            model = Gtk.ListStore(float)
            default_combo = Gtk.ComboBox(model=model)
            default_lab = Gtk.Label(label=_("_Default rating:"))
            default_lab.set_use_underline(True)
            default_lab.set_alignment(0, 0.5)

            def draw_rating(column, cell, model, it, data):
                num = model[it][0]
                text = "%0.2f: %s" % (num, util.format_rating(num))
                cell.set_property('text', text)

            def default_rating_changed(combo, model):
                it = combo.get_active_iter()
                if it is None:
                    return
                RATINGS.default = model[it][0]
                qltk.redraw_all_toplevels()

            def populate_default_rating_model(combo, num):
                model = combo.get_model()
                model.clear()
                deltas = []
                default = RATINGS.default
                precision = RATINGS.precision
                for i in range(0, num + 1):
                    r = i * precision
                    model.append(row=[r])
                    deltas.append((abs(default - r), i))
                active = sorted(deltas)[0][1]
                print_d("Choosing #%d (%.2f), closest to current %.2f" %
                        (active, precision * active, default))
                combo.set_active(active)

            cell = Gtk.CellRendererText()
            default_combo.pack_start(cell, True)
            default_combo.set_cell_data_func(cell, draw_rating, None)
            default_combo.connect('changed', default_rating_changed, model)
            default_lab.set_mnemonic_widget(default_combo)

            def refresh_default_combo(num):
                populate_default_rating_model(default_combo, num)

            # Rating Scale
            model = Gtk.ListStore(int)
            scale_combo = Gtk.ComboBox(model=model)
            scale_lab = Gtk.Label(label=_("Rating _scale:"))
            scale_lab.set_use_underline(True)
            scale_lab.set_mnemonic_widget(scale_combo)

            cell = Gtk.CellRendererText()
            scale_combo.pack_start(cell, False)
            num = RATINGS.number
            for i in [1, 2, 3, 4, 5, 6, 8, 10]:
                it = model.append(row=[i])
                if i == num:
                    scale_combo.set_active_iter(it)

            def draw_rating_scale(column, cell, model, it, data):
                num_stars = model[it][0]
                text = "%d: %s" % (num_stars, RATINGS.full_symbol * num_stars)
                cell.set_property('text', text)

            def rating_scale_changed(combo, model):
                it = combo.get_active_iter()
                if it is None:
                    return
                RATINGS.number = num = model[it][0]
                refresh_default_combo(num)

            refresh_default_combo(RATINGS.number)
            scale_combo.set_cell_data_func(cell, draw_rating_scale, None)
            scale_combo.connect('changed', rating_scale_changed, model)

            default_align = Align(halign=Gtk.Align.START)
            default_align.add(default_lab)
            scale_align = Align(halign=Gtk.Align.START)
            scale_align.add(scale_lab)

            grid = Gtk.Grid(column_spacing=6, row_spacing=6)
            grid.add(scale_align)
            grid.add(scale_combo)
            grid.attach(default_align, 0, 1, 1, 1)
            grid.attach(default_combo, 1, 1, 1, 1)
            vb.pack_start(grid, False, False, 6)

            # Bayesian Factor
            bayesian_factor = config.getfloat("settings",
                                              "bayesian_rating_factor", 0.0)
            adj = Gtk.Adjustment.new(bayesian_factor, 0.0, 10.0, 0.5, 0.5, 0.0)
            bayes_spin = Gtk.SpinButton(adjustment=adj, numeric=True)
            bayes_spin.set_digits(1)
            bayes_spin.connect('changed', self.__changed_and_signal_library,
                               'settings', 'bayesian_rating_factor')
            bayes_spin.set_tooltip_text(
                _("Bayesian Average factor (C) for aggregated ratings.\n"
                  "0 means a conventional average, higher values mean that "
                  "albums with few tracks will have less extreme ratings. "
                  "Changing this value triggers a re-calculation for all "
                  "albums."))
            bayes_label = Gtk.Label(label=_("_Bayesian averaging amount:"))
            bayes_label.set_use_underline(True)
            bayes_label.set_mnemonic_widget(bayes_spin)

            # Save Ratings
            hb = Gtk.HBox(spacing=6)
            hb.pack_start(bayes_label, False, True, 0)
            hb.pack_start(bayes_spin, False, True, 0)
            vb.pack_start(hb, True, True, 0)
            cb = CCB(_("Save ratings and play _counts in tags"),
                     "editing",
                     "save_to_songs",
                     populate=True)

            def update_entry(widget, email_entry):
                email_entry.set_sensitive(widget.get_active())

            vb.pack_start(cb, True, True, 0)
            hb = Gtk.HBox(spacing=6)
            lab = Gtk.Label(label=_("_Email:"))
            entry = UndoEntry()
            entry.set_tooltip_text(
                _("Ratings and play counts will be saved "
                  "in tags for this email address"))
            entry.set_text(config.get("editing", "save_email"))
            entry.connect('changed', self.__changed, 'editing', 'save_email')

            # Disable the entry if not saving to tags
            cb.connect('clicked', update_entry, entry)
            update_entry(cb, entry)

            hb.pack_start(lab, False, True, 0)
            hb.pack_start(entry, True, True, 0)
            lab.set_mnemonic_widget(entry)
            lab.set_use_underline(True)
            vb.pack_start(hb, True, True, 0)

            return vb
Esempio n. 34
0
    class SongList(Gtk.VBox):
        name = "songlist"

        PREDEFINED_TAGS = [("~#disc", _("_Disc")), ("~#track", _("_Track")),
                           ("grouping", _("Grou_ping")),
                           ("artist", _("_Artist")), ("album", _("Al_bum")),
                           ("title", util.tag("title")),
                           ("genre", _("_Genre")), ("date", _("_Date")),
                           ("~basename", _("_Filename")),
                           ("~#length", _("_Length")),
                           ("~rating", _("_Rating")),
                           ("~#filesize", util.tag("~#filesize"))]

        def __init__(self):
            def create_behaviour_frame():
                vbox = Gtk.VBox(spacing=6)
                jump_button = CCB(_("_Jump to playing song automatically"),
                                  'settings',
                                  'jump',
                                  populate=True,
                                  tooltip=_("When the playing song changes, "
                                            "scroll to it in the song list"))
                autosort_button = CCB(
                    _("Sort songs when tags are modified"),
                    'song_list',
                    'auto_sort',
                    populate=True,
                    tooltip=_(
                        "Automatically re-sort songs in the song list when "
                        "tags are modified"))
                vbox.pack_start(jump_button, False, True, 0)
                vbox.pack_start(autosort_button, False, True, 0)
                return qltk.Frame(_("Behavior"), child=vbox)

            def create_visible_columns_frame():
                buttons = {}
                vbox = Gtk.VBox(spacing=12)
                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)
                return qltk.Frame(_("Visible Columns"), child=vbox), buttons

            def create_columns_prefs_frame():
                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")]
                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)
                return qltk.Frame(_("Column Preferences"), child=t)

            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

            super().__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Song List")
            self.pack_start(create_behaviour_frame(), False, True, 0)
            columns_frame, buttons = create_visible_columns_frame()
            self.pack_start(columns_frame, False, True, 0)
            self.pack_start(create_columns_prefs_frame(), False, True, 0)
            self.pack_start(create_apply_button(), True, True, 0)
            self.__update(buttons, self._toggle_data, get_columns())

            for child in self.get_children():
                child.show_all()

        def __update(self, buttons, toggle_data, columns):
            """Updates all widgets based on the passed column list"""

            columns = list(columns)

            for key, widget in buttons.items():
                widget.set_active(key in columns)
                if key in columns:
                    columns.remove(key)

            for (check, off, on) in toggle_data:
                if on in columns:
                    buttons[off].set_active(True)
                    check.set_active(True)
                    columns.remove(on)

            self.others.set_text(", ".join(columns))
            self.other_cols = columns

        def __get_current_columns(self, buttons):
            """Given the current column list and the widgets states compute
            a new column list.
            """

            new_headers = set()
            # Get the checked headers
            for key, name in self.PREDEFINED_TAGS:
                if buttons[key].get_active():
                    new_headers.add(key)
                # And the customs
            new_headers.update(set(self.other_cols))

            on_to_off = dict((on, off) for (w, off, on) in self._toggle_data)
            result = []
            cur_cols = get_columns()
            for h in cur_cols:
                if h in new_headers:
                    result.append(h)
                else:
                    try:
                        alternative = on_to_off[h]
                        if alternative in new_headers:
                            result.append(alternative)
                    except KeyError:
                        pass

            # Add new ones on the end
            result.extend(new_headers - set(result))

            # After this, do the substitutions
            for (check, off, on) in self._toggle_data:
                if check.get_active():
                    try:
                        result[result.index(off)] = on
                    except ValueError:
                        pass

            return result

        def __apply(self, button, buttons):
            result = self.__get_current_columns(buttons)
            SongList.set_all_column_headers(result)

        def __config_cols(self, button, buttons):
            def __closed(widget):
                cols = widget.get_strings()
                self.__update(buttons, self._toggle_data, cols)

            columns = self.__get_current_columns(buttons)
            m = TagListEditor(_("Edit Columns"), columns)
            m.set_transient_for(qltk.get_top_parent(self))
            m.connect('destroy', __closed)
            m.show()
Esempio n. 35
0
        def __init__(self):
            super().__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Playback")

            # player backend
            if app.player and hasattr(app.player, 'PlayerPreferences'):
                player_prefs = app.player.PlayerPreferences()
                f = qltk.Frame(_("Output Configuration"), child=player_prefs)
                self.pack_start(f, False, True, 0)

            # replaygain
            fallback_gain = config.getfloat("player", "fallback_gain", 0.0)
            adj = Gtk.Adjustment.new(fallback_gain, -12.0, 12.0, 0.5, 0.5, 0.0)
            fb_spin = Gtk.SpinButton(adjustment=adj)
            fb_spin.set_digits(1)
            fb_spin.connect('changed', self.__changed, 'player',
                            'fallback_gain')
            fb_spin.set_tooltip_text(
                _("If no Replay Gain information is available "
                  "for a song, scale the volume by this value"))

            fb_label = Gtk.Label(label=_("_Fall-back gain (dB):"))
            fb_label.set_use_underline(True)
            fb_label.set_mnemonic_widget(fb_spin)

            pre_amp_gain = config.getfloat("player", "pre_amp_gain", 0.0)
            adj = Gtk.Adjustment.new(pre_amp_gain, -12, 12, 0.5, 0.5, 0.0)
            adj.connect('value-changed', self.__changed, 'player',
                        'pre_amp_gain')
            pre_spin = Gtk.SpinButton(adjustment=adj)
            pre_spin.set_digits(1)
            pre_spin.set_tooltip_text(
                _("Scale volume for all songs by this value, "
                  "as long as the result will not clip"))

            pre_label = Gtk.Label(label=_("_Pre-amp gain (dB):"))
            pre_label.set_use_underline(True)
            pre_label.set_mnemonic_widget(pre_spin)

            widgets = [pre_label, pre_spin, fb_label, fb_spin]
            c = CCB(_("_Enable Replay Gain volume adjustment"),
                    "player",
                    "replaygain",
                    populate=True)
            c.connect('toggled', self.__toggled_gain, widgets)

            # packing
            table = Gtk.Table.new(3, 2, False)
            table.set_col_spacings(6)
            table.set_row_spacings(6)

            table.attach(c, 0, 2, 0, 1)
            fb_label.set_alignment(0, 0.5)
            table.attach(fb_label, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.FILL)
            pre_label.set_alignment(0, 0.5)
            table.attach(pre_label,
                         0,
                         1,
                         2,
                         3,
                         xoptions=Gtk.AttachOptions.FILL)

            fb_align = Align(halign=Gtk.Align.START)
            fb_align.add(fb_spin)
            table.attach(fb_align, 1, 2, 1, 2)

            pre_align = Align(halign=Gtk.Align.START)
            pre_align.add(pre_spin)
            table.attach(pre_align, 1, 2, 2, 3)

            f = qltk.Frame(_("Replay Gain Volume Adjustment"), child=table)

            c.emit('toggled')

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

            vbox = Gtk.VBox()
            c = CCB(_("_Continue playback on startup"),
                    "player",
                    "restore_playing",
                    populate=True,
                    tooltip=_("If music is playing on shutdown, automatically "
                              "start playing on next startup"))
            vbox.pack_start(c, False, False, 0)

            f = qltk.Frame(_("Startup"), child=vbox)
            self.pack_start(f, False, True, 0)

            for child in self.get_children():
                child.show_all()
Esempio n. 36
0
        def __init__(self):
            def create_display_frame():
                vbox = Gtk.VBox(spacing=6)
                model = Gtk.ListStore(str, str)

                def on_changed(combo):
                    it = combo.get_active_iter()
                    if it is None:
                        return
                    DURATION.format = model[it][0]
                    app.window.songlist.info.refresh()
                    app.window.qexpander.refresh()
                    # TODO: refresh info windows ideally too (but see #2019)

                def draw_duration(column, cell, model, it, data):
                    df, example = model[it]
                    cell.set_property('text', example)

                for df in sorted(DurationFormat.values):
                    # 4954s == longest ever CD, FWIW
                    model.append([df, format_time_preferred(4954, df)])
                duration = Gtk.ComboBox(model=model)
                cell = Gtk.CellRendererText()
                duration.pack_start(cell, True)
                duration.set_cell_data_func(cell, draw_duration, None)
                index = sorted(DurationFormat.values).index(DURATION.format)
                duration.set_active(index)
                duration.connect('changed', on_changed)
                hbox = Gtk.HBox(spacing=6)
                label = Gtk.Label(label=_("Duration totals") + ":",
                                  use_underline=True)
                label.set_mnemonic_widget(duration)
                hbox.pack_start(label, False, True, 0)
                hbox.pack_start(duration, False, True, 0)

                vbox.pack_start(hbox, False, True, 0)
                return qltk.Frame(_("Display"), child=vbox)

            def create_search_frame():
                vb = Gtk.VBox(spacing=6)
                hb = Gtk.HBox(spacing=6)
                l = Gtk.Label(label=_("_Global filter:"))
                l.set_use_underline(True)
                e = ValidatingEntry(Query.validator)
                e.set_text(config.get("browsers", "background"))
                e.connect('changed', self._entry, 'background', 'browsers')
                e.set_tooltip_text(
                    _("Apply this query in addition to all others"))
                l.set_mnemonic_widget(e)
                hb.pack_start(l, False, True, 0)
                hb.pack_start(e, True, True, 0)
                vb.pack_start(hb, False, True, 0)
                # Translators: The heading of the preference group, no action
                return qltk.Frame(C_("heading", "Search"), child=vb)

            super().__init__(spacing=12)
            self.set_border_width(12)
            self.title = _("Browsers")
            self.pack_start(create_search_frame(), False, True, 0)
            self.pack_start(create_display_frame(), False, True, 0)

            # Ratings
            vb = Gtk.VBox(spacing=6)
            c1 = CCB(_("Confirm _multiple ratings"),
                     'browsers',
                     'rating_confirm_multiple',
                     populate=True,
                     tooltip=_("Ask for confirmation before changing the "
                               "rating of multiple songs at once"))

            c2 = CCB(_("Enable _one-click ratings"),
                     'browsers',
                     'rating_click',
                     populate=True,
                     tooltip=_("Enable rating by clicking on the rating "
                               "column in the song list"))

            vbox = Gtk.VBox(spacing=6)
            vbox.pack_start(c1, False, True, 0)
            vbox.pack_start(c2, False, True, 0)
            f = qltk.Frame(_("Ratings"), child=vbox)
            self.pack_start(f, False, True, 0)

            vb = Gtk.VBox(spacing=6)

            # Filename choice algorithm config
            cb = CCB(_("Prefer _embedded art"),
                     'albumart',
                     'prefer_embedded',
                     populate=True,
                     tooltip=_("Choose to use artwork embedded in the audio "
                               "(where available) over other sources"))
            vb.pack_start(cb, False, True, 0)

            hb = Gtk.HBox(spacing=3)

            preferred_image_filename_tooltip = _(
                "The album art image file(s) to use when available "
                "(supports wildcards). If you want to supply more "
                "than one, separate them with commas.")

            cb = CCB(_("_Preferred image filename(s):"),
                     'albumart',
                     'force_filename',
                     populate=True,
                     tooltip=preferred_image_filename_tooltip)
            hb.pack_start(cb, False, True, 0)

            entry = UndoEntry()
            entry.set_tooltip_text(preferred_image_filename_tooltip)
            entry.set_text(config.get("albumart", "filename"))
            entry.connect('changed', self.__changed_text, 'filename')
            # Disable entry when not forcing
            entry.set_sensitive(cb.get_active())
            cb.connect('toggled', self.__toggled_force_filename, entry)
            hb.pack_start(entry, True, True, 0)
            vb.pack_start(hb, False, True, 0)

            f = qltk.Frame(_("Album Art"), child=vb)
            self.pack_start(f, False, True, 0)

            for child in self.get_children():
                child.show_all()
Esempio n. 37
0
class Command(JSONObject):
    """
    Wraps an arbitrary shell command and its argument pattern.
    Serialises as JSON for some editability
    """

    NAME = _("Command")

    FIELDS = {
        "name":
        Field(_("name"), _("The name of this command")),
        "command":
        Field(_("command"), _("The shell command syntax to run")),
        "parameter":
        Field(
            _("parameter"),
            _("If specified, a parameter whose occurrences in "
              "the command will be substituted with a "
              "user-supplied value, e.g. by using 'PARAM' "
              "all instances of '{PARAM}' in your command will "
              "have the value prompted for when run")),
        "pattern":
        Field(
            _("pattern"),
            _("The QL pattern, e.g. <~filename>, to use to "
              "compute a value for the command. For playlists, "
              "this also supports virtual tags <~playlistname> "
              "and <~#playlistindex>.")),
        "unique":
        Field(
            _("unique"),
            _("If set, this will remove duplicate computed values "
              "of the pattern")),
        "max_args":
        Field(
            _("max args"),
            _("The maximum number of argument to pass to the "
              "command at one time (like xargs)")),
    }

    def __init__(self,
                 name=None,
                 command=None,
                 pattern="<~filename>",
                 unique=False,
                 parameter=None,
                 max_args=10000,
                 warn_threshold=50):
        JSONObject.__init__(self, name)
        self.command = str(command or "")
        self.pattern = str(pattern)
        self.unique = bool(unique)
        self.max_args = max_args
        self.parameter = str(parameter or "")
        self.__pat = Pattern(self.pattern)
        self.warn_threshold = warn_threshold

    def run(self, songs, playlist_name=None):
        """
        Runs this command on `songs`,
        splitting into multiple calls if necessary.
        `playlist_name` if populated contains the Playlist's name.
        """
        args = []
        template_vars = {}
        if self.parameter:
            value = GetStringDialog(None, _("Input value"),
                                    _("Value for %s?") % self.parameter).run()
            template_vars[self.parameter] = value
        if playlist_name:
            print_d("Playlist command for %s" % playlist_name)
            template_vars["PLAYLIST"] = playlist_name
        self.command = self.command.format(**template_vars)
        print_d("Actual command=%s" % self.command)
        for i, song in enumerate(songs):
            wrapped = SongWrapper(song)
            if playlist_name:
                wrapped["~playlistname"] = playlist_name
                wrapped["~playlistindex"] = str(i + 1)
                wrapped["~#playlistindex"] = i + 1
            arg = str(self.__pat.format(wrapped))
            if not arg:
                print_w("Couldn't build shell command using \"%s\"."
                        "Check your pattern?" % self.pattern)
                break
            if not self.unique:
                args.append(arg)
            elif arg not in args:
                args.append(arg)
        max = int((self.max_args or 10000))
        com_words = self.command.split(" ")
        while args:
            print_d(
                "Running %s with %d substituted arg(s) (of %d%s total)..." %
                (self.command, min(max, len(args)), len(args),
                 " unique" if self.unique else ""))
            util.spawn(com_words + args[:max])
            args = args[max:]

    @property
    def playlists_only(self):
        return ("~playlistname" in self.pattern
                or "playlistindex" in self.pattern)

    def __str__(self):
        return 'Command: "{command} {pattern}"'.format(**dict(self.data))
Esempio n. 38
0
class AutoLibraryUpdate(EventPlugin):
    PLUGIN_ID = "Automatic library update"
    PLUGIN_NAME = _("Automatic Library Update")
    PLUGIN_DESC = _("Keeps your library up to date with inotify. "
                    "Requires %s.") % "pyinotify"
    PLUGIN_ICON = Icons.VIEW_REFRESH

    # TODO: make a config option
    USE_THREADS = True

    event_handler = None
    running = False

    def enabled(self):
        if not self.running:
            wm = WatchManager()
            self.event_handler = LibraryEvent(library=app.library)

            FLAGS = ['IN_DELETE', 'IN_CLOSE_WRITE', # 'IN_MODIFY',
                     'IN_MOVED_FROM', 'IN_MOVED_TO', 'IN_CREATE']

            masks = [EventsCodes.FLAG_COLLECTIONS['OP_FLAGS'][s]
                     for s in FLAGS]
            mask = reduce(operator.or_, masks, 0)

            if self.USE_THREADS:
                print_d("Using threaded notifier")
                self.notifier = ThreadedNotifier(wm, self.event_handler)
                # Daemonize to ensure thread dies on exit
                self.notifier.daemon = True
                self.notifier.start()
            else:
                self.notifier = Notifier(wm, self.event_handler, timeout=100)
                GLib.timeout_add(1000, self.unthreaded_callback)

            for path in get_scan_dirs():
                real_path = os.path.realpath(path)
                print_d('Watching directory %s for %s (mask: %x)'
                        % (real_path, FLAGS, mask))
                # See https://github.com/seb-m/pyinotify/wiki/
                # Frequently-Asked-Questions
                wm.add_watch(real_path, mask, rec=True, auto_add=True)

            self.running = True

    def unthreaded_callback(self):
        """Processes as much of the inotify events as allowed"""
        assert self.notifier._timeout is not None, \
                'Notifier must be constructed with a [short] timeout'
        self.notifier.process_events()
        # loop in case more events appear while we are processing
        while self.notifier.check_events():
            self.notifier.read_events()
        self.notifier.process_events()
        return True

    # disable hook, stop the notifier:
    def disabled(self):
        if self.running:
            self.running = False
        if self.notifier:
            print_d("Stopping inotify watch...")
            self.notifier.stop()
Esempio n. 39
0
    def __init__(self, parent, plugin_instance):
        GObject.GObject.__init__(self, spacing=12)
        self.plugin_instance = plugin_instance

        # notification text settings
        table = Gtk.Table(n_rows=2, n_columns=3)
        table.set_col_spacings(6)
        table.set_row_spacings(6)

        text_frame = qltk.Frame(_("Notification text"), child=table)

        title_entry = UndoEntry()
        title_entry.set_text(pconfig.gettext("titlepattern"))

        def on_entry_changed(entry, cfgname):
            pconfig.settext(cfgname, gdecode(entry.get_text()))

        title_entry.connect("changed", on_entry_changed, "titlepattern")
        table.attach(title_entry, 1, 2, 0, 1)

        title_label = Gtk.Label(label=_("_Title:"))
        title_label.set_use_underline(True)
        title_label.set_alignment(0, 0.5)
        title_label.set_mnemonic_widget(title_entry)
        table.attach(title_label, 0, 1, 0, 1,
                     xoptions=Gtk.AttachOptions.FILL |
                     Gtk.AttachOptions.SHRINK)

        title_revert = Gtk.Button()
        title_revert.add(Gtk.Image.new_from_icon_name(
            Icons.DOCUMENT_REVERT, Gtk.IconSize.MENU))
        title_revert.set_tooltip_text(_("Revert to default pattern"))
        title_revert.connect(
            "clicked", lambda *x: title_entry.set_text(
                pconfig.defaults.gettext("titlepattern")))
        table.attach(title_revert, 2, 3, 0, 1,
                     xoptions=Gtk.AttachOptions.SHRINK)

        body_textbuffer = TextBuffer()
        body_textview = TextView(buffer=body_textbuffer)
        body_textview.set_size_request(-1, 85)
        body_textview.get_buffer().set_text(pconfig.gettext("bodypattern"))

        def on_textbuffer_changed(text_buffer, cfgname):
            start, end = text_buffer.get_bounds()
            text = gdecode(text_buffer.get_text(start, end, True))
            pconfig.settext(cfgname, text)

        body_textbuffer.connect("changed", on_textbuffer_changed,
                                "bodypattern")
        body_scrollarea = Gtk.ScrolledWindow()
        body_scrollarea.set_policy(Gtk.PolicyType.AUTOMATIC,
                                   Gtk.PolicyType.AUTOMATIC)
        body_scrollarea.set_shadow_type(Gtk.ShadowType.ETCHED_OUT)
        body_scrollarea.add(body_textview)
        table.attach(body_scrollarea, 1, 2, 1, 2)

        body_label = Gtk.Label(label=_("_Body:"))
        body_label.set_padding(0, 3)
        body_label.set_use_underline(True)
        body_label.set_alignment(0, 0)
        body_label.set_mnemonic_widget(body_textview)
        table.attach(body_label, 0, 1, 1, 2, xoptions=Gtk.AttachOptions.SHRINK)

        body_revert = Gtk.Button()
        body_revert.add(Gtk.Image.new_from_icon_name(
                        Icons.DOCUMENT_REVERT, Gtk.IconSize.MENU))
        body_revert.set_tooltip_text(_("Revert to default pattern"))
        body_revert.connect("clicked", lambda *x:
            body_textbuffer.set_text(pconfig.defaults.gettext("bodypattern")))
        table.attach(
            body_revert, 2, 3, 1, 2,
            xoptions=Gtk.AttachOptions.SHRINK,
            yoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK)

        # preview button
        preview_button = qltk.Button(
            _("_Show notification"), Icons.SYSTEM_RUN)
        preview_button.set_sensitive(app.player.info is not None)
        preview_button.connect("clicked", self.on_preview_button_clicked)
        self.qlplayer_connected_signals = [
            app.player.connect("paused", self.on_player_state_changed,
                             preview_button),
            app.player.connect("unpaused", self.on_player_state_changed,
                             preview_button),
        ]

        table.attach(
            preview_button, 0, 3, 2, 3,
            xoptions=Gtk.AttachOptions.FILL | Gtk.AttachOptions.SHRINK)

        self.pack_start(text_frame, True, True, 0)

        # notification display settings
        display_box = Gtk.VBox(spacing=12)
        display_frame = qltk.Frame(_("Show notifications"), child=display_box)

        radio_box = Gtk.VBox(spacing=6)
        display_box.pack_start(radio_box, True, True, 0)

        only_user_radio = Gtk.RadioButton(label=_(
            "Only on <i>_manual</i> song changes"
        ), use_underline=True)
        only_user_radio.get_child().set_use_markup(True)
        only_user_radio.connect("toggled", self.on_radiobutton_toggled,
                                "show_notifications", "user")
        radio_box.pack_start(only_user_radio, True, True, 0)

        only_auto_radio = Gtk.RadioButton(group=only_user_radio, label=_(
            "Only on <i>_automatic</i> song changes"
        ), use_underline=True)
        only_auto_radio.get_child().set_use_markup(True)
        only_auto_radio.connect("toggled", self.on_radiobutton_toggled,
                                "show_notifications", "auto")
        radio_box.pack_start(only_auto_radio, True, True, 0)

        all_radio = Gtk.RadioButton(group=only_user_radio, label=_(
            "On <i>a_ll</i> song changes"
        ), use_underline=True)
        all_radio.get_child().set_use_markup(True)
        all_radio.connect("toggled", self.on_radiobutton_toggled,
                          "show_notifications", "all")
        radio_box.pack_start(all_radio, True, True, 0)

        {
            "user": only_user_radio,
            "auto": only_auto_radio,
            "all": all_radio
        }.get(pconfig.gettext("show_notifications"),
              all_radio).set_active(True)

        focus_check = Gtk.CheckButton(
            label=_("Only when the main window is not _focused"),
            use_underline=True)
        focus_check.set_active(pconfig.getboolean("show_only_when_unfocused"))
        focus_check.connect("toggled", self.on_checkbutton_toggled,
                            "show_only_when_unfocused")
        display_box.pack_start(focus_check, True, True, 0)

        show_next = Gtk.CheckButton(
            label=_("Show \"_Next\" button"),
            use_underline=True)
        show_next.set_active(pconfig.getboolean("show_next_button"))
        show_next.connect("toggled", self.on_checkbutton_toggled,
                            "show_next_button")
        display_box.pack_start(show_next, True, True, 0)

        self.pack_start(display_frame, True, True, 0)

        self.show_all()
        self.connect("destroy", self.on_destroyed)
Esempio n. 40
0
class CustomCommands(PlaylistPlugin, SongsMenuPlugin, PluginConfigMixin):

    PLUGIN_ICON = Icons.APPLICATION_UTILITIES
    PLUGIN_ID = "CustomCommands"
    PLUGIN_NAME = _("Custom Commands")
    PLUGIN_DESC = _("Runs custom commands (in batches if required) on songs "
                    "using any of their tags.")

    # Here are some starters...
    DEFAULT_COMS = [
        Command("Compress files", "file-roller -d"),
        Command("Browse folders (Thunar)",
                "thunar",
                "<~dirname>",
                unique=True,
                max_args=50,
                warn_threshold=20),
        Command(name="Flash notification",
                command="notify-send"
                " -t 2000"
                " -i "
                "/usr/share/icons/hicolor/scalable/apps/"
                "io.github.quodlibet.QuodLibet.svg",
                pattern="<~rating> \"<title><version| (<version>)>\""
                "<~people| by <~people>>"
                "<album|, from <album><discnumber| : disk <discnumber>>"
                "<~length| (<~length>)>",
                max_args=1,
                warn_threshold=10),
        Command(name="Output playlist to stdout",
                command="echo -e",
                pattern="<~playlistname>: <~playlistindex>. "
                " <~artist~title>\\\\n",
                warn_threshold=20),
        Command("Fix MP3 VBR with mp3val",
                "mp3val -f",
                unique=True,
                max_args=1),
    ]
    COMS_FILE = os.path.join(quodlibet.get_user_dir(), 'lists',
                             'customcommands.json')

    _commands = None
    """Commands known to the class"""
    def __set_pat(self, name):
        self.com_index = name

    def get_data(self, key):
        """Gets the pattern for a given key"""
        try:
            return self.all_commands()[key]
        except (KeyError, TypeError):
            print_d("Invalid key %s" % key)
            return None

    @classmethod
    def edit_patterns(cls, button):
        win = JSONBasedEditor(Command,
                              cls.all_commands(),
                              filename=cls.COMS_FILE,
                              title=_("Edit Custom Commands"))
        # Cache busting
        cls._commands = None
        win.show()

    @classmethod
    def PluginPreferences(cls, parent):
        hb = Gtk.HBox(spacing=3)
        hb.set_border_width(0)

        button = qltk.Button(_("Edit Custom Commands") + "…", Icons.EDIT)
        button.set_tooltip_markup(
            _("Supports QL patterns\neg "
              "<tt>&lt;~artist~title&gt;</tt>"))
        button.connect("clicked", cls.edit_patterns)
        hb.pack_start(button, True, True, 0)
        hb.show_all()
        return hb

    @classmethod
    def all_commands(cls):
        if cls._commands is None:
            cls._commands = cls._get_saved_commands()
        return cls._commands

    @classmethod
    def _get_saved_commands(cls):
        filename = cls.COMS_FILE
        print_d("Loading saved commands from '%s'..." % filename)
        coms = None
        try:
            with open(filename, "r", encoding="utf-8") as f:
                coms = JSONObjectDict.from_json(Command, f.read())
        except (IOError, ValueError) as e:
            print_w("Couldn't parse saved commands (%s)" % e)

        # Failing all else...
        if not coms:
            print_d("No commands found in %s. Using defaults." % filename)
            coms = {c.name: c for c in cls.DEFAULT_COMS}
        print_d("Loaded commands: %s" % coms.keys())
        return coms

    def __init__(self, *args, **kwargs):
        super(CustomCommands, self).__init__(**kwargs)
        pl_mode = hasattr(self, '_playlists') and bool(len(self._playlists))
        self.com_index = None
        self.unique_only = False
        submenu = Gtk.Menu()
        for name, c in self.all_commands().items():
            item = Gtk.MenuItem(label=name)
            connect_obj(item, 'activate', self.__set_pat, name)
            if pl_mode and not c.playlists_only:
                continue
            item.set_sensitive(c.playlists_only == pl_mode)
            submenu.append(item)

        self.add_edit_item(submenu)
        if submenu.get_children():
            self.set_submenu(submenu)
        else:
            self.set_sensitive(False)

    @classmethod
    def add_edit_item(cls, submenu):
        config = Gtk.MenuItem(label=_("Edit Custom Commands") + "…")
        connect_obj(config, 'activate', cls.edit_patterns, config)
        config.set_sensitive(not JSONBasedEditor.is_not_unique())
        submenu.append(SeparatorMenuItem())
        submenu.append(config)

    def plugin_songs(self, songs):
        self._handle_songs(songs)

    def plugin_playlist(self, playlist):
        print_d("Running playlist plugin for %s" % playlist)
        return self._handle_songs(playlist.songs, playlist)

    def _handle_songs(self, songs, playlist=None):
        # Check this is a launch, not a configure
        if self.com_index:
            com = self.get_data(self.com_index)
            if len(songs) > com.warn_threshold:
                if not confirm_multi_song_invoke(self, com.name, len(songs)):
                    print_d("User decided not to run on %d songs" % len(songs))
                    return
            print_d("Running %s on %d song(s)" % (com, len(songs)))
            try:
                com.run(songs, playlist and playlist.name)
            except Exception as err:
                print_e("Couldn't run command %s: %s %s at:" % (
                    com.name,
                    type(err),
                    err,
                ))
                print_exc()
                ErrorMessage(
                    self.plugin_window,
                    _("Unable to run custom command %s") %
                    util.escape(self.com_index), util.escape(str(err))).run()
Esempio n. 41
0
class Notify(EventPlugin):
    PLUGIN_ID = "Notify"
    PLUGIN_NAME = _("Song Notifications")
    PLUGIN_DESC = _("Displays a notification when the song changes.")
    PLUGIN_ICON = Icons.DIALOG_INFORMATION

    DBUS_NAME = "org.freedesktop.Notifications"
    DBUS_IFACE = "org.freedesktop.Notifications"
    DBUS_PATH = "/org/freedesktop/Notifications"

    # these can all be used even if it wasn't enabled
    __enabled = False
    __last_id = 0
    __image_fp = None
    __interface = None
    __action_sig = None
    __watch = None

    def enabled(self):
        self.__enabled = True

        # This works because:
        #  - if paused, any on_song_started event will be generated by user
        #    interaction
        #  - if playing, an on_song_ended event will be generated before any
        #    on_song_started event in any case.
        self.__was_stopped_by_user = True

        self.__force_notification = False
        self.__caps = None
        self.__spec_version = None

        self.__enable_watch()

    def disabled(self):
        self.__disable_watch()
        self.__disconnect()
        self.__enabled = False
        self._set_image_fileobj(None)

    def __enable_watch(self):
        """Enable events for dbus name owner change"""
        try:
            bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
            # This also triggers for existing name owners
            self.__watch = bus.watch_name_owner(self.DBUS_NAME,
                                                self.__owner_changed)
        except dbus.DBusException:
            pass

    def __disable_watch(self):
        """Disable name owner change events"""
        if self.__watch:
            self.__watch.cancel()
            self.__watch = None

    def __disconnect(self):
        self.__interface = None
        if self.__action_sig:
            self.__action_sig.remove()
            self.__action_sig = None

    def __owner_changed(self, owner):
        # In case the owner gets removed, remove all references to it
        if not owner:
            self.__disconnect()

    def PluginPreferences(self, parent):
        return PreferencesWidget(parent, self)

    def __get_interface(self):
        """Returns a fresh proxy + info about the server"""

        obj = dbus.SessionBus().get_object(self.DBUS_NAME, self.DBUS_PATH)
        interface = dbus.Interface(obj, self.DBUS_IFACE)

        name, vendor, version, spec_version = \
            map(str, interface.GetServerInformation())
        spec_version = map(int, spec_version.split("."))
        caps = map(str, interface.GetCapabilities())

        return interface, caps, spec_version

    def close_notification(self):
        """Closes the last opened notification"""

        if not self.__last_id:
            return

        try:
            obj = dbus.SessionBus().get_object(self.DBUS_NAME, self.DBUS_PATH)
            interface = dbus.Interface(obj, self.DBUS_IFACE)
            interface.CloseNotification(self.__last_id)
        except dbus.DBusException:
            pass
        else:
            self.__last_id = 0

    def _set_image_fileobj(self, fileobj):
        if self.__image_fp is not None:
            self.__image_fp.close()
            self.__image_fp = None
        self.__image_fp = fileobj

    def _get_image_uri(self, song):
        """A unicode file URI or an empty string"""

        fileobj = app.cover_manager.get_cover(song)
        self._set_image_fileobj(fileobj)
        if fileobj:
            return fsn2uri(fileobj.name)
        return u""

    def show_notification(self, song):
        """Returns True if showing the notification was successful"""

        if not song:
            return True

        try:
            if self.__enabled:
                # we are enabled try to work with the data we have and
                # keep it fresh
                if not self.__interface:
                    iface, caps, spec = self.__get_interface()
                    self.__interface = iface
                    self.__caps = caps
                    self.__spec_version = spec
                    if "actions" in caps:
                        self.__action_sig = iface.connect_to_signal(
                            "ActionInvoked", self.on_dbus_action)
                else:
                    iface = self.__interface
                    caps = self.__caps
                    spec = self.__spec_version
            else:
                # not enabled, just get everything temporary,
                # propably preview
                iface, caps, spec = self.__get_interface()

        except dbus.DBusException:
            print_w("[notify] %s" %
                    _("Couldn't connect to notification daemon."))
            self.__disconnect()
            return False

        strip_markup = lambda t: re.subn("\</?[iub]\>", "", t)[0]
        strip_links = lambda t: re.subn("\</?a.*?\>", "", t)[0]
        strip_images = lambda t: re.subn("\<img.*?\>", "", t)[0]

        title = XMLFromPattern(pconfig.gettext("titlepattern")) % song
        title = unescape(strip_markup(strip_links(strip_images(title))))

        body = ""
        if "body" in caps:
            body = XMLFromPattern(pconfig.gettext("bodypattern")) % song

            if "body-markup" not in caps:
                body = strip_markup(body)
            if "body-hyperlinks" not in caps:
                body = strip_links(body)
            if "body-images" not in caps:
                body = strip_images(body)

        actions = []
        if pconfig.getboolean("show_next_button") and "actions" in caps:
            actions = ["next", _("Next")]

        hints = {
            "desktop-entry": "quodlibet",
        }

        image_uri = self._get_image_uri(song)
        if image_uri:
            hints["image_path"] = image_uri
            hints["image-path"] = image_uri

        try:
            self.__last_id = iface.Notify(
                "Quod Libet", self.__last_id,
                image_uri, title, body, actions, hints,
                pconfig.getint("timeout"))
        except dbus.DBusException:
            print_w("[notify] %s" %
                    _("Couldn't connect to notification daemon."))
            self.__disconnect()
            return False

        # preview done, remove all references again
        if not self.__enabled:
            self.__disconnect()

        return True

    def on_dbus_action(self, notify_id, key):
        if notify_id == self.__last_id and key == "next":
            # Always show a new notification if the next button got clicked
            self.__force_notification = True
            app.player.next()

    def on_song_change(self, song, typ):
        if not song:
            self.close_notification()
        if pconfig.gettext("show_notifications") in [typ, "all"] \
                and not (pconfig.getboolean("show_only_when_unfocused")
                         and app.window.has_toplevel_focus()) \
                or self.__force_notification:
            def idle_show(song):
                self.show_notification(song)
            GLib.idle_add(idle_show, song)
            self.__force_notification = False

    def plugin_on_song_started(self, song):
        typ = (self.__was_stopped_by_user and "user") or "auto"
        self.on_song_change(song, typ)

    def plugin_on_song_ended(self, song, stopped):
        # if `stopped` is `True`, this song was ended due to some kind of user
        # interaction.
        self.__was_stopped_by_user = stopped
Esempio n. 42
0
class SessionInhibit(EventPlugin):
    PLUGIN_ID = "screensaver_inhibit"
    PLUGIN_NAME = _("Inhibit Screensaver/Suspend")
    PLUGIN_DESC = _("On a GNOME desktop, when a song is playing, prevents"
                    " either the screensaver from activating, or prevents the"
                    " computer from suspending.")
    PLUGIN_ICON = Icons.PREFERENCES_DESKTOP_SCREENSAVER

    CONFIG_MODE = PLUGIN_ID + "_mode"

    DBUS_NAME = "org.gnome.SessionManager"
    DBUS_INTERFACE = "org.gnome.SessionManager"
    DBUS_PATH = "/org/gnome/SessionManager"

    APPLICATION_ID = "quodlibet"
    INHIBIT_REASON = _("Music is playing")

    __cookie = None

    def __get_dbus_proxy(self):
        bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
        return Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None,
                                      self.DBUS_NAME,
                                      self.DBUS_PATH,
                                      self.DBUS_INTERFACE,
                                      None)

    def enabled(self):
        if not app.player.paused:
            self.plugin_on_unpaused()

    def disabled(self):
        if not app.player.paused:
            self.plugin_on_paused()

    def plugin_on_unpaused(self):
        xid = get_toplevel_xid()
        mode = config.get("plugins", self.CONFIG_MODE, InhibitStrings.IDLE)
        flags = InhibitFlags.SUSPEND if mode == InhibitStrings.SUSPEND \
                                     else InhibitFlags.IDLE

        try:
            dbus_proxy = self.__get_dbus_proxy()
            self.__cookie = dbus_proxy.Inhibit('(susu)',
                                               self.APPLICATION_ID, xid,
                                               self.INHIBIT_REASON, flags)
        except GLib.Error:
            pass

    def plugin_on_paused(self):
        if self.__cookie is None:
            return

        try:
            dbus_proxy = self.__get_dbus_proxy()
            dbus_proxy.Uninhibit('(u)', self.__cookie)
            self.__cookie = None
        except GLib.Error:
            pass

    def PluginPreferences(self, parent):
        def changed(combo):
            index = combo.get_active()
            mode = InhibitStrings.SUSPEND if index == 1 \
                                          else InhibitStrings.IDLE
            config.set("plugins", self.CONFIG_MODE, mode)
            if not app.player.paused:
                self.plugin_on_paused()
                self.plugin_on_unpaused()

        mode = config.get("plugins", self.CONFIG_MODE, InhibitStrings.IDLE)

        hb = Gtk.HBox(spacing=6)
        hb.set_border_width(6)
        # Translators: Inhibiting Mode
        hb.pack_start(Gtk.Label(label=_("Mode:")), False, True, 0)
        combo = Gtk.ComboBoxText()
        combo.append_text(_("Inhibit Screensaver"))
        combo.append_text(_("Inhibit Suspend"))
        combo.set_active(1 if mode == InhibitStrings.SUSPEND else 0)
        combo.connect('changed', changed)
        hb.pack_start(combo, True, True, 0)
        return hb
Esempio n. 43
0
class Import(SongsMenuPlugin):
    PLUGIN_ID = "ImportMeta"
    PLUGIN_NAME = _("Import Metadata")
    PLUGIN_DESC = _("Imports metadata for selected songs from a .tags file.")
    PLUGIN_ICON = Icons.DOCUMENT_OPEN
    REQUIRES_ACTION = True

    plugin_handles = each_song(is_writable, is_a_file)

    # Note: the usage of plugin_album here is sometimes NOT what you want. It
    # supports fixing up tags on several already-known albums just by walking
    # them via the plugin system and just selecting a new .tags; this mimics
    # export of several albums.
    #
    # However if one of the songs in your album is different from the rest
    # (e.g.
    # one isn't tagged, or only one is) it will be passed in as two different
    # invocations, neither of which has the right size. If you find yourself in
    # that scenario a lot more than the previous one, change this to
    #   def plugin_songs(self, songs):
    # and comment out the songs.sort line for safety.
    def plugin_album(self, songs):

        songs.sort(key=sort_key_for)

        chooser = filechooser(save=False, title=songs[0]('album'))
        box = Gtk.HBox()
        rename = Gtk.CheckButton("Rename Files")
        rename.set_active(False)
        box.pack_start(rename, True, True, 0)
        append = Gtk.CheckButton("Append Metadata")
        append.set_active(True)
        box.pack_start(append, True, True, 0)
        box.show_all()
        chooser.set_extra_widget(box)

        resp = chooser.run()
        append = append.get_active()
        rename = rename.get_active()
        fn = chooser.get_filename()
        chooser.destroy()
        if resp != Gtk.ResponseType.ACCEPT:
            return

        global lastfolder
        lastfolder = dirname(fn)

        metadata = []
        names = []
        index = 0
        for line in open(fn, 'r', encoding="utf-8"):
            if index == len(metadata):
                names.append(line[:line.rfind('.')])
                metadata.append({})
            elif line == '\n':
                index = len(metadata)
            else:
                key, value = line[:-1].split('=', 1)
                try:
                    metadata[index][key].append(value)
                except KeyError:
                    metadata[index][key] = [value]

        if not (len(songs) == len(metadata) == len(names)):
            ErrorMessage(
                None, "Songs mismatch",
                "There are %(select)d songs selected, but %(meta)d "
                "songs in the file. Aborting." %
                dict(select=len(songs), meta=len(metadata))).run()
            return

        self.update_files(songs, metadata, names, append=append, rename=rename)

    def update_files(self, songs, metadata, names, append=True, rename=False):
        for song, meta, name in zip(songs, metadata, names):
            for key, values in meta.items():
                if append and key in song:
                    values = song.list(key) + values
                song[key] = '\n'.join(values)
            if rename:
                path = song('~dirname')
                base = os.path.basename(name)
                newname = os.path.join(path, base)
                try:
                    app.library.rename(song._song, newname)
                except ValueError:
                    print_e("File {} already exists. Ignoring file "
                            "rename.".format(newname))
        app.library.changed(songs)
Esempio n. 44
0
    def show_notification(self, song):
        """Returns True if showing the notification was successful"""

        if not song:
            return True

        try:
            if self.__enabled:
                # we are enabled try to work with the data we have and
                # keep it fresh
                if not self.__interface:
                    iface, caps, spec = self.__get_interface()
                    self.__interface = iface
                    self.__caps = caps
                    self.__spec_version = spec
                    if "actions" in caps:
                        self.__action_sig = iface.connect_to_signal(
                            "ActionInvoked", self.on_dbus_action)
                else:
                    iface = self.__interface
                    caps = self.__caps
                    spec = self.__spec_version
            else:
                # not enabled, just get everything temporary,
                # propably preview
                iface, caps, spec = self.__get_interface()

        except dbus.DBusException:
            print_w("[notify] %s" %
                    _("Couldn't connect to notification daemon."))
            self.__disconnect()
            return False

        strip_markup = lambda t: re.subn("\</?[iub]\>", "", t)[0]
        strip_links = lambda t: re.subn("\</?a.*?\>", "", t)[0]
        strip_images = lambda t: re.subn("\<img.*?\>", "", t)[0]

        title = XMLFromPattern(pconfig.gettext("titlepattern")) % song
        title = unescape(strip_markup(strip_links(strip_images(title))))

        body = ""
        if "body" in caps:
            body = XMLFromPattern(pconfig.gettext("bodypattern")) % song

            if "body-markup" not in caps:
                body = strip_markup(body)
            if "body-hyperlinks" not in caps:
                body = strip_links(body)
            if "body-images" not in caps:
                body = strip_images(body)

        actions = []
        if pconfig.getboolean("show_next_button") and "actions" in caps:
            actions = ["next", _("Next")]

        hints = {
            "desktop-entry": "quodlibet",
        }

        image_uri = self._get_image_uri(song)
        if image_uri:
            hints["image_path"] = image_uri
            hints["image-path"] = image_uri

        try:
            self.__last_id = iface.Notify(
                "Quod Libet", self.__last_id,
                image_uri, title, body, actions, hints,
                pconfig.getint("timeout"))
        except dbus.DBusException:
            print_w("[notify] %s" %
                    _("Couldn't connect to notification daemon."))
            self.__disconnect()
            return False

        # preview done, remove all references again
        if not self.__enabled:
            self.__disconnect()

        return True
Esempio n. 45
0
 def ftime(t):
     if t == 0:
         return _("Unknown")
     else:
         return str(time.strftime("%c", time.localtime(t)))
Esempio n. 46
0
 def on_preview_button_clicked(self, button):
     if app.player.info is not None:
         if not self.plugin_instance.show_notification(app.player.info):
             ErrorMessage(self, _("Connection Error"),
                 _("Couldn't connect to notification daemon.")).run()
Esempio n. 47
0
 def __init__(self):
     super(NoSongs, self).__init__(label=_("No songs are selected."))
     self.title = _("No Songs")
Esempio n. 48
0
class AlbumList(Browser, util.InstanceTracker, VisibleUpdate,
                DisplayPatternMixin):
    __model = None
    __last_render = None
    __last_render_surface = None

    _PATTERN_FN = os.path.join(quodlibet.get_user_dir(), "album_pattern")
    _DEFAULT_PATTERN_TEXT = DEFAULT_PATTERN_TEXT
    STAR = ["~people", "album"]

    name = _("Album List")
    accelerated_name = _("_Album List")
    keys = ["AlbumList"]
    priority = 4

    def pack(self, songpane):
        container = qltk.ConfigRHPaned("browsers", "albumlist_pos", 0.4)
        container.pack1(self, True, False)
        container.pack2(songpane, True, False)
        return container

    def unpack(self, container, songpane):
        container.remove(songpane)
        container.remove(self)

    @classmethod
    def init(klass, library):
        super(AlbumList, klass).load_pattern()

    @classmethod
    def _destroy_model(klass):
        klass.__model.destroy()
        klass.__model = None

    @classmethod
    def toggle_covers(klass):
        on = config.getboolean("browsers", "album_covers")
        for albumlist in klass.instances():
            albumlist.__cover_column.set_visible(on)
            for column in albumlist.view.get_columns():
                column.queue_resize()

    @classmethod
    def refresh_all(cls):
        cls.__model.refresh_all()

    @classmethod
    def _init_model(klass, library):
        klass.__model = AlbumModel(library)
        klass.__library = library

    @util.cached_property
    def _no_cover(self):
        """Returns a cairo surface representing a missing cover"""

        cover_size = get_cover_size()
        scale_factor = self.get_scale_factor()
        pb = get_no_cover_pixbuf(cover_size, cover_size, scale_factor)
        return get_surface_for_pixbuf(self, pb)

    def __init__(self, library):
        super(AlbumList, self).__init__(spacing=6)
        self.set_orientation(Gtk.Orientation.VERTICAL)

        self._register_instance()
        if self.__model is None:
            self._init_model(library)

        self._cover_cancel = Gio.Cancellable()

        sw = ScrolledWindow()
        sw.set_shadow_type(Gtk.ShadowType.IN)
        self.view = view = AllTreeView()
        view.set_headers_visible(False)
        model_sort = AlbumSortModel(model=self.__model)
        model_filter = AlbumFilterModel(child_model=model_sort)

        self.__bg_filter = background_filter()
        self.__filter = None
        model_filter.set_visible_func(self.__parse_query)

        render = Gtk.CellRendererPixbuf()
        self.__cover_column = column = Gtk.TreeViewColumn("covers", render)
        column.set_visible(config.getboolean("browsers", "album_covers"))
        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        column.set_fixed_width(get_cover_size() + 12)
        render.set_property('height', get_cover_size() + 8)
        render.set_property('width', get_cover_size() + 8)

        def cell_data_pb(column, cell, model, iter_, no_cover):
            item = model.get_value(iter_)

            if item.album is None:
                surface = None
            elif item.cover:
                pixbuf = item.cover
                pixbuf = add_border_widget(pixbuf, self.view)
                surface = get_surface_for_pixbuf(self, pixbuf)
                # don't cache, too much state has an effect on the result
                self.__last_render_surface = None
            else:
                surface = no_cover

            if self.__last_render_surface == surface:
                return
            self.__last_render_surface = surface
            cell.set_property("surface", surface)

        column.set_cell_data_func(render, cell_data_pb, self._no_cover)
        view.append_column(column)

        render = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn("albums", render)
        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        if view.supports_hints():
            render.set_property('ellipsize', Pango.EllipsizeMode.END)

        def cell_data(column, cell, model, iter_, data):
            album = model.get_album(iter_)

            if album is None:
                text = "<b>%s</b>\n" % _("All Albums")
                text += numeric_phrase("%d album", "%d albums", len(model) - 1)
                markup = text
            else:
                markup = self.display_pattern % album

            if self.__last_render == markup:
                return
            self.__last_render = markup
            cell.markup = markup
            cell.set_property('markup', markup)

        column.set_cell_data_func(render, cell_data)
        view.append_column(column)

        view.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
        view.set_rules_hint(True)
        view.set_search_equal_func(self.__search_func, None)
        view.set_search_column(0)
        view.set_model(model_filter)
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        sw.add(view)

        view.connect('row-activated', self.__play_selection)
        self.__sig = view.connect('selection-changed',
            util.DeferredSignal(self.__update_songs, owner=view))

        targets = [("text/x-quodlibet-songs", Gtk.TargetFlags.SAME_APP, 1),
                   ("text/uri-list", 0, 2)]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        view.drag_source_set(
            Gdk.ModifierType.BUTTON1_MASK, targets, Gdk.DragAction.COPY)
        view.connect("drag-data-get", self.__drag_data_get)
        connect_obj(view, 'popup-menu', self.__popup, view, library)

        self.accelerators = Gtk.AccelGroup()
        search = SearchBarBox(completion=AlbumTagCompletion(),
                              accel_group=self.accelerators,
                              star=self.STAR)
        search.connect('query-changed', self.__update_filter)
        connect_obj(search, 'focus-out', lambda w: w.grab_focus(), view)
        self.__search = search

        prefs = PreferencesButton(self, model_sort)
        search.pack_start(prefs, False, True, 0)
        self.pack_start(Align(search, left=6, top=6), False, True, 0)
        self.pack_start(sw, True, True, 0)

        self.connect("destroy", self.__destroy)

        self.enable_row_update(view, sw, self.__cover_column)

        self.connect('key-press-event', self.__key_pressed, library.librarian)

        if app.cover_manager:
            connect_destroy(
                app.cover_manager, "cover-changed", self._cover_changed)

        self.show_all()

    def _cover_changed(self, manager, songs):
        model = self.__model
        songs = set(songs)
        for iter_, item in model.iterrows():
            album = item.album
            if album is not None and songs & album.songs:
                item.scanned = False
                model.row_changed(model.get_path(iter_), iter_)

    def __key_pressed(self, widget, event, librarian):
        if qltk.is_accel(event, "<Primary>I"):
            songs = self.__get_selected_songs()
            if songs:
                window = Information(librarian, songs, self)
                window.show()
            return True
        elif qltk.is_accel(event, "<alt>Return"):
            songs = self.__get_selected_songs()
            if songs:
                window = SongProperties(librarian, songs, self)
                window.show()
            return True
        return False

    def _row_needs_update(self, model, iter_):
        item = model.get_value(iter_)
        return item.album is not None and not item.scanned

    def _update_row(self, filter_model, iter_):
        sort_model = filter_model.get_model()
        model = sort_model.get_model()
        iter_ = filter_model.convert_iter_to_child_iter(iter_)
        iter_ = sort_model.convert_iter_to_child_iter(iter_)
        tref = Gtk.TreeRowReference.new(model, model.get_path(iter_))

        def callback():
            path = tref.get_path()
            if path is not None:
                model.row_changed(path, model.get_iter(path))

        item = model.get_value(iter_)
        scale_factor = self.get_scale_factor()
        item.scan_cover(scale_factor=scale_factor,
                        callback=callback,
                        cancel=self._cover_cancel)

    def __destroy(self, browser):
        self._cover_cancel.cancel()
        self.disable_row_update()

        self.view.set_model(None)

        klass = type(browser)
        if not klass.instances():
            klass._destroy_model()

    def __update_filter(self, entry, text, scroll_up=True, restore=False):
        model = self.view.get_model()

        self.__filter = None
        query = self.__search.query
        if not query.matches_all:
            self.__filter = query.search
        self.__bg_filter = background_filter()

        self.__inhibit()

        # We could be smart and try to scroll to a selected album
        # but that introduces lots of wild scrolling. Feel free to change it.
        # Without scrolling the TV tries to stay at the same position
        # (40% down) which makes no sense, so always go to the top.
        if scroll_up:
            self.view.scroll_to_point(0, 0)

        # Don't filter on restore if there is nothing to filter
        if not restore or self.__filter or self.__bg_filter:
            model.refilter()

        self.__uninhibit()

    def __parse_query(self, model, iter_, data):
        f, b = self.__filter, self.__bg_filter

        if f is None and b is None:
            return True
        else:
            album = model.get_album(iter_)
            if album is None:
                return True
            elif b is None:
                return f(album)
            elif f is None:
                return b(album)
            else:
                return b(album) and f(album)

    def __search_func(self, model, column, key, iter_, data):
        album = model.get_album(iter_)
        if album is None:
            return True
        key = gdecode(key).lower()
        title = album.title.lower()
        if key in title:
            return False
        if config.getboolean("browsers", "album_substrings"):
            people = (p.lower() for p in album.list("~people"))
            for person in people:
                if key in person:
                    return False
        return True

    def __popup(self, view, library):
        albums = self.__get_selected_albums()
        songs = self.__get_songs_from_albums(albums)

        items = []
        if self.__cover_column.get_visible():
            num = len(albums)
            button = MenuItem(
                ngettext("Reload album _cover", "Reload album _covers", num),
                Icons.VIEW_REFRESH)
            button.connect('activate', self.__refresh_album, view)
            items.append(button)

        menu = SongsMenu(library, songs, items=[items])
        menu.show_all()
        return view.popup_menu(menu, 0, Gtk.get_current_event_time())

    def __refresh_album(self, menuitem, view):
        items = self.__get_selected_items()
        for item in items:
            item.scanned = False
        model = self.view.get_model()
        for iter_, item in model.iterrows():
            if item in items:
                model.row_changed(model.get_path(iter_), iter_)

    def __get_selected_items(self):
        selection = self.view.get_selection()
        model, paths = selection.get_selected_rows()
        return model.get_items(paths)

    def __get_selected_albums(self):
        selection = self.view.get_selection()
        model, paths = selection.get_selected_rows()
        return model.get_albums(paths)

    def __get_songs_from_albums(self, albums, sort=True):
        # Sort first by how the albums appear in the model itself,
        # then within the album using the default order.
        songs = []
        if sort:
            for album in albums:
                songs.extend(sorted(album.songs, key=lambda s: s.sort_key))
        else:
            for album in albums:
                songs.extend(album.songs)
        return songs

    def __get_selected_songs(self, sort=True):
        albums = self.__get_selected_albums()
        return self.__get_songs_from_albums(albums, sort)

    def __drag_data_get(self, view, ctx, sel, tid, etime):
        songs = self.__get_selected_songs()
        if tid == 1:
            qltk.selection_set_songs(sel, songs)
        else:
            sel.set_uris([song("~uri") for song in songs])

    def __play_selection(self, view, indices, col):
        self.songs_activated()

    def active_filter(self, song):
        for album in self.__get_selected_albums():
            if song in album.songs:
                return True
        return False

    def can_filter_text(self):
        return True

    def filter_text(self, text):
        self.__search.set_text(text)
        if Query(text).is_parsable:
            self.__update_filter(self.__search, text)
            self.__inhibit()
            self.view.set_cursor((0,))
            self.__uninhibit()
            self.activate()

    def get_filter_text(self):
        return self.__search.get_text()

    def can_filter(self, key):
        # Numerics are different for collections, and although title works,
        # it's not of much use here.
        if key is not None and (key.startswith("~#") or key == "title"):
            return False
        return super(AlbumList, self).can_filter(key)

    def can_filter_albums(self):
        return True

    def list_albums(self):
        model = self.view.get_model()
        return [row[0].album.key for row in model if row[0].album]

    def filter_albums(self, values):
        view = self.view
        self.__inhibit()
        changed = view.select_by_func(
            lambda r: r[0].album and r[0].album.key in values)
        self.__uninhibit()
        if changed:
            self.activate()

    def unfilter(self):
        self.filter_text("")
        self.view.set_cursor((0,))

    def activate(self):
        self.view.get_selection().emit('changed')

    def __inhibit(self):
        self.view.handler_block(self.__sig)

    def __uninhibit(self):
        self.view.handler_unblock(self.__sig)

    def restore(self):
        text = config.gettext("browsers", "query_text")
        entry = self.__search
        entry.set_text(text)

        # update_filter expects a parsable query
        if Query(text).is_parsable:
            self.__update_filter(entry, text, scroll_up=False, restore=True)

        keys = config.gettext("browsers", "albums").split("\n")

        # FIXME: If albums is "" then it could be either all albums or
        # no albums. If it's "" and some other stuff, assume no albums,
        # otherwise all albums.
        self.__inhibit()
        if keys == [""]:
            self.view.set_cursor((0,))
        else:

            def select_fun(row):
                album = row[0].album
                if not album:  # all
                    return False
                return album.str_key in keys
            self.view.select_by_func(select_fun)
        self.__uninhibit()

    def scroll(self, song):
        album_key = song.album_key
        select = lambda r: r[0].album and r[0].album.key == album_key
        self.view.select_by_func(select, one=True)

    def __get_config_string(self):
        selection = self.view.get_selection()
        model, paths = selection.get_selected_rows()

        # All is selected
        if model.contains_all(paths):
            return ""

        # All selected albums
        albums = model.get_albums(paths)

        confval = "\n".join((a.str_key for a in albums))
        # ConfigParser strips a trailing \n so we move it to the front
        if confval and confval[-1] == "\n":
            confval = "\n" + confval[:-1]
        return confval

    def save(self):
        conf = self.__get_config_string()
        config.settext("browsers", "albums", conf)
        text = self.__search.get_text()
        config.settext("browsers", "query_text", text)

    def __update_songs(self, view, selection):
        songs = self.__get_selected_songs(sort=False)
        self.songs_selected(songs)
Esempio n. 49
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()
Esempio n. 50
0
 def counter(i):
     return _("Never") if i == 0 \
         else numeric_phrase("%(n)d time", "%(n)d times", i, "n")
Esempio n. 51
0
    def PluginPreferences(self, parent):
        red = Gdk.RGBA()
        red.parse("#ff0000")

        def validate_color(entry):
            text = entry.get_text()

            if not Gdk.RGBA().parse(text):
                # Invalid color, make text red
                entry.override_color(Gtk.StateFlags.NORMAL, red)
            else:
                # Reset text color
                entry.override_color(Gtk.StateFlags.NORMAL, None)

        def elapsed_color_changed(entry):
            validate_color(entry)

            CONFIG.elapsed_color = entry.get_text()

        def hover_color_changed(entry):
            validate_color(entry)

            CONFIG.hover_color = entry.get_text()

        def remaining_color_changed(entry):
            validate_color(entry)

            CONFIG.remaining_color = entry.get_text()

        def on_show_pos_toggled(button, *args):
            CONFIG.show_current_pos = button.get_active()

        def seek_amount_changed(spinbox):
            CONFIG.seek_amount = spinbox.get_value_as_int()

        vbox = Gtk.VBox(spacing=6)

        def on_show_time_labels_toggled(button, *args):
            CONFIG.show_time_labels = button.get_active()
            if self._bar is not None:
                self._bar.set_time_label_visibility(CONFIG.show_time_labels)

        def create_color(label_text, color, callback):
            hbox = Gtk.HBox(spacing=6)
            hbox.set_border_width(6)
            label = Gtk.Label(label=label_text)
            hbox.pack_start(label, False, True, 0)
            entry = Gtk.Entry()
            if color:
                entry.set_text(color)
            entry.connect('changed', callback)
            hbox.pack_start(entry, True, True, 0)
            return hbox

        box = create_color(_("Override foreground color:"),
                           CONFIG.elapsed_color, elapsed_color_changed)
        vbox.pack_start(box, True, True, 0)

        box = create_color(_("Override hover color:"), CONFIG.hover_color,
                           hover_color_changed)
        vbox.pack_start(box, True, True, 0)

        box = create_color(_("Override remaining color:"),
                           CONFIG.remaining_color, remaining_color_changed)
        vbox.pack_start(box, True, True, 0)

        show_current_pos = Gtk.CheckButton(label=_("Show current position"))
        show_current_pos.set_active(CONFIG.show_current_pos)
        show_current_pos.connect("toggled", on_show_pos_toggled)
        vbox.pack_start(show_current_pos, True, True, 0)

        show_time_labels = Gtk.CheckButton(label=_("Show time labels"))
        show_time_labels.set_active(CONFIG.show_time_labels)
        show_time_labels.connect("toggled", on_show_time_labels_toggled)
        vbox.pack_start(show_time_labels, True, True, 0)

        hbox = Gtk.HBox(spacing=6)
        hbox.set_border_width(6)
        label = Gtk.Label(label=_(
            "Seek amount when scrolling (milliseconds):"
        ))
        hbox.pack_start(label, False, True, 0)
        seek_amount = Gtk.SpinButton(
            adjustment=Gtk.Adjustment(CONFIG.seek_amount,
                                      0, 60000, 1000, 1000, 0)
        )
        seek_amount.set_numeric(True)
        seek_amount.connect("changed", seek_amount_changed)
        hbox.pack_start(seek_amount, True, True, 0)
        vbox.pack_start(hbox, True, True, 0)

        return vbox
Esempio n. 52
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()
Esempio n. 53
0
    def __init__(self, library):
        super(MaskedBox, self).__init__(spacing=6)

        self.model = model = Gtk.ListStore(object)
        view = RCMHintedTreeView(model=model)
        view.set_fixed_height_mode(True)
        view.set_headers_visible(False)
        self.view = view

        menu = Gtk.Menu()
        unhide_item = qltk.MenuItem(_("Unhide"), Icons.LIST_ADD)
        connect_obj(unhide_item, 'activate', self.__unhide, view, library)
        menu.append(unhide_item)

        remove_item = qltk.MenuItem(_("_Remove"), Icons.LIST_REMOVE)
        connect_obj(remove_item, 'activate', self.__remove, view, library)
        menu.append(remove_item)

        menu.show_all()
        view.connect('popup-menu', self.__popup, menu)

        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))

        def cdf(column, cell, model, iter, data):
            row = model[iter]
            cell.set_property('text', fsn2text(row[0]))

        def cdf_count(column, cell, model, iter, data):
            mount = model[iter][0]
            song_count = len(library.get_masked(mount))
            text = ngettext("%d song", "%d songs", song_count) % song_count
            cell.set_property('text', text)

        column = Gtk.TreeViewColumn(None)
        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)

        render = Gtk.CellRendererText()
        render.set_property('ellipsize', Pango.EllipsizeMode.END)
        column.pack_start(render, True)
        column.set_cell_data_func(render, cdf)

        render = Gtk.CellRendererText()
        render.props.sensitive = False
        column.pack_start(render, False)
        column.set_cell_data_func(render, cdf_count)

        view.append_column(column)

        unhide = qltk.Button(_("_Unhide"), Icons.LIST_ADD)
        connect_obj(unhide, "clicked", self.__unhide, view, library)
        remove = qltk.Button(_("_Remove"), Icons.LIST_REMOVE)

        selection = view.get_selection()
        selection.set_mode(Gtk.SelectionMode.MULTIPLE)
        selection.connect("changed", self.__select_changed, remove, unhide)
        selection.emit("changed")

        connect_obj(remove, "clicked", self.__remove, view, library)

        vbox = Gtk.VBox(spacing=6)
        vbox.pack_start(unhide, False, True, 0)
        vbox.pack_start(remove, False, True, 0)

        self.pack_start(sw, True, True, 0)
        self.pack_start(vbox, False, True, 0)

        for path in library.masked_mount_points:
            model.append(row=[path])

        if not len(model):
            self.set_sensitive(False)

        for child in self.get_children():
            child.show_all()
Esempio n. 54
0
 def get_field_name(field, key):
     field_name = (field.human_name or (key and key.replace("_", " ")))
     return field_name and util.capitalize(field_name) or _("(unknown)")
Esempio n. 55
0
class ReplayGain(SongsMenuPlugin, PluginConfigMixin):
    PLUGIN_ID = 'ReplayGain'
    PLUGIN_NAME = _('Replay Gain')
    PLUGIN_DESC = _('Analyzes and updates ReplayGain information, '
                    'using GStreamer. Results are grouped by album.')
    PLUGIN_ICON = Icons.MULTIMEDIA_VOLUME_CONTROL
    CONFIG_SECTION = 'replaygain'

    plugin_handles = each_song(is_finite, is_writable)

    def plugin_albums(self, albums):
        mode = self.config_get("process_if", UpdateMode.ALWAYS)
        win = RGDialog(albums, parent=self.plugin_window, process_mode=mode)
        win.show_all()
        win.start_analysis()

        # plugin_done checks for metadata changes and opens the write dialog
        win.connect("destroy", self.__plugin_done)

    def __plugin_done(self, win):
        self.plugin_finish()

    @classmethod
    def PluginPreferences(cls, parent):
        vb = Gtk.VBox(spacing=12)

        # Tabulate all settings for neatness
        table = Gtk.Table(n_rows=1, n_columns=2)
        table.props.expand = False
        table.set_col_spacings(6)
        table.set_row_spacings(6)
        rows = []

        def process_option_changed(combo):
            #xcode = combo.get_child().get_text()
            model = combo.get_model()
            lbl, value = model[combo.get_active()]
            cls.config_set("process_if", value)

        def create_model():
            model = Gtk.ListStore(str, str)
            model.append(["<b>%s</b>" % _("always"), UpdateMode.ALWAYS])
            model.append([
                _("if <b>any</b> RG tags are missing"), UpdateMode.ANY_MISSING
            ])
            model.append([
                _("if <b>album</b> RG tags are missing"),
                UpdateMode.ALBUM_MISSING
            ])
            return model

        def set_active(value):
            for i, item in enumerate(model):
                if value == item[1]:
                    combo.set_active(i)

        model = create_model()
        combo = Gtk.ComboBox(model=model)
        set_active(cls.config_get("process_if", UpdateMode.ALWAYS))
        renderer = Gtk.CellRendererText()
        combo.connect('changed', process_option_changed)
        combo.pack_start(renderer, True)
        combo.add_attribute(renderer, "markup", 0)

        rows.append((_("_Process albums:"), combo))

        for (row, (label_text, entry)) in enumerate(rows):
            label = Gtk.Label(label=label_text)
            label.set_alignment(0.0, 0.5)
            label.set_use_underline(True)
            label.set_mnemonic_widget(entry)
            table.attach(label,
                         0,
                         1,
                         row,
                         row + 1,
                         xoptions=Gtk.AttachOptions.FILL)
            table.attach(entry, 1, 2, row, row + 1)

        # Server settings Frame
        frame = Frame(_("Existing Tags"), table)

        vb.pack_start(frame, True, True, 0)
        return vb
Esempio n. 56
0
class WaveformSeekBarPlugin(EventPlugin):
    """The plugin class."""

    PLUGIN_ID = "WaveformSeekBar"
    PLUGIN_NAME = _("Waveform Seek Bar")
    PLUGIN_ICON = Icons.GO_JUMP
    PLUGIN_CONFIG_SECTION = __name__
    PLUGIN_DESC = _(
        "A seekbar in the shape of the waveform of the current song.")

    def __init__(self):
        self._bar = None

    def enabled(self):
        self._bar = WaveformSeekBar(app.player, app.librarian)
        self._bar.show()
        app.window.set_seekbar_widget(self._bar)

    def disabled(self):
        app.window.set_seekbar_widget(None)
        self._bar.destroy()
        self._bar = None

    def PluginPreferences(self, parent):
        red = Gdk.RGBA()
        red.parse("#ff0000")

        def validate_color(entry):
            text = entry.get_text()

            if not Gdk.RGBA().parse(text):
                # Invalid color, make text red
                entry.override_color(Gtk.StateFlags.NORMAL, red)
            else:
                # Reset text color
                entry.override_color(Gtk.StateFlags.NORMAL, None)

        def elapsed_color_changed(entry):
            validate_color(entry)

            CONFIG.elapsed_color = entry.get_text()

        def hover_color_changed(entry):
            validate_color(entry)

            CONFIG.hover_color = entry.get_text()

        def remaining_color_changed(entry):
            validate_color(entry)

            CONFIG.remaining_color = entry.get_text()

        def on_show_pos_toggled(button, *args):
            CONFIG.show_current_pos = button.get_active()

        def seek_amount_changed(spinbox):
            CONFIG.seek_amount = spinbox.get_value_as_int()

        vbox = Gtk.VBox(spacing=6)

        def on_show_time_labels_toggled(button, *args):
            CONFIG.show_time_labels = button.get_active()
            if self._bar is not None:
                self._bar.set_time_label_visibility(CONFIG.show_time_labels)

        def create_color(label_text, color, callback):
            hbox = Gtk.HBox(spacing=6)
            hbox.set_border_width(6)
            label = Gtk.Label(label=label_text)
            hbox.pack_start(label, False, True, 0)
            entry = Gtk.Entry()
            if color:
                entry.set_text(color)
            entry.connect('changed', callback)
            hbox.pack_start(entry, True, True, 0)
            return hbox

        box = create_color(_("Override foreground color:"),
                           CONFIG.elapsed_color, elapsed_color_changed)
        vbox.pack_start(box, True, True, 0)

        box = create_color(_("Override hover color:"), CONFIG.hover_color,
                           hover_color_changed)
        vbox.pack_start(box, True, True, 0)

        box = create_color(_("Override remaining color:"),
                           CONFIG.remaining_color, remaining_color_changed)
        vbox.pack_start(box, True, True, 0)

        show_current_pos = Gtk.CheckButton(label=_("Show current position"))
        show_current_pos.set_active(CONFIG.show_current_pos)
        show_current_pos.connect("toggled", on_show_pos_toggled)
        vbox.pack_start(show_current_pos, True, True, 0)

        show_time_labels = Gtk.CheckButton(label=_("Show time labels"))
        show_time_labels.set_active(CONFIG.show_time_labels)
        show_time_labels.connect("toggled", on_show_time_labels_toggled)
        vbox.pack_start(show_time_labels, True, True, 0)

        hbox = Gtk.HBox(spacing=6)
        hbox.set_border_width(6)
        label = Gtk.Label(label=_(
            "Seek amount when scrolling (milliseconds):"
        ))
        hbox.pack_start(label, False, True, 0)
        seek_amount = Gtk.SpinButton(
            adjustment=Gtk.Adjustment(CONFIG.seek_amount,
                                      0, 60000, 1000, 1000, 0)
        )
        seek_amount.set_numeric(True)
        seek_amount.connect("changed", seek_amount_changed)
        hbox.pack_start(seek_amount, True, True, 0)
        vbox.pack_start(hbox, True, True, 0)

        return vbox
Esempio n. 57
0
class Browser(Gtk.Box, Filter):
    """Browsers are how the audio library is presented to the user; they
    create the list of songs that MainSongList is filled with, and pass
    them back via a callback function.
    """

    __gsignals__ = {
        'songs-selected':
        (GObject.SignalFlags.RUN_LAST, None, (object, object)),
        'songs-activated': (GObject.SignalFlags.RUN_LAST, None, ()),
        'uri-received': (GObject.SignalFlags.RUN_LAST, None, (str, ))
    }

    name = _("Library Browser")
    """The browser's name, without an accelerator."""

    accelerated_name = _("Library Browser")
    """The name, with an accelerator."""

    keys = ["Unknown"]
    """Keys which are used to reference the browser from the command line.
    The first is the primary one.
    """

    priority = 100
    """Priority in the menu list (0 is first, higher numbers come later)"""

    uses_main_library = True
    """Whether the browser has the main library as source"""
    def songs_selected(self, songs, is_sorted=False):
        """Emits the songs-selected signal.

        If is_sorted is True the songs will be put as is in the song list.
        In case it's False the songs will be sorted by the song list depending
        on its current sort configuration.
        """

        self.emit("songs-selected", songs, is_sorted)

    def songs_activated(self):
        """Call after calling songs_selected() to activate the songs
        (start playing, enqueue etc..)
        """

        self.emit("songs-activated")

    def pack(self, songpane):
        """For custom packing, define a function that returns a Widget with the
        browser and MainSongList both packed into it.
        """
        raise NotImplementedError

    def unpack(self, container, songpane):
        """Unpack the browser and songlist when switching browsers in the main
        window. The container will be automatically destroyed afterwards.
        """
        raise NotImplementedError

    background = True
    """If true, the global filter will be applied by MainSongList to
    the songs returned.
    """

    headers: Optional[List[str]] = None
    """A list of column headers to display; None means all are okay."""

    @classmethod
    def init(klass, library):
        """Called after library and MainWindow initialization, before the
        GTK main loop starts.
        """
        pass

    def save(self):
        """Save the selected songlist. Browsers should save whatever
        they need to recreate the criteria for the current song list (not
        the list itself).
        """
        raise NotImplementedError

    def restore(self):
        """Restore the selected songlist. restore is called at startup if the
        browser is the first loaded.
        """
        raise NotImplementedError

    def finalize(self, restored):
        """Called after restore/activate or after the browser is loaded.
        restored is True if restore was called."""
        pass

    def scroll(self, song):
        """Scroll to something related to the given song."""
        pass

    def activate(self):
        """Do whatever is needed to emit songs-selected again."""
        raise NotImplementedError

    can_reorder = False
    """If the song list should be reorderable. In case this is True
    every time the song list gets reordered the whole list of songs is
    passed to reordered().
    """

    def reordered(self, songs):
        """In case can_reorder is True and the song list gets reordered
        this gets called with the whole list of songs.
        """

        raise NotImplementedError

    def dropped(self, songs):
        """Called with a list of songs when songs are dropped but the song
        list does not support reordering. This function should return True if
        the drop was successful.
        """

        return False

    def key_pressed(self, event):
        """Gets called with a key pressed event from the song list.
        Should return True if the key was handled.
        """
        return False

    accelerators = None
    """An AccelGroup that is added to / removed from the window where
    the browser is.
    """

    def Menu(self, songs, library, items):
        """This method returns a Gtk.Menu, probably a SongsMenu. After this
        menu is returned the SongList may modify it further.
        """

        return SongsMenu(library, songs, delete=True, items=items)

    def status_text(self, count: int, time: Optional[str] = None) -> str:
        tmpl = numeric_phrase("%d song", "%d songs", count)
        return f"{tmpl} ({time})" if time else tmpl

    replaygain_profiles: Optional[List[str]] = None
    """Replay Gain profiles for this browser."""

    def __str__(self):
        return f"<{type(self).__name__} @ {hex(id(self))}>"
Esempio n. 58
0
    def PluginPreferences(cls, parent):
        vb = Gtk.VBox(spacing=12)

        # Tabulate all settings for neatness
        table = Gtk.Table(n_rows=1, n_columns=2)
        table.props.expand = False
        table.set_col_spacings(6)
        table.set_row_spacings(6)
        rows = []

        def process_option_changed(combo):
            #xcode = combo.get_child().get_text()
            model = combo.get_model()
            lbl, value = model[combo.get_active()]
            cls.config_set("process_if", value)

        def create_model():
            model = Gtk.ListStore(str, str)
            model.append(["<b>%s</b>" % _("always"), UpdateMode.ALWAYS])
            model.append([
                _("if <b>any</b> RG tags are missing"), UpdateMode.ANY_MISSING
            ])
            model.append([
                _("if <b>album</b> RG tags are missing"),
                UpdateMode.ALBUM_MISSING
            ])
            return model

        def set_active(value):
            for i, item in enumerate(model):
                if value == item[1]:
                    combo.set_active(i)

        model = create_model()
        combo = Gtk.ComboBox(model=model)
        set_active(cls.config_get("process_if", UpdateMode.ALWAYS))
        renderer = Gtk.CellRendererText()
        combo.connect('changed', process_option_changed)
        combo.pack_start(renderer, True)
        combo.add_attribute(renderer, "markup", 0)

        rows.append((_("_Process albums:"), combo))

        for (row, (label_text, entry)) in enumerate(rows):
            label = Gtk.Label(label=label_text)
            label.set_alignment(0.0, 0.5)
            label.set_use_underline(True)
            label.set_mnemonic_widget(entry)
            table.attach(label,
                         0,
                         1,
                         row,
                         row + 1,
                         xoptions=Gtk.AttachOptions.FILL)
            table.attach(entry, 1, 2, row, row + 1)

        # Server settings Frame
        frame = Frame(_("Existing Tags"), table)

        vb.pack_start(frame, True, True, 0)
        return vb
Esempio n. 59
0
class LastFMCover(ApiCoverSourcePlugin):
    PLUGIN_ID = "lastfm-cover"
    PLUGIN_NAME = _("Last.fm Cover Source")
    PLUGIN_DESC = _("Downloads covers from Last.fm's cover art archive.")

    @classmethod
    def group_by(cls, song):
        return song.album_key

    @staticmethod
    def priority():
        return 0.33  # No cover size guarantee, accurate

    @property
    def cover_path(self):
        mbid = self.song.get('musicbrainz_albumid', None)
        # It is beneficial to use mbid for cover names.
        if mbid:
            return path.join(cover_dir, escape_filename(mbid))
        else:
            return super().cover_path

    @property
    def url(self):
        _url = 'https://ws.audioscrobbler.com/2.0?method=album.getinfo&' + \
               'api_key=107db6fd4c1c7f53b1526fafddab2c82&format=json&' + \
               'artist={artist}&album={album}&mbid={mbid}'
        song = self.song
        # This can work well for albums in Last.FM
        artists = self._album_artists_for(song) or 'Various Artists'
        song = self.song
        artist = escape_query_value(artists)
        album = escape_query_value(song.get('album', ''))
        mbid = escape_query_value(song.get('musicbrainz_albumid', ''))
        if (artist and album) or mbid:
            return _url.format(artist=artist, album=album, mbid=mbid)
        else:
            return None  # Not enough data

    def _handle_search_response(self, message, json, data=None):
        if not json:
            print_d('Server did not return valid JSON')
            return self.emit('search-complete', [])
        album = json.get('album', {})
        if not album:
            print_d('Album data is not available')
            return self.emit('search-complete', [])
        results = []
        for img in album['image']:
            if img['size'] in ('mega', 'extralarge'):
                url = img['#text']
                if not url:
                    # Yes sometimes it's there but blank
                    continue
                print_d("Got last.fm image: %s" % img)
                results.append({
                    'artist': album['artist'],
                    'album': album['name'],
                    'cover': url.replace('/300x300', '/500x500'),
                    'dimensions': '500x500'
                })
                # This one can be massive, and slow
                results.append({
                    'artist': album['artist'],
                    'album': album['name'],
                    'cover': url.replace('/300x300', ''),
                    'dimensions': '(original)'
                })
                # Prefer the bigger ones
                break
        self.emit('search-complete', results)
Esempio n. 60
0
    def __init__(self, albums, parent, process_mode):
        super(RGDialog, self).__init__(title=_('ReplayGain Analyzer'),
                                       parent=parent)

        self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL)
        self.add_icon_button(_("_Save"), Icons.DOCUMENT_SAVE,
                             Gtk.ResponseType.OK)

        self.process_mode = process_mode
        self.set_default_size(600, 400)
        self.set_border_width(6)

        hbox = Gtk.HBox(spacing=6)
        info = Gtk.Label()
        hbox.pack_start(info, True, True, 0)
        self.vbox.pack_start(hbox, False, False, 6)

        swin = Gtk.ScrolledWindow()
        swin.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        swin.set_shadow_type(Gtk.ShadowType.IN)

        self.vbox.pack_start(swin, True, True, 0)
        view = HintedTreeView()
        swin.add(view)

        def icon_cdf(column, cell, model, iter_, *args):
            item = model[iter_][0]
            if item.error:
                cell.set_property('icon-name', Icons.DIALOG_ERROR)
            else:
                cell.set_property('icon-name', Icons.NONE)

        column = Gtk.TreeViewColumn()
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
        icon_render = Gtk.CellRendererPixbuf()
        column.pack_start(icon_render, True)
        column.set_cell_data_func(icon_render, icon_cdf)
        view.append_column(column)

        def track_cdf(column, cell, model, iter_, *args):
            item = model[iter_][0]
            cell.set_property('text', item.title)
            cell.set_sensitive(model[iter_][1])

        column = Gtk.TreeViewColumn(_("Track"))
        column.set_expand(True)
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
        track_render = Gtk.CellRendererText()
        track_render.set_property('ellipsize', Pango.EllipsizeMode.END)
        column.pack_start(track_render, True)
        column.set_cell_data_func(track_render, track_cdf)
        view.append_column(column)

        def progress_cdf(column, cell, model, iter_, *args):
            item = model[iter_][0]
            cell.set_property('value', int(item.progress * 100))
            cell.set_sensitive(model[iter_][1])

        column = Gtk.TreeViewColumn(_("Progress"))
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
        progress_render = Gtk.CellRendererProgress()
        column.pack_start(progress_render, True)
        column.set_cell_data_func(progress_render, progress_cdf)
        view.append_column(column)

        def gain_cdf(column, cell, model, iter_, *args):
            item = model[iter_][0]
            if item.gain is None or not item.done:
                cell.set_property('text', "-")
            else:
                cell.set_property('text', "%.2f db" % item.gain)
            cell.set_sensitive(model[iter_][1])

        column = Gtk.TreeViewColumn(_("Gain"))
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
        gain_renderer = Gtk.CellRendererText()
        column.pack_start(gain_renderer, True)
        column.set_cell_data_func(gain_renderer, gain_cdf)
        view.append_column(column)

        def peak_cdf(column, cell, model, iter_, *args):
            item = model[iter_][0]
            if item.gain is None or not item.done:
                cell.set_property('text', "-")
            else:
                cell.set_property('text', "%.2f" % item.peak)
            cell.set_sensitive(model[iter_][1])

        column = Gtk.TreeViewColumn(_("Peak"))
        column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
        peak_renderer = Gtk.CellRendererText()
        column.pack_start(peak_renderer, True)
        column.set_cell_data_func(peak_renderer, peak_cdf)
        view.append_column(column)

        self.create_pipelines()
        self._timeout = None
        self._sigs = {}
        self._done = []

        self.__fill_view(view, albums)
        num_to_process = sum(int(rga.should_process) for rga in self._todo)
        template = ngettext(
            "There is <b>%(to-process)s</b> album to update (of %(all)s)",
            "There are <b>%(to-process)s</b> albums to update (of %(all)s)",
            num_to_process)
        info.set_markup(
            template % {
                "to-process": format_int_locale(num_to_process),
                "all": format_int_locale(len(self._todo)),
            })
        self.connect("destroy", self.__destroy)
        self.connect('response', self.__response)