0001"""Common plugins for Louie."""
0002
0003from louie import dispatcher
0004from louie import error
0005
0006
0007def install_plugin(plugin):
0008    cls = plugin.__class__
0009    for p in dispatcher.plugins:
0010        if p.__class__ is cls:
0011            raise error.PluginTypeError(
0012                'Plugin of type %r already installed.' % cls)
0013    dispatcher.plugins.append(plugin)
0014
0015def remove_plugin(plugin):
0016    dispatcher.plugins.remove(plugin)
0017
0018
0019class Plugin(object):
0020    """Base class for Louie plugins.
0021
0022    Plugins are used to extend or alter the behavior of Louie
0023    in a uniform way without having to modify the Louie code
0024    itself.
0025    """
0026
0027    def is_live(self, receiver):
0028        """Return True if the receiver is still live.
0029
0030        Only called for receivers who have already been determined to
0031        be live by default Louie semantics.
0032        """
0033        return True
0034
0035    def wrap_receiver(self, receiver):
0036        """Return a callable that passes arguments to the receiver.
0037
0038        Useful when you want to change the behavior of all receivers.
0039        """
0040        return receiver
0041
0042
0043class QtWidgetPlugin(Plugin):
0044    """A Plugin for Louie that knows how to handle Qt widgets
0045    when using PyQt built with SIP 4 or higher.
0046
0047    Weak references are not useful when dealing with QWidget
0048    instances, because even after a QWidget is closed and destroyed,
0049    only the C++ object is destroyed.  The Python 'shell' object
0050    remains, but raises a RuntimeError when an attempt is made to call
0051    an underlying QWidget method.
0052
0053    This plugin alleviates this behavior, and if a QWidget instance is
0054    found that is just an empty shell, it prevents Louie from
0055    dispatching to any methods on those objects.
0056    """
0057
0058    def __init__(self):
0059        try:
0060            import qt
0061        except ImportError:
0062            self.is_live = self._is_live_no_qt
0063        else:
0064            self.qt = qt
0065
0066    def is_live(self, receiver):
0067        """If receiver is a method on a QWidget, only return True if
0068        it hasn't been destroyed."""
0069        if (hasattr(receiver, 'im_self') and
0070            isinstance(receiver.im_self, self.qt.QWidget)
0071            ):
0072            try:
0073                receiver.im_self.x()
0074            except RuntimeError:
0075                return False
0076        return True
0077
0078    def _is_live_no_qt(self, receiver):
0079        return True
0080
0081
0082class TwistedDispatchPlugin(Plugin):
0083    """Plugin for Louie that wraps all receivers in callables
0084    that return Twisted Deferred objects.
0085
0086    When the wrapped receiver is called, it adds a call to the actual
0087    receiver to the reactor event loop, and returns a Deferred that is
0088    called back with the result.
0089    """
0090
0091    def __init__(self):
0092        # Don't import reactor ourselves, but make access to it
0093        # easier.
0094        from twisted import internet
0095        from twisted.internet.defer import Deferred
0096        self._internet = internet
0097        self._Deferred = Deferred
0098
0099    def wrap_receiver(self, receiver):
0100        def wrapper(*args, **kw):
0101            d = self._Deferred()
0102            def called(dummy):
0103                return receiver(*args, **kw)
0104            d.addCallback(called)
0105            self._internet.reactor.callLater(0, d.callback, None)
0106            return d
0107        return wrapper