def test_blank_lengths(self): """Check that there are no unsuspected edge-cases for various rating precisions""" for self.r.number in [1, 5, 4, 3, 2]: steps = self.r.number self.failUnlessEqual(len(util.format_rating(1)), steps) self.failUnlessEqual(len(util.format_rating(0)), steps) self.failUnlessEqual(len(util.format_rating(0.5)), steps) self.failUnlessEqual(len(util.format_rating(1 / 3.0)), steps)
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) self._songs = songs image = Gtk.Image.new_from_icon_name(Icons.FAVORITE, Gtk.IconSize.MENU) image.show() self.set_image(image) submenu = Gtk.Menu() self.set_submenu(submenu) self._rating_menu_items = [] for i in RATINGS.all: text = "%0.2f\t%s" % (i, util.format_rating(i)) itm = Gtk.CheckMenuItem(label=text) itm.rating = i submenu.append(itm) handler = itm.connect( 'toggled', self._on_rating_change, i, library) self._rating_menu_items.append((itm, handler)) reset = Gtk.MenuItem(label=_("_Remove Rating"), use_underline=True) reset.connect('activate', self._on_rating_remove, library) self._select_ratings() submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def __init__(self, *args, **kwargs): super(RatingColumn, self).__init__("~rating", *args, **kwargs) self.set_expand(False) self.set_resizable(False) width = self._cell_width(util.format_rating(1.0)) self.set_fixed_width(width) self.set_min_width(width)
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) self._songs = songs image = Gtk.Image.new_from_icon_name(Icons.FAVORITE, Gtk.IconSize.MENU) image.show() self.set_image(image) submenu = Gtk.Menu() self.set_submenu(submenu) self._rating_menu_items = [] for i in RATINGS.all: text = "%0.2f\t%s" % (i, util.format_rating(i)) itm = Gtk.CheckMenuItem(label=text) itm.rating = i submenu.append(itm) handler = itm.connect('toggled', self._on_rating_change, i, library) self._rating_menu_items.append((itm, handler)) reset = Gtk.MenuItem(label=_("_Remove Rating"), use_underline=True) reset.connect('activate', self._on_rating_remove, library) self._select_ratings() submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label) submenu = gtk.Menu() self.set_submenu(submenu) for i in range(0, int(1.0 / util.RATING_PRECISION) + 1): i *= util.RATING_PRECISION itm = gtk.MenuItem("%0.2f\t%s" % (i, util.format_rating(i))) submenu.append(itm) itm.connect_object('activate', self.set_rating, i, songs, library) submenu.show_all()
def _cdf(self, column, cell, model, iter_, user_data): song = model.get_value(iter_) rating = song.get("~#rating") default = config.RATINGS.default if not self._needs_update((rating, default)): return cell.set_sensitive(rating is not None) value = rating if rating is not None else default cell.set_property('text', util.format_rating(value))
def test_numeric_funcs_text(self): songs = NUMERIC_SONGS album = Album(songs[0]) album.songs = set(songs) self.assertEqual(album("~length:sum"), "0:12") self.assertEqual(album("~length:min"), "0:01") self.assertEqual(album("~long-length:min"), "1 second") self.assertEqual(album("~tracks:min"), "6 tracks") self.assertEqual(album("~discs:min"), "2 discs") self.assertEqual(album("~rating:min"), format_rating(0.1)) self.assertEqual(album("~filesize:min"), "0 B")
def test_blank_values(self): self.r.number = 5 self.r.blank_symbol = "0" self.r.full_symbol = "1" # Easy ones first self.failUnlessEqual(util.format_rating(0.0), "00000") self.failUnlessEqual(util.format_rating(0.2), "10000") self.failUnlessEqual(util.format_rating(0.8), "11110") self.failUnlessEqual(util.format_rating(1.0), "11111") # A bit arbitrary, but standard behaviour self.failUnlessEqual(util.format_rating(0.5), "11100") # Test rounding down... self.failUnlessEqual(util.format_rating(0.6), "11100") # Test rounding up... self.failUnlessEqual(util.format_rating(0.9), "11111") # You never know... self.failUnlessEqual(util.format_rating(3.0), "11111") self.failUnlessEqual(util.format_rating(-0.5), "00000")
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) submenu = Gtk.Menu() self.set_submenu(submenu) for i in RATINGS.all: itm = Gtk.MenuItem(label="%0.2f\t%s" % (i, util.format_rating(i))) submenu.append(itm) connect_obj(itm, 'activate', self.set_rating, i, songs, library) reset = Gtk.MenuItem(label=_("_Remove rating"), use_underline=True) connect_obj(reset, 'activate', self.remove_rating, songs, library) submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def __init__(self, parent, count, value): assert count > 1 title = (_("Are you sure you want to change the " "rating of all %d songs?") % count) description = _("The rating of all selected songs will " "be changed to '%s'") % util.format_rating(value) super(ConfirmRateMultipleDialog, self).__init__( gtk.MESSAGE_WARNING, parent, title, description, gtk.BUTTONS_NONE) self.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_APPLY, gtk.RESPONSE_YES)
def __init__(self, parent, action_title, count, value): assert count > 1 title = (_("Are you sure you want to change the " "rating of all %d songs?") % count) desc = (_("The saved ratings will be removed") if value is None else _("The rating of all selected songs will be changed to " "'%s'") % util.format_rating(value)) super().__init__( Gtk.MessageType.WARNING, parent, title, desc, Gtk.ButtonsType.NONE) self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) self.add_button(action_title, Gtk.ResponseType.YES)
def __init__(self, parent, action_title, count, value): assert count > 1 title = (_("Are you sure you want to change the " "rating of all %d songs?") % count) desc = (_("The saved ratings will be removed") if value is None else _("The rating of all selected songs will be changed to " "'%s'") % util.format_rating(value)) super(ConfirmRateMultipleDialog, self).__init__( Gtk.MessageType.WARNING, parent, title, desc, Gtk.ButtonsType.NONE) self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) self.add_button(action_title, Gtk.ResponseType.YES)
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) self.set_songs(songs) submenu = Gtk.Menu() self.set_submenu(submenu) for i in RATINGS.all: itm = Gtk.MenuItem(label="%0.2f\t%s" % (i, util.format_rating(i))) submenu.append(itm) itm.connect('activate', self._on_rating_change, i, library) reset = Gtk.MenuItem(label=_("_Remove Rating"), use_underline=True) reset.connect('activate', self._on_rating_remove, library) submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton( _("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton( _("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) 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()
def __init__(self, parent, count: int, value: Optional[float]): assert count > 1 title = (_("Are you sure you want to change the " "rating of all %d songs?") % count) if value is None: desc = _("The saved ratings will be removed") action_title = _("_Remove Rating") else: desc = ( _("The rating of all selected songs will be changed to %s") % format_rating(value)) action_title = _("Change _Rating") super().__init__(Gtk.MessageType.WARNING, parent, title, desc, Gtk.ButtonsType.NONE) self.add_button(_("_Cancel"), Gtk.ResponseType.CANCEL) self.add_button(action_title, Gtk.ResponseType.YES)
def __button_press(self, view, event, librarian): if event.button != 1: return x, y = map(int, [event.x, event.y]) try: path, col, cellx, celly = view.get_path_at_pos(x, y) except TypeError: return True if event.window != self.get_bin_window(): return False if col.header_name == "~#rating": if not config.getboolean("browsers", "rating_click"): return song = view.get_model()[path][0] l = gtk.Label() l.set_text(util.format_rating(util.RATING_PRECISION)) width = l.size_request()[0] l.destroy() count = int(float(cellx - 5) / width) + 1 rating = max(0.0, min(1.0, count * util.RATING_PRECISION)) if (rating <= util.RATING_PRECISION and song("~#rating") == util.RATING_PRECISION): rating = 0 self.__set_rating(rating, [song], librarian)
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) self._songs = songs ratings = {song("~#rating") for song in songs if song.has_rating} submenu = Gtk.Menu() self.set_submenu(submenu) for i in RATINGS.all: text = "%0.2f\t%s" % (i, util.format_rating(i)) itm = Gtk.CheckMenuItem(label=text) is_selected = i in ratings itm.set_active(is_selected) submenu.append(itm) itm.connect('activate', self._on_rating_change, i, library) reset = Gtk.MenuItem(label=_("_Remove Rating"), use_underline=True) reset.connect('activate', self._on_rating_remove, library) submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def __init__(self, songs, library, label=_("_Rating")): super(RatingsMenuItem, self).__init__(label=label, use_underline=True) self._songs = songs ratings = {song("~#rating") for song in songs} submenu = Gtk.Menu() self.set_submenu(submenu) for i in RATINGS.all: text = "%0.2f\t%s" % (i, util.format_rating(i)) itm = Gtk.CheckMenuItem(label=text) is_selected = i in ratings itm.set_active(is_selected) submenu.append(itm) itm.connect('activate', self._on_rating_change, i, library) reset = Gtk.MenuItem(label=_("_Remove Rating"), use_underline=True) reset.connect('activate', self._on_rating_remove, library) submenu.append(SeparatorMenuItem()) submenu.append(reset) submenu.show_all()
def test_full(self): self.failUnlessEqual( len(util.format_rating(1, blank=False)), int(1 / self.r.precision))
def _cdf(self, column, cell, model, iter_, user_data): value = model.get_value(iter_).get("~#rating", config.RATINGS.default) if not self._needs_update(value): return cell.set_property('text', util.format_rating(value))
def __get_value(self, key): """This is similar to __call__ in the AudioFile class. All internal tags are changed to represent a collection of songs. """ # Using key:<func> runs the resulting list of values # through the function before returning it. # Numeric keys without a func will default to a reasonable function if key.startswith("~#"): key = key[2:] if key[-4:-3] == ":": func = key[-3:] key = key[:-4] elif key == "tracks": return len(self.songs) elif key == "discs": return len(set([song("~#disc", 1) for song in self.songs])) elif key == "bitrate": length = self.__get_value("~#length") if not length: return 0 w = lambda s: s("~#bitrate", 0) * s("~#length", 0) return sum(w(song) for song in self.songs) / length elif key in NUM_DEFAULT_FUNCS: func = NUM_DEFAULT_FUNCS[key] else: #Unknown key. AudioFile will try to cast the values to int, #default to avg func = "avg" key = "~#" + key func = NUM_FUNCS.get(func) if func: #if none of the songs can return a numeric key #the album returns default values = (song(key) for song in self.songs) values = [v for v in values if v != ""] if values: return func(values) else: return None elif key in INTERN_NUM_DEFAULT: return 0 return None elif key[:1] == "~": key = key[1:] keys = {"people": {}, "peoplesort": {}} if key in keys: people = keys["people"] peoplesort = keys["peoplesort"] for song in self.songs: # Rank people by "relevance" -- artists before composers # before performers, then by number of appearances. for w, k in enumerate(ELPOEP): persons = song.list(k) for person in persons: people[person] = (people.get(person, 0) - PEOPLE_SCORE[w]) if k in TAG_TO_SORT: persons = song.list(TAG_TO_SORT[k]) or persons for person in persons: peoplesort[person] = (peoplesort.get(person, 0) - PEOPLE_SCORE[w]) #It's cheaper to get people and peoplesort in one go keys["people"] = sorted(people.keys(), key=people.__getitem__)[:100] keys["peoplesort"] = sorted(peoplesort.keys(), key=peoplesort.__getitem__)[:100] ret = keys.pop(key) ret = (ret and "\n".join(ret)) or None other, values = keys.popitem() other = "~" + other if not values: self.__default.add(other) else: if other in self.__used: self.__used.remove(other) self.__used.append(other) self.__cache[other] = "\n".join(values) return ret elif key == "length": length = self.__get_value("~#length") if length is None: return None return util.format_time(length) elif key == "long-length": length = self.__get_value("~#length") if length is None: return None return util.format_time_long(length) elif key == "tracks": tracks = self.__get_value("~#tracks") if tracks is None: return None return ngettext("%d track", "%d tracks", tracks) % tracks elif key == "discs": discs = self.__get_value("~#discs") if discs > 1: return ngettext("%d disc", "%d discs", discs) % discs else: return None elif key == "rating": rating = self.__get_value("~#rating") if rating is None: return None return util.format_rating(rating) elif key == "cover": return ((self.cover != type(self).cover) and "y") or None elif key == "filesize": size = self.__get_value("~#filesize") if size is None: return None return util.format_size(size) key = "~" + key #Nothing special was found, so just take all values of the songs #and sort them by their number of appearance result = {} for song in self.songs: for value in song.list(key): result[value] = result.get(value, 0) - 1 values = map(lambda x: x[0], sorted(result.items(), key=lambda x: x[1])) if not values: return None return "\n".join(values)
def test_full(self): self.failUnlessEqual(len(util.format_rating(1, blank=False)), int(1 / self.r.precision))
def _get_min_width(self): return self._cell_width(util.format_rating(1.0))
def test_bogus(self): max_length = int(1 / self.r.precision) self.failUnlessEqual(len(util.format_rating(2**32 - 1, blank=False)), max_length) self.failUnlessEqual(len(util.format_rating(-4.2, blank=False)), 0)
def _apply_value(self, model, iter_, cell, value): rating, default = value cell.set_sensitive(rating is not None) value = rating if rating is not None else default cell.set_property('text', util.format_rating(value))
def __init__(self, browser): if self.is_not_unique(): return super().__init__() self.set_border_width(12) self.set_title(_("Cover Grid Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) self.mag_lock = False box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _text"), "browsers", "album_text") cb.set_active(config.getboolean("browsers", "album_text")) cb.connect('toggled', lambda s: browser.toggle_text()) vbox.pack_start(cb, False, True, 0) cb2 = ConfigCheckButton(_("Show \"All Albums\" Item"), "browsers", "covergrid_all") cb2.set_active(config.getboolean("browsers", "covergrid_all", True)) def refilter(s): mod = browser.view.get_model() if mod: mod.refilter() cb2.connect('toggled', refilter) vbox.pack_start(cb2, False, True, 0) cb3 = ConfigCheckButton(_("Wide Mode"), "browsers", "covergrid_wide") cb3.set_active(config.getboolean("browsers", "covergrid_wide", False)) cb3.connect('toggled', lambda s: browser.toggle_wide()) vbox.pack_start(cb3, False, True, 0) # Redraws the covers only when the user releases the slider def mag_button_press(*_): self.mag_lock = True def mag_button_release(mag, _): self.mag_lock = False mag_changed(mag) def mag_changed(mag): if self.mag_lock: return newmag = mag.get_value() oldmag = config.getfloat("browsers", "covergrid_magnification", 3.) if newmag == oldmag: print_d("Covergrid magnification haven't changed: {0}".format( newmag)) return print_d('Covergrid magnification update from {0} to {1}'.format( oldmag, newmag)) config.set("browsers", "covergrid_magnification", mag.get_value()) browser.update_mag() mag_scale = Gtk.HScale(adjustment=Gtk.Adjustment.new( config.getfloat("browsers", "covergrid_magnification", 3), 0., 10., .5, .5, 0)) mag_scale.set_tooltip_text(_("Cover Magnification")) l = Gtk.Label(label=_("Cover Magnification")) mag_scale.set_value_pos(Gtk.PositionType.RIGHT) mag_scale.connect('button-press-event', mag_button_press) mag_scale.connect('button-release-event', mag_button_release) mag_scale.connect('value-changed', mag_changed) vbox.pack_start(l, False, True, 0) vbox.pack_start(mag_scale, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) 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()
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences") + " - Quod Libet") self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._EXAMPLE_ALBUM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton(_("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton(_("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) vbox = Gtk.VBox(spacing=6) label = Gtk.Label() label.set_alignment(0.0, 0.5) label.set_padding(6, 6) eb = Gtk.EventBox() eb.get_style_context().add_class("entry") eb.add(label) edit = PatternEditBox(PATTERN) edit.text = browser._pattern_text edit.apply.connect('clicked', self.__set_pattern, edit, browser) connect_obj(edit.buffer, 'changed', self.__preview_pattern, edit, label) vbox.pack_start(eb, False, True, 3) vbox.pack_start(edit, True, True, 0) self.__preview_pattern(edit, label) f = qltk.Frame(_("Album Display"), child=vbox) box.pack_start(f, True, True, 0) main_box = Gtk.VBox(spacing=12) close = Gtk.Button(stock=Gtk.STOCK_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()
def test_empty(self): self.failUnlessEqual(util.format_rating(0, blank=False), "")
def __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. A default value may be given (like dict.get); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the 'connector' keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. For details on tied tags, see the documentation for util.tagsplit. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] for v in map(self.__call__, util.tagsplit(real_key)): v = decode_value(real_key, v) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self("~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return URI.frompath(self["~filename"]) elif key == "format": return self.get("~format", self.format) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": try: fileobj = file(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in INTERN_NUM_DEFAULT: return dict.get(self, key, 0) else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % ( decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def _popup_menu(self, icon, button, time): if self.__destroy_win32_menu(): return self.__menu = menu = Gtk.Menu() player = app.player window = app.window if player.paused: playpause = MenuItem(_("_Play"), Icons.MEDIA_PLAYBACK_START) else: playpause = MenuItem(_("P_ause"), Icons.MEDIA_PLAYBACK_PAUSE) playpause.connect('activate', self.__play_pause) previous = MenuItem(_("Pre_vious"), Icons.MEDIA_SKIP_BACKWARD) previous.connect('activate', lambda *args: player.previous()) next = MenuItem(_("_Next"), Icons.MEDIA_SKIP_FORWARD) next.connect('activate', lambda *args: player.next()) orders = Gtk.MenuItem(label=_("Play _Order"), use_underline=True) repeat = Gtk.CheckMenuItem(label=_("_Repeat"), use_underline=True) repeat.set_active(window.repeat.get_active()) repeat.connect('toggled', lambda s: window.repeat.set_active(s.get_active())) def set_safter(widget, safter_action): safter_action.set_active(widget.get_active()) safter_action = app.window.stop_after safter = Gtk.CheckMenuItem(label=_("Stop _after this song"), use_underline=True) safter.set_active(safter_action.get_active()) safter.connect('toggled', set_safter, safter_action) def set_order(widget, order): name = order.name try: window.order.set_active_by_name(name) except ValueError: pass order_items = [] item = None active_order = window.order.get_active() for Kind in ORDERS: item = RadioMenuItem( group=item, label=Kind.accelerated_name, use_underline=True) order_items.append(item) if Kind is active_order: item.set_active(True) item.connect('toggled', set_order, Kind) order_sub = Gtk.Menu() order_sub.append(repeat) order_sub.append(safter) order_sub.append(SeparatorMenuItem()) for item in order_items: order_sub.append(item) orders.set_submenu(order_sub) browse = qltk.MenuItem(_("_Browse Library"), Icons.EDIT_FIND) browse_sub = Gtk.Menu() for Kind in browsers.browsers: if Kind.is_empty: continue i = Gtk.MenuItem(label=Kind.accelerated_name, use_underline=True) connect_obj(i, 'activate', LibraryBrowser.open, Kind, app.library, app.player) browse_sub.append(i) browse.set_submenu(browse_sub) props = qltk.MenuItem(_("Edit _Tags"), Icons.DOCUMENT_PROPERTIES) props.connect('activate', self.__properties) info = MenuItem(_("_Information"), Icons.DIALOG_INFORMATION) info.connect('activate', self.__information) def set_rating(value): song = player.song if song is None: return else: song["~#rating"] = value app.librarian.changed([song]) rating = Gtk.MenuItem(label=_("_Rating"), use_underline=True) rating_sub = Gtk.Menu() for r in RATINGS.all: item = Gtk.MenuItem(label="%0.2f\t%s" % (r, util.format_rating(r))) connect_obj(item, 'activate', set_rating, r) rating_sub.append(item) rating.set_submenu(rating_sub) quit = MenuItem(_("_Quit"), Icons.APPLICATION_EXIT) quit.connect('activate', lambda *x: app.quit()) menu.append(playpause) menu.append(SeparatorMenuItem()) menu.append(previous) menu.append(next) menu.append(orders) menu.append(SeparatorMenuItem()) menu.append(browse) menu.append(SeparatorMenuItem()) menu.append(props) menu.append(info) menu.append(rating) menu.append(SeparatorMenuItem()) menu.append(quit) menu.show_all() if sys.platform in ("win32", "darwin"): pos_func = pos_arg = None else: pos_func = Gtk.StatusIcon.position_menu pos_arg = self._icon menu.popup(None, None, pos_func, pos_arg, button, time)
def test_bogus(self): max_length = int(1 / self.r.precision) self.failUnlessEqual(len(util.format_rating(2 ** 32 - 1, blank=False)), max_length) self.failUnlessEqual(len(util.format_rating(-4.2, blank=False)), 0)
def __get_value(self, key): """This is similar to __call__ in the AudioFile class. All internal tags are changed to represent a collection of songs. """ # Using key:<func> runs the resulting list of values # through the function before returning it. # Numeric keys without a func will default to a reasonable function if key.startswith("~#"): key = key[2:] if key[-4:-3] == ":": func = key[-3:] key = key[:-4] elif key == "tracks": return len(self.songs) elif key == "discs": return len(set([song("~#disc", 1) for song in self.songs])) elif key == "bitrate": length = self.__get_value("~#length") if not length: return 0 w = lambda s: s("~#bitrate", 0) * s("~#length", 0) return sum(w(song) for song in self.songs) / length else: # Standard or unknown numeric key. # AudioFile will try to cast the values to int, # default to avg func = NUM_DEFAULT_FUNCS.get(key, "avg") key = "~#" + key func = NUM_FUNCS.get(func) if func: # If none of the songs can return a numeric key, # the album returns default values = (song(key) for song in self.songs) values = [v for v in values if v != ""] return func(values) if values else None elif key in INTERN_NUM_DEFAULT: return 0 return None elif key[:1] == "~": key = key[1:] keys = {"people": {}, "peoplesort": {}} if key in keys: people = keys["people"] peoplesort = keys["peoplesort"] for song in self.songs: # Rank people by "relevance" -- artists before composers # before performers, then by number of appearances. for w, k in enumerate(ELPOEP): persons = song.list(k) for person in persons: people[person] = (people.get(person, 0) - PEOPLE_SCORE[w]) if k in TAG_TO_SORT: persons = song.list(TAG_TO_SORT[k]) or persons for person in persons: peoplesort[person] = (peoplesort.get(person, 0) - PEOPLE_SCORE[w]) # It's cheaper to get people and peoplesort in one go keys["people"] = sorted(people.keys(), key=people.__getitem__)[:100] keys["peoplesort"] = sorted(peoplesort.keys(), key=peoplesort.__getitem__)[:100] ret = keys.pop(key) ret = (ret and "\n".join(ret)) or None other, values = keys.popitem() other = "~" + other if not values: self.__default.add(other) else: if other in self.__used: self.__used.remove(other) self.__used.append(other) self.__cache[other] = "\n".join(values) return ret elif key == "length": length = self.__get_value("~#length") return None if length is None else util.format_time(length) elif key == "long-length": length = self.__get_value("~#length") return (None if length is None else util.format_time_long(length)) elif key == "tracks": tracks = self.__get_value("~#tracks") return (None if tracks is None else ngettext("%d track", "%d tracks", tracks) % tracks) elif key == "discs": discs = self.__get_value("~#discs") if discs > 1: return ngettext("%d disc", "%d discs", discs) % discs else: # TODO: check this is correct for discs == 1 return None elif key == "rating": rating = self.__get_value("~#rating") if rating is None: return None return util.format_rating(rating) elif key == "cover": return ((self.cover != type(self).cover) and "y") or None elif key == "filesize": size = self.__get_value("~#filesize") return None if size is None else util.format_size(size) key = "~" + key # Nothing special was found, so just take all values of the songs # and sort them by their number of appearance result = {} for song in self.songs: for value in song.list(key): result[value] = result.get(value, 0) - 1 values = map(lambda x: x[0], sorted(result.items(), key=lambda x: x[1])) return "\n".join(values) if values else None
def _cdf(self, column, cell, model, iter, tag): value = model[iter][0].get("~#rating", const.DEFAULT_RATING) if not self._needs_update(value): return cell.set_property('text', util.format_rating(value))
def test_bogus(self): max_length = int(1 / util.RATING_PRECISION) self.failUnlessEqual(len(util.format_rating(2**32-1)), max_length) self.failUnlessEqual(len(util.format_rating(-4.2)), 0)
def test_rating_length(self): config.RATINGS.number = 4 for i in range(0, int(1 / self.r.precision + 1)): self.failUnlessEqual( i, len(util.format_rating(i * self.r.precision, blank=False)))
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Album List Preferences") + " - Quod Libet") self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._EXAMPLE_ALBUM["~rating"] = format_rating(0.75) box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton( _("Show album _covers"), "browsers", "album_covers") cb.set_active(config.getboolean("browsers", "album_covers")) cb.connect('toggled', lambda s: browser.toggle_covers()) vbox.pack_start(cb, False, True, 0) cb = ConfigCheckButton( _("Inline _search includes people"), "browsers", "album_substrings") cb.set_active(config.getboolean("browsers", "album_substrings")) vbox.pack_start(cb, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) vbox = Gtk.VBox(spacing=6) label = Gtk.Label() label.set_alignment(0.0, 0.5) label.set_padding(6, 6) eb = Gtk.EventBox() eb.get_style_context().add_class("entry") eb.add(label) edit = PatternEditBox(PATTERN) edit.text = browser._pattern_text edit.apply.connect('clicked', self.__set_pattern, edit, browser) connect_obj( edit.buffer, 'changed', self.__preview_pattern, edit, label) vbox.pack_start(eb, False, True, 3) vbox.pack_start(edit, True, True, 0) self.__preview_pattern(edit, label) f = qltk.Frame(_("Album Display"), child=vbox) box.pack_start(f, True, True, 0) 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()
def __call__(self, key, default: Any = u"", connector=" - ", joiner=', '): """Return the value(s) for a key, synthesizing if necessary. Multiple values for a key are delimited by newlines. A default value may be given (like `dict.get`); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the `connector` keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. The `joiner` keyword specifies how multiple *values* will be joined within that tied tag output, e.g. ~people~title = "Kanye West, Jay Z - New Day" For details on tied tags, see the documentation for `util.tagsplit`. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] sub_tags = util.tagsplit(real_key) # If it's genuinely a tied tag (not ~~people etc), we want # to delimit the multi-values separately from the tying j = joiner if len(sub_tags) > 1 else "\n" for t in sub_tags: vs = [decode_value(real_key, v) for v in (self.list(t))] v = j.join(vs) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return fsn2uri(self["~filename"]) elif key == "format": return self.get("~format", str(self.format)) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": encoding = "\n".join( part for part in [self.get("~encoding"), self.get("encodedby")] if part) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self["lyrics"] except KeyError: pass try: return self["unsyncedlyrics"] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. lyric_filename = self.lyric_filename if not lyric_filename: return default try: with open(lyric_filename, "rb") as fileobj: print_d(f"Reading lyrics from {lyric_filename!r}") text = fileobj.read().decode("utf-8", "replace") # try to skip binary files if "\0" in text: return default return text except (EnvironmentError, UnicodeDecodeError): return default elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # TODO: avoid static dependency here... somehow from quodlibet import app lib = app.library if not lib: return "" playlists = lib.playlists.playlists_featuring(self) return "\n".join(s.name for s in playlists) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: # build a title with missing_title_template option unknown_track_template = _( config.gettext("browsers", "missing_title_template")) from quodlibet.pattern import Pattern try: pattern = Pattern(unknown_track_template) except ValueError: title = decode_value("~basename", self("~basename")) else: title = pattern % self return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
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 __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. A default value may be given (like dict.get); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the 'connector' keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. For details on tied tags, see the documentation for util.tagsplit. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] for v in map(self.__call__, util.tagsplit(real_key)): v = decode_value(real_key, v) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return text_type(fsn2uri(self["~filename"])) elif key == "format": return self.get("~format", self.format) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": parts = filter(None, [self.get("~encoding"), self.get("encodedby")]) encoding = u"\n".join(parts) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % (decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def __call__(self, key, default=u"", connector=" - ", joiner=', '): """Return the value(s) for a key, synthesizing if necessary. Multiple values for a key are delimited by newlines. A default value may be given (like `dict.get`); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the `connector` keyword argument may be used to specify what it is tied with. In case the tied tag contains numeric and file path tags, the result will still be a unicode string. The `joiner` keyword specifies how multiple *values* will be joined within that tied tag output, e.g. ~people~title = "Kanye West, Jay Z - New Day" For details on tied tags, see the documentation for `util.tagsplit`. """ if key[:1] == "~": key = key[1:] if "~" in key: real_key = "~" + key values = [] sub_tags = util.tagsplit(real_key) # If it's genuinely a tied tag (not ~~people etc), we want # to delimit the multi-values separately from the tying j = joiner if len(sub_tags) > 1 else "\n" for t in sub_tags: vs = [decode_value(real_key, v) for v in (self.list(t))] v = j.join(vs) if v: values.append(v) return connector.join(values) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self("~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return fsn2uri(self["~filename"]) elif key == "format": return self.get("~format", text_type(self.format)) elif key == "codec": codec = self.get("~codec") if codec is None: return self("~format") return codec elif key == "encoding": parts = filter(None, [self.get("~encoding"), self.get("encodedby")]) encoding = u"\n".join(parts) return encoding or default elif key == "language": codes = self.list("language") if not codes: return default return u"\n".join(iso639.translate(c) or c for c in codes) elif key == "bitrate": return util.format_bitrate(self("~#bitrate")) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": # First, try the embedded lyrics. try: return self[key] except KeyError: pass # If there are no embedded lyrics, try to read them from # the external file. try: fileobj = open(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in NUMERIC_ZERO_DEFAULT: return 0 else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") return "%s [%s]" % ( decode_value("~basename", basename), _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def __call__(self, key, default=u"", connector=" - "): """Return a key, synthesizing it if necessary. A default value may be given (like dict.get); the default default is an empty unicode string (even if the tag is numeric). If a tied tag ('a~b') is requested, the 'connector' keyword argument may be used to specify what it is tied with. For details on tied tags, see the documentation for util.tagsplit.""" if key[:1] == "~": key = key[1:] if "~" in key: # FIXME: decode ~filename etc. if not isinstance(default, basestring): return default return connector.join( filter( None, map( lambda x: isinstance(x, basestring) and x or str(x ), map( lambda x: (isinstance(x, float) and "%.2f" % x) or x, map(self.__call__, util.tagsplit("~" + key)))))) or default elif key == "#track": try: return int(self["tracknumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "#disc": try: return int(self["discnumber"].split("/")[0]) except (ValueError, TypeError, KeyError): return default elif key == "length": length = self.get("~#length") if length is None: return default else: return util.format_time_display(length) elif key == "#rating": return dict.get(self, "~" + key, config.RATINGS.default) elif key == "rating": return util.format_rating(self("~#rating")) elif key == "people": return "\n".join(self.list_unique(PEOPLE)) or default elif key == "people:real": # Issue 1034: Allow removal of V.A. if others exist. unique = self.list_unique(PEOPLE) # Order is important, for (unlikely case): multiple removals for val in VARIOUS_ARTISTS_VALUES: if len(unique) > 1 and val in unique: unique.remove(val) return "\n".join(unique) or default elif key == "people:roles": return (self._role_call("performer", PEOPLE) or default) elif key == "peoplesort": return ("\n".join(self.list_unique(PEOPLE_SORT)) or self("~people", default, connector)) elif key == "peoplesort:roles": # Ignores non-sort tags if there are any sort tags (e.g. just # returns "B" for {artist=A, performersort=B}). # TODO: figure out the "correct" behavior for mixed sort tags return (self._role_call("performersort", PEOPLE_SORT) or self("~peoplesort", default, connector)) elif key in ("performers", "performer"): return self._prefixvalue("performer") or default elif key in ("performerssort", "performersort"): return (self._prefixvalue("performersort") or self("~" + key[-4:], default, connector)) elif key in ("performers:roles", "performer:roles"): return (self._role_call("performer") or default) elif key in ("performerssort:roles", "performersort:roles"): return (self._role_call("performersort") or self( "~" + key.replace("sort", ""), default, connector)) elif key == "basename": return os.path.basename(self["~filename"]) or self["~filename"] elif key == "dirname": return os.path.dirname(self["~filename"]) or self["~filename"] elif key == "uri": try: return self["~uri"] except KeyError: return URI.frompath(self["~filename"]) elif key == "format": return self.get("~format", self.format) elif key == "#date": date = self.get("date") if date is None: return default return util.date_key(date) elif key == "year": return self.get("date", default)[:4] elif key == "#year": try: return int(self.get("date", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "originalyear": return self.get("originaldate", default)[:4] elif key == "#originalyear": try: return int(self.get("originaldate", default)[:4]) except (ValueError, TypeError, KeyError): return default elif key == "#tracks": try: return int(self["tracknumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "#discs": try: return int(self["discnumber"].split("/")[1]) except (ValueError, IndexError, TypeError, KeyError): return default elif key == "lyrics": try: fileobj = file(self.lyric_filename, "rU") except EnvironmentError: return default else: return fileobj.read().decode("utf-8", "replace") elif key == "filesize": return util.format_size(self("~#filesize", 0)) elif key == "playlists": # See Issue 876 # Avoid circular references from formats/__init__.py from quodlibet.util.collection import Playlist playlists = Playlist.playlists_featuring(self) return "\n".join([s.name for s in playlists]) or default elif key.startswith("#replaygain_"): try: val = self.get(key[1:], default) return round(float(val.split(" ")[0]), 2) except (ValueError, TypeError, AttributeError): return default elif key[:1] == "#": key = "~" + key if key in self: return self[key] elif key in INTERN_NUM_DEFAULT: return dict.get(self, key, 0) else: try: val = self[key[2:]] except KeyError: return default try: return int(val) except ValueError: try: return float(val) except ValueError: return default else: return dict.get(self, "~" + key, default) elif key == "title": title = dict.get(self, "title") if title is None: basename = self("~basename") basename = basename.decode(const.FSCODING, "replace") return "%s [%s]" % (basename, _("Unknown")) else: return title elif key in SORT_TO_TAG: try: return self[key] except KeyError: key = SORT_TO_TAG[key] return dict.get(self, key, default)
def __init__(self, browser): if self.is_not_unique(): return super(Preferences, self).__init__() self.set_border_width(12) self.set_title(_("Cover Grid Preferences")) self.set_default_size(420, 380) self.set_transient_for(qltk.get_top_parent(browser)) # Do this config-driven setup at instance-time self._PREVIEW_ITEM["~rating"] = format_rating(0.75) self.mag_lock = False box = Gtk.VBox(spacing=6) vbox = Gtk.VBox(spacing=6) cb = ConfigCheckButton( _("Show album _text"), "browsers", "album_text") cb.set_active(config.getboolean("browsers", "album_text")) cb.connect('toggled', lambda s: browser.toggle_text()) vbox.pack_start(cb, False, True, 0) cb2 = ConfigCheckButton( _("Show \"All Albums\" Item"), "browsers", "covergrid_all") cb2.set_active(config.getboolean("browsers", "covergrid_all", False)) cb2.connect('toggled', lambda s: browser.view.get_model().refilter()) vbox.pack_start(cb2, False, True, 0) cb3 = ConfigCheckButton( _("Wide Mode"), "browsers", "covergrid_wide") cb3.set_active(config.getboolean("browsers", "covergrid_wide", False)) cb3.connect('toggled', lambda s: browser.toggle_wide()) vbox.pack_start(cb3, False, True, 0) # Redraws the covers only when the user releases the slider def mag_button_press(*_): self.mag_lock = True def mag_button_release(mag, _): self.mag_lock = False mag_changed(mag) def mag_changed(mag): if self.mag_lock: return newmag = mag.get_value() oldmag = config.getfloat("browsers", "covergrid_magnification", 3.) if newmag == oldmag: print_d("Covergrid magnification haven't changed: {0}" .format(newmag)) return print_d('Covergrid magnification update from {0} to {1}' .format(oldmag, newmag)) config.set("browsers", "covergrid_magnification", mag.get_value()) browser.update_mag() mag_scale = Gtk.HScale( adjustment=Gtk.Adjustment.new(config.getfloat("browsers", "covergrid_magnification", 3), 0., 10., .5, .5, 0)) mag_scale.set_tooltip_text(_("Cover Magnification")) l = Gtk.Label(label=_("Cover Magnification")) mag_scale.set_value_pos(Gtk.PositionType.RIGHT) mag_scale.connect('button-press-event', mag_button_press) mag_scale.connect('button-release-event', mag_button_release) mag_scale.connect('value-changed', mag_changed) vbox.pack_start(l, False, True, 0) vbox.pack_start(mag_scale, False, True, 0) f = qltk.Frame(_("Options"), child=vbox) box.pack_start(f, False, True, 12) display_frame = self.edit_display_pane(browser, _("Album Display")) box.pack_start(display_frame, True, True, 0) 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()
def _popup_menu(self, icon, button, time): if self.__destroy_win32_menu(): return self.__menu = menu = Gtk.Menu() player = app.player window = app.window pp_icon = [Gtk.STOCK_MEDIA_PAUSE, Gtk.STOCK_MEDIA_PLAY][player.paused] playpause = Gtk.ImageMenuItem.new_from_stock(pp_icon, None) playpause.connect('activate', self.__play_pause) previous = Gtk.ImageMenuItem.new_from_stock( Gtk.STOCK_MEDIA_PREVIOUS, None) previous.connect('activate', lambda *args: player.previous()) next = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_MEDIA_NEXT, None) next.connect('activate', lambda *args: player.next()) orders = Gtk.MenuItem(label=_("Play _Order"), use_underline=True) repeat = Gtk.CheckMenuItem(label=_("_Repeat"), use_underline=True) repeat.set_active(window.repeat.get_active()) repeat.connect('toggled', lambda s: window.repeat.set_active(s.get_active())) def set_safter(widget, safter_action): safter_action.set_active(widget.get_active()) safter_action = app.window.stop_after safter = Gtk.CheckMenuItem(label=_("Stop _after this song"), use_underline=True) safter.set_active(safter_action.get_active()) safter.connect('toggled', set_safter, safter_action) def set_order(widget, num): window.order.set_active(num) order_items = [] item = None for i, Kind in enumerate(ORDERS): item = RadioMenuItem( group=item, label=Kind.accelerated_name, use_underline=True) order_items.append(item) item.connect('toggled', set_order, i) order_items[window.order.get_active()].set_active(True) order_sub = Gtk.Menu() order_sub.append(repeat) order_sub.append(safter) order_sub.append(SeparatorMenuItem()) for item in order_items: order_sub.append(item) orders.set_submenu(order_sub) browse = qltk.MenuItem(_("_Browse Library"), Gtk.STOCK_FIND) browse_sub = Gtk.Menu() for Kind in browsers.browsers: if not Kind.in_menu: continue i = Gtk.MenuItem(label=Kind.accelerated_name, use_underline=True) i.connect_object( 'activate', LibraryBrowser.open, Kind, app.library) browse_sub.append(i) browse.set_submenu(browse_sub) props = qltk.MenuItem(_("Edit _Tags"), Gtk.STOCK_PROPERTIES) props.connect('activate', self.__properties) info = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_INFO, None) info.connect('activate', self.__information) def set_rating(value): song = player.song if song is None: return else: song["~#rating"] = value app.librarian.changed([song]) rating = Gtk.MenuItem(label=_("_Rating"), use_underline=True) rating_sub = Gtk.Menu() for r in RATINGS.all: item = Gtk.MenuItem("%0.2f\t%s" % (r, util.format_rating(r))) item.connect_object('activate', set_rating, r) rating_sub.append(item) rating.set_submenu(rating_sub) quit = Gtk.ImageMenuItem.new_from_stock(Gtk.STOCK_QUIT, None) quit.connect('activate', lambda *x: app.quit()) menu.append(playpause) menu.append(SeparatorMenuItem()) menu.append(previous) menu.append(next) menu.append(orders) menu.append(SeparatorMenuItem()) menu.append(browse) menu.append(SeparatorMenuItem()) menu.append(props) menu.append(info) menu.append(rating) menu.append(SeparatorMenuItem()) menu.append(quit) menu.show_all() if sys.platform != "win32": pos_func = Gtk.StatusIcon.position_menu pos_arg = self._icon else: pos_func = pos_arg = None menu.popup(None, None, pos_func, pos_arg, button, time)
def __init__(self): super(SongList.RatingColumn, self).__init__("~#rating") self._update_layout(util.format_rating(1.0)) self.set_resizable(False) self.set_expand(False)