/usr/share/pyshared/quodlibet/plugins/songsmenu.py is in exfalso 2.3.2-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | # Copyright 2006 Joe Wreschnig
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation
import gtk
from quodlibet import qltk
from quodlibet.util import print_exc
from quodlibet.plugins import ListWrapper, Manager
class SongsMenuPlugin(gtk.ImageMenuItem):
"""Plugins of this type are subclasses of gtk.ImageMenuItem.
They will be added, in alphabetical order, to the "Plugins" menu
that appears when songs or lists of songs are right-clicked.
They provide one or more of the following instance methods:
self.plugin_single_song(song)
self.plugin_song(song)
self.plugin_songs(songs)
self.plugin_single_album(album)
self.plugin_album(album)
self.plugin_albums(albums)
All matching provided callables on a single object are called in the
above order if they match until one returns a true value. They are
not called with real AudioFile objects, but rather wrappers that
automatically detect metadata or disk changes, and save or reload
the files as appropriate.
The single_ variant is only called if a single song/album is selected.
The singular tense is called once for each selected song/album, but the
plural tense is called with a list of songs/albums.
An album is a list of songs all with the same album, labelid,
and/or musicbrainz_albumid tags (like in the Album List).
To make your plugin insensitive if unsupported songs are selected,
a method that takes a list of songs and returns True or False to set
the sensitivity of the menu entry:
self.plugin_handles(songs)
When these functions are called, the self.plugin_window will be
available. This is the gtk.Window the plugin was invoked from. This
provides access to two important widgets, self.plugin_window.browser
and self.plugin_window.songlist.
All of this is managed by the constructor for SongsMenuPlugin, so
make sure it gets called if you override it (you shouldn't have to).
"""
plugin_single_song = None
plugin_song = None
plugin_songs = None
plugin_single_album = None
plugin_album = None
plugin_albums = None
__initialized = False
def __init__(self, songs):
super(SongsMenuPlugin, self).__init__(self.PLUGIN_NAME)
self.__initialized = True
try: i = gtk.image_new_from_stock(self.PLUGIN_ICON, gtk.ICON_SIZE_MENU)
except AttributeError: pass
else: self.set_image(i)
self.set_sensitive(bool(self.plugin_handles(songs)))
@property
def initialized(self):
# If the GObject __init__ method is bypassed, it can cause segfaults.
# This explicitly prevents a bad plugin from taking down the app.
return self.__initialized
def plugin_handles(self, songs):
return True
class SongsMenuPlugins(Manager):
Kinds = [SongsMenuPlugin]
def Menu(self, library, parent, songs):
songs = ListWrapper(songs)
parent = qltk.get_top_parent(parent)
attrs = ['plugin_song', 'plugin_songs',
'plugin_album', 'plugin_albums']
if len(songs) == 1: attrs.append('plugin_single_song')
last = (songs and songs[-1]) or None
for song in songs:
if song.album_key != last.album_key:
break
last = song
else:
attrs.append('plugin_single_album')
items = []
kinds = self.find_subclasses(SongsMenuPlugin)
kinds.sort(key=lambda plugin: plugin.PLUGIN_ID)
for Kind in kinds:
usable = max([callable(getattr(Kind, s)) for s in attrs])
if usable:
try: items.append(Kind(songs))
except: print_exc()
items = filter(lambda i: i.initialized, items)
if items:
menu = gtk.Menu()
for item in items:
try:
menu.append(item)
args = (library, parent, songs)
if item.get_submenu():
for subitem in item.get_submenu().get_children():
subitem.connect_object(
'activate', self.__handle, item, *args)
else:
item.connect('activate', self.__handle, *args)
except:
print_exc()
item.destroy()
else: menu = None
return menu
def __get_albums(self, songs):
albums = {}
for song in songs:
key = song.album_key
if key not in albums:
albums[key] = []
albums[key].append(song)
albums = albums.values()
map(list.sort, albums)
return albums
def __handle(self, plugin, library, parent, songs):
if len(songs) == 0: return
plugin.plugin_window = parent
try:
if len(songs) == 1 and callable(plugin.plugin_single_song):
try: ret = plugin.plugin_single_song(songs[0])
except Exception: print_exc()
else:
if ret: return
if callable(plugin.plugin_song):
try: ret = map(plugin.plugin_song, songs)
except Exception: print_exc()
else:
if max(ret): return
if callable(plugin.plugin_songs):
try: ret = plugin.plugin_songs(songs)
except Exception: print_exc()
else:
if ret: return
if max(map(callable,(plugin.plugin_single_album,
plugin.plugin_album, plugin.plugin_albums))):
albums = self.__get_albums(songs)
if callable(plugin.plugin_single_album) and len(albums) == 1:
try: ret = plugin.plugin_single_album(albums[0])
except Exception: print_exc()
else:
if ret: return
if callable(plugin.plugin_album):
try: ret = map(plugin.plugin_album, albums)
except Exception: print_exc()
else:
if max(ret): return
if callable(plugin.plugin_albums):
try: ret = plugin.plugin_albums(albums)
except Exception: print_exc()
else:
if ret: return
finally:
del(plugin.plugin_window)
self._check_change(library, parent, filter(None, songs))
|