1# Copyright 2014, 2016 Nick Boultbee 2# 2015 Christoph Reiter 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License as published by 6# the Free Software Foundation; either version 2 of the License, or 7# (at your option) any later version. 8 9from quodlibet import config, print_d, app 10from quodlibet.plugins import PluginHandler 11from quodlibet.qltk import get_menu_item_top_parent 12from quodlibet.qltk import Icons 13from gi.repository import Gtk 14 15 16class UserInterfacePlugin(object): 17 """Plugins that provide a (Gtk+ Widget) 18 to display as a side bar (currently) in the main Quod Libet Window. 19 20 These can be combined well with an EventPlugin to listen for 21 current song or selection changes. 22 23 TODO: generalise this better. See #152, #2273, #1991. 24 """ 25 26 PLUGIN_INSTANCE = True 27 28 def create_sidebar(self): 29 """If defined, returns a Gtk.Box to populate the sidebar""" 30 pass 31 32 33class UserInterfacePluginHandler(PluginHandler): 34 def __init__(self): 35 36 self.__plugins = {} 37 self.__sidebars = {} 38 39 def plugin_handle(self, plugin): 40 return issubclass(plugin.cls, UserInterfacePlugin) 41 42 def plugin_enable(self, plugin): 43 self.__plugins[plugin.cls] = pl_obj = plugin.get_instance() 44 sidebar = pl_obj.create_sidebar() 45 app.window.hide_side_book() 46 if sidebar: 47 print_d("Enabling sidebar for %s" % plugin.cls) 48 self.__sidebars[plugin] = app.window.add_sidebar( 49 sidebar, name=plugin.name) 50 sidebar.show_all() 51 52 def plugin_disable(self, plugin): 53 widget = self.__sidebars.get(plugin) 54 if widget: 55 print_d("Removing sidebar %s" % widget) 56 app.window.remove_sidebar(widget) 57 self.__plugins.pop(plugin.cls) 58 59 60class MenuItemPlugin(Gtk.ImageMenuItem): 61 """ 62 A base plugin that appears in a menu, typically. 63 64 During plugin callbacks, `self.plugin_window` will be 65 available. This is the `Gtk.Window` that the plugin was invoked from. 66 It provides access to two important widgets, `self.plugin_window.browser` 67 and `self.plugin_window.songlist`. 68 """ 69 70 MAX_INVOCATIONS = config.getint("plugins", "default_max_invocations", 30) 71 """An upper limit on how many instances of the plugin should be launched 72 at once without warning. Heavyweight plugins should override this value 73 to prevent users killing their performance by opening on many songs.""" 74 75 REQUIRES_ACTION = False 76 """This plugin will run a user interface first (e.g. dialog) requiring 77 action from the user. The menu entry may be altered accordingly""" 78 79 def __init__(self): 80 label = self.PLUGIN_NAME + ("…" if self.REQUIRES_ACTION else "") 81 super(Gtk.ImageMenuItem, self).__init__(label=label) 82 self.__set_icon() 83 self.__initialized = True 84 85 @property 86 def plugin_window(self): 87 return get_menu_item_top_parent(self) 88 89 def __set_icon(self): 90 """Sets the GTK icon for this plugin item""" 91 icon = getattr(self, "PLUGIN_ICON", Icons.SYSTEM_RUN) 92 93 image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU) 94 self.set_always_show_image(True) 95 self.set_image(image) 96 97 @property 98 def initialized(self): 99 # If the GObject __init__ method is bypassed, it can cause segfaults. 100 # This explicitly prevents a bad plugin from taking down the app. 101 return self.__initialized 102