def setUp(self): config.init() self.h = SongsMenuPluginHandler() library = SongLibrary() library.librarian = SongLibrarian() self.lib = library self.parent = Gtk.Window()
def setUp(self): self.tempdir = mkdtemp() self.pm = PluginManager(folders=[self.tempdir]) self.confirmed = False self.handler = SongsMenuPluginHandler(self._confirmer, self._confirmer) self.pm.register_handler(self.handler) self.pm.rescan() self.assertEquals(self.pm.plugins, []) self.library = SongLibrary('foo')
class TSongsMenuPlugins(TestCase): def _confirmer(self, *args): self.confirmed = True def setUp(self): self.tempdir = mkdtemp() self.pm = PluginManager(folders=[self.tempdir]) self.confirmed = False self.handler = SongsMenuPluginHandler(self._confirmer, self._confirmer) self.pm.register_handler(self.handler) self.pm.rescan() self.assertEquals(self.pm.plugins, []) self.library = SongLibrary('foo') def tearDown(self): self.library.destroy() self.pm.quit() for f in os.listdir(self.tempdir): os.remove(os.path.join(self.tempdir, f)) os.rmdir(self.tempdir) def create_plugin(self, id='', name='', desc='', icon='', funcs=None, mod=False): fd, fn = mkstemp(suffix='.py', text=True, dir=self.tempdir) file = os.fdopen(fd, 'w') if mod: indent = '' else: file.write( "from quodlibet.plugins.songsmenu import SongsMenuPlugin\n") file.write("class %s(SongsMenuPlugin):\n" % name) indent = ' ' file.write("%spass\n" % indent) if name: file.write("%sPLUGIN_ID = %r\n" % (indent, name)) if name: file.write("%sPLUGIN_NAME = %r\n" % (indent, name)) if desc: file.write("%sPLUGIN_DESC = %r\n" % (indent, desc)) if icon: file.write("%sPLUGIN_ICON = %r\n" % (indent, icon)) for f in (funcs or []): if f in ["__init__"]: file.write("%sdef %s(self, *args): super(%s, self).__init__(" "*args); raise Exception(\"as expected\")\n" % (indent, f, name)) else: file.write("%sdef %s(*args): return args\n" % (indent, f)) file.flush() file.close() def test_empty_has_no_plugins(self): self.pm.rescan() self.assertEquals(self.pm.plugins, []) def test_name_and_desc_plus_func_is_one(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song']) self.pm.rescan() self.assertEquals(len(self.pm.plugins), 1) def test_additional_functions_still_only_one(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song', 'plugin_songs']) self.pm.rescan() self.assertEquals(len(self.pm.plugins), 1) def test_two_plugins_are_two(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song']) self.create_plugin(name='Name2', desc='Desc2', funcs=['plugin_albums']) self.pm.rescan() self.assertEquals(len(self.pm.plugins), 2) def test_disables_plugin(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song']) self.pm.rescan() self.failIf(self.pm.enabled(self.pm.plugins[0])) def test_enabledisable_plugin(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song']) self.pm.rescan() plug = self.pm.plugins[0] self.pm.enable(plug, True) self.failUnless(self.pm.enabled(plug)) self.pm.enable(plug, False) self.failIf(self.pm.enabled(plug)) def test_ignores_broken_plugin(self): self.create_plugin(name="Broken", desc="Desc", funcs=["__init__", "plugin_song"]) self.pm.rescan() plug = self.pm.plugins[0] self.pm.enable(plug, True) with capture_output(): menu = self.handler.Menu(None, [AudioFile()]) self.failIf(menu and menu.get_children()) def test_Menu(self): self.create_plugin(name='Name', desc='Desc', funcs=['plugin_song']) self.handler.Menu(None, [AudioFile()]) def test_handling_songs_without_confirmation(self): plugin = Plugin(FakeSongsMenuPlugin) self.handler.plugin_enable(plugin) MAX = FakeSongsMenuPlugin.MAX_INVOCATIONS songs = [AudioFile({'~filename': "/tmp/%s" % x, 'artist': 'foo'}) for x in range(MAX)] self.handler.handle(plugin.id, self.library, None, songs) self.failIf(self.confirmed, ("Wasn't expecting a confirmation for %d" " invocations" % len(songs))) def test_handling_lots_of_songs_with_confirmation(self): plugin = Plugin(FakeSongsMenuPlugin) self.handler.plugin_enable(plugin) MAX = FakeSongsMenuPlugin.MAX_INVOCATIONS songs = [AudioFile({'~filename': "/tmp/%s" % x, 'artist': 'foo'}) for x in range(MAX + 1)] self.handler.handle(plugin.id, self.library, None, songs) self.failUnless(self.confirmed, ("Should have confirmed %d invocations (Max=%d)." % (len(songs), MAX)))
class ExFalsoWindow(Window, PersistentWindowMixin): __gsignals__ = { 'changed': (GObject.SignalFlags.RUN_LAST, None, (object, )), } pm = SongsMenuPluginHandler() @classmethod def init_plugins(cls): PluginManager.instance.register_handler(cls.pm) def __init__(self, library, dir=None): super(ExFalsoWindow, self).__init__(dialog=False) self.set_title("Ex Falso") self.set_default_size(750, 475) self.enable_window_tracking("exfalso") self.__library = library hp = ConfigRHPaned("memory", "exfalso_paned_position", 1.0) hp.set_border_width(0) hp.set_position(250) hp.show() self.add(hp) vb = Gtk.VBox() bbox = Gtk.HBox(spacing=6) about = Gtk.Button() about.add( Gtk.Image.new_from_icon_name(Icons.HELP_ABOUT, Gtk.IconSize.BUTTON)) connect_obj(about, 'clicked', self.__show_about, self) bbox.pack_start(about, False, True, 0) def prefs_cb(*args): window = PreferencesWindow(self) window.show() def plugin_window_cb(*args): window = PluginWindow(self) window.show() menu = Gtk.Menu() plugin_item = MenuItem(_("_Plugins"), Icons.SYSTEM_RUN) plugin_item.connect("activate", plugin_window_cb) menu.append(plugin_item) pref_item = MenuItem(_("_Preferences"), Icons.PREFERENCES_SYSTEM) pref_item.connect("activate", prefs_cb) menu.append(pref_item) menu.show_all() menu_button = MenuButton(SymbolicIconImage(Icons.EMBLEM_SYSTEM, Gtk.IconSize.BUTTON), arrow=True, down=False) menu_button.set_menu(menu) bbox.pack_start(menu_button, False, True, 0) l = Gtk.Label() l.set_alignment(1.0, 0.5) l.set_ellipsize(Pango.EllipsizeMode.END) bbox.pack_start(l, True, True, 0) fs = MainFileSelector() vb.pack_start(fs, True, True, 0) vb.pack_start(Align(bbox, border=6), False, True, 0) vb.show_all() hp.pack1(vb, resize=True, shrink=False) nb = qltk.Notebook() nb.props.scrollable = True nb.show() for Page in [EditTags, TagsFromPath, RenameFiles, TrackNumbers]: page = Page(self, self.__library) page.show() nb.append_page(page) hp.pack2(nb, resize=True, shrink=False) fs.connect('changed', self.__changed, l) if dir: fs.go_to(dir) connect_destroy(self.__library, 'changed', self.__library_changed, fs) self.__save = None connect_obj(self, 'changed', self.set_pending, None) for c in fs.get_children(): c.get_child().connect('button-press-event', self.__pre_selection_changed, fs, nb) c.get_child().connect('focus', self.__pre_selection_changed, fs, nb) fs.get_children()[1].get_child().connect('popup-menu', self.__popup_menu, fs) self.emit('changed', []) self.get_child().show() self.__ag = Gtk.AccelGroup() key, mod = Gtk.accelerator_parse("<Primary>Q") self.__ag.connect(key, mod, 0, lambda *x: self.destroy()) self.add_accel_group(self.__ag) # GtkosxApplication assumes the menu bar is mapped, so add # it but don't show it. self._dummy_osx_menu_bar = Gtk.MenuBar() vb.pack_start(self._dummy_osx_menu_bar, False, False, 0) def __library_changed(self, library, songs, fs): fs.rescan() def set_as_osx_window(self, osx_app): osx_app.set_menu_bar(self._dummy_osx_menu_bar) def get_osx_is_persistent(self): return False def __show_about(self, window): about = AboutExFalso(self) about.run() about.destroy() def set_pending(self, button, *excess): self.__save = button def __pre_selection_changed(self, view, event, fs, nb): if self.__save: resp = qltk.CancelRevertSave(self).run() if resp == Gtk.ResponseType.YES: self.__save.clicked() elif resp == Gtk.ResponseType.NO: fs.rescan() else: nb.grab_focus() return True # cancel or closed def __popup_menu(self, view, fs): # get all songs for the selection filenames = [ normalize_path(f, canonicalise=True) for f in fs.get_selected_paths() ] maybe_songs = [self.__library.get(f) for f in filenames] songs = [s for s in maybe_songs if s] if songs: menu = self.pm.Menu(self.__library, songs) if menu is None: menu = Gtk.Menu() else: menu.prepend(SeparatorMenuItem()) else: menu = Gtk.Menu() b = TrashMenuItem() b.connect('activate', self.__delete, filenames, fs) menu.prepend(b) def selection_done_cb(menu): menu.destroy() menu.connect('selection-done', selection_done_cb) menu.show_all() return view.popup_menu(menu, 0, Gtk.get_current_event_time()) def __delete(self, item, paths, fs): trash_files(self, paths) fs.rescan() def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(ngettext("%d song", "%d songs", count) % count) for row in rows: filename = model[row][0] if not os.path.exists(filename): pass elif filename in self.__library: song = self.__library[filename] if song("~#mtime") + 1. < mtime(filename): try: song.reload() except Exception: pass files.append(song) else: files.append(formats.MusicFile(filename)) files = filter(None, files) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: self.set_title("%s - Ex Falso" % (ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(files) - 1) % ({ 'title': files[0].comma("title"), 'count': len(files) - 1 }))) self.__library.add(files) self.emit('changed', files)
class ExFalsoWindow(Gtk.Window, PersistentWindowMixin): __gsignals__ = { 'changed': (GObject.SignalFlags.RUN_LAST, None, (object, )), 'artwork-changed': (GObject.SignalFlags.RUN_LAST, None, (object, )) } pm = SongsMenuPluginHandler(confirm_action) @classmethod def init_plugins(cls): PluginManager.instance.register_handler(cls.pm) def __init__(self, library, dir=None): super(ExFalsoWindow, self).__init__() self.set_title("Ex Falso") self.set_default_size(750, 475) self.enable_window_tracking("exfalso") self.__library = library hp = ConfigRHPaned("memory", "exfalso_paned_position", 1.0) hp.set_border_width(0) hp.set_position(250) hp.show() self.add(hp) vb = Gtk.VBox() bbox = Gtk.HBox(spacing=6) about = Gtk.Button() about.add( Gtk.Image.new_from_stock(Gtk.STOCK_ABOUT, Gtk.IconSize.BUTTON)) about.connect_object('clicked', self.__show_about, self) bbox.pack_start(about, False, True, 0) prefs = Gtk.Button() prefs.add( Gtk.Image.new_from_stock(Gtk.STOCK_PREFERENCES, Gtk.IconSize.BUTTON)) def prefs_cb(button): window = PreferencesWindow(self) window.show() prefs.connect('clicked', prefs_cb) bbox.pack_start(prefs, False, True, 0) plugins = qltk.Button(_("_Plugins"), Gtk.STOCK_EXECUTE) def plugin_window_cb(button): window = PluginWindow(self) window.show() plugins.connect('clicked', plugin_window_cb) bbox.pack_start(plugins, False, True, 0) l = Gtk.Label() l.set_alignment(1.0, 0.5) l.set_ellipsize(Pango.EllipsizeMode.END) bbox.pack_start(l, True, True, 0) fs = MainFileSelector() vb.pack_start(fs, True, True, 0) vb.pack_start(Alignment(bbox, border=6), False, True, 0) vb.show_all() hp.pack1(vb, resize=True, shrink=False) nb = qltk.Notebook() nb.show() for Page in [EditTags, TagsFromPath, RenameFiles, TrackNumbers]: page = Page(self, self.__library) page.show() nb.append_page(page) align = Alignment(nb, top=3) align.show() hp.pack2(align, resize=True, shrink=False) fs.connect('changed', self.__changed, l) if dir: fs.go_to(dir) s = self.__library.connect('changed', lambda *x: fs.rescan()) self.connect_object('destroy', self.__library.disconnect, s) self.__save = None self.connect_object('changed', self.set_pending, None) for c in fs.get_children(): c.get_child().connect('button-press-event', self.__pre_selection_changed, fs, nb) c.get_child().connect('focus', self.__pre_selection_changed, fs, nb) fs.get_children()[1].get_child().connect('popup-menu', self.__popup_menu, fs) self.emit('changed', []) self.get_child().show() self.__ag = Gtk.AccelGroup() key, mod = Gtk.accelerator_parse("<control>Q") self.__ag.connect(key, mod, 0, lambda *x: self.destroy()) self.add_accel_group(self.__ag) def __show_about(self, window): about = AboutExFalso(self) about.run() about.destroy() def set_pending(self, button, *excess): self.__save = button def __pre_selection_changed(self, view, event, fs, nb): if self.__save: resp = qltk.CancelRevertSave(self).run() if resp == Gtk.ResponseType.YES: self.__save.clicked() elif resp == Gtk.ResponseType.NO: fs.rescan() else: nb.grab_focus() return True # cancel or closed def __popup_menu(self, view, fs): # get all songs for the selection filenames = [ normalize_path(f, canonicalise=True) for f in fs.get_selected_paths() ] maybe_songs = [self.__library.get(f) for f in filenames] songs = [s for s in maybe_songs if s] if songs: menu = self.pm.Menu(self.__library, self, songs) if menu is None: menu = Gtk.Menu() else: menu.prepend(SeparatorMenuItem()) else: menu = Gtk.Menu() b = TrashMenuItem() b.connect('activate', self.__delete, filenames, fs) menu.prepend(b) menu.connect_object('selection-done', Gtk.Menu.destroy, menu) menu.show_all() return view.popup_menu(menu, 0, Gtk.get_current_event_time()) def __delete(self, item, paths, fs): trash_files(self, paths) fs.rescan() def __changed(self, selector, selection, label): model, rows = selection.get_selected_rows() files = [] if len(rows) < 2: count = len(model or []) else: count = len(rows) label.set_text(ngettext("%d song", "%d songs", count) % count) for row in rows: filename = util.fsnative(model[row][0]) if not os.path.exists(filename): pass elif filename in self.__library: file = self.__library[filename] if file("~#mtime") + 1. < mtime(filename): try: file.reload() except StandardError: pass files.append(file) else: files.append(formats.MusicFile(filename)) files = filter(None, files) if len(files) == 0: self.set_title("Ex Falso") elif len(files) == 1: self.set_title("%s - Ex Falso" % files[0].comma("title")) else: self.set_title("%s - Ex Falso" % (ngettext( "%(title)s and %(count)d more", "%(title)s and %(count)d more", len(files) - 1) % ({ 'title': files[0].comma("title"), 'count': len(files) - 1 }))) self.__library.add(files) self.emit('changed', files)
class TPluginsSongsMenu(PluginTestCase): def setUp(self): config.init() self.h = SongsMenuPluginHandler() library = SongLibrary() library.librarian = SongLibrarian() self.lib = library self.parent = Gtk.Window() def tearDown(self): self.lib.destroy() self.parent.destroy() config.quit() def test_init(self): for id_, plugin in self.plugins.items(): if self.h.plugin_handle(plugin): self.h.plugin_enable(plugin) self.h.handle(id_, None, None, []) self.h.plugin_disable(plugin) def test_handle_single(self): self.skipTest("Pops up windows and needs user input.. so disabled." "Still worth keeping whilst we don't have unit tests " "for all plugins.") # Ignored... for id_, plugin in self.plugins.items(): if self.h.plugin_handle(plugin): self.h.plugin_enable(plugin, None) self.h.handle(id_, self.lib, self.parent, SONGS) self.h.plugin_disable(plugin) def test_handles_albums(self): for id_, plugin in self.plugins.items(): if isinstance(plugin, SongsMenuPlugin): ha = plugin.handles_albums self.failIf(hasattr(plugin, "plugin_single_album") and not ha) self.failIf(hasattr(plugin, "plugin_plugin_album") and not ha) self.failIf(hasattr(plugin, "plugin_albums") and not ha)
class TPluginsSongsMenu(PluginTestCase): def setUp(self): config.init() self.h = SongsMenuPluginHandler() library = SongLibrary() library.librarian = SongLibrarian() self.lib = library self.parent = Gtk.Window() def tearDown(self): self.lib.destroy() self.parent.destroy() config.quit() def test_init(self): for id_, plugin in self.plugins.iteritems(): if self.h.plugin_handle(plugin): self.h.plugin_enable(plugin) self.h.handle(id_, None, None, []) self.h.plugin_disable(plugin) def test_handle_single(self): self.skipTest("Pops up windows and needs user input.. so disabled." "Still worth keeping whilst we don't have unit tests " "for all plugins.") # Ignored... for id_, plugin in self.plugins.iteritems(): if self.h.plugin_handle(plugin): self.h.plugin_enable(plugin, None) self.h.handle(id_, self.lib, self.parent, SONGS) self.h.plugin_disable(plugin) def test_handles_albums(self): for id_, plugin in self.plugins.iteritems(): if isinstance(plugin, SongsMenuPlugin): ha = plugin.handles_albums self.failIf(hasattr(plugin, "plugin_single_album") and not ha) self.failIf(hasattr(plugin, "plugin_plugin_album") and not ha) self.failIf(hasattr(plugin, "plugin_albums") and not ha)