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