Plasma::Applet's protected methods hurt the scripting API

Richard Dale richard.j.dale at gmail.com
Mon Jun 30 13:05:46 CEST 2008


On Fri, Jun 27, 2008 at 6:23 PM, Paul Giannaros <paul at giannaros.org> wrote:

> Hello,
>
> I've scratched my own itch and written a script engine for Python
> using sip/PyQt/PyKDE; I wanted to write a couple of applets in Python
> and the Kross bindings wouldn't work, appeared outdated, and didn't
> provide the kind of API that I think Python users would appreciate
> (operating on a global applet object isn't the nicest thing in the
> world when your applet does something interesting).
> I have hit an irritating brick wall, though. My MPD cover art applet
> is perfect, except that I don't like its current background -- I'd
> much rather it had a translucent background.
> Plasma::Applet::setBackgroundHints is a protected method. I cannot
> touch it from Python, nor from my AppletScript subclass (class
> friendship is not inherited). I'm stuck.
> There are plenty of other important things cannot be called:
> setIsContainment; setConfigurationRequired; setFailedToLaunch.

In the Ruby script engine based bindings I have the underlying C++applet in
the Plasma::AppletScript class wrapped as a Ruby instance. If any method
calls made on the the scripting applet instance are found to be missing they
are then forwarded to the underlying applet.

I assume that PyQt works the same way as QtRuby and that SIP will generate a
C++ subclass of Plasma::Applet for supporting python with the protected
methods mapped onto public. ones which are callable from python.

In Ruby, the code to do the message forwarding looks like this:

module PlasmaScripting
  class Applet < Qt::Object
    ...

    # If a method is called on a PlasmaScripting::Applet instance is found
to be missing
    # then try calling the method on the underlying Plasma::Applet in the
ScriptEngine.
    def method_missing(method, *args)
      begin
        super(method, *args)
      rescue
        applet_script.applet.method_missing(method, *args)
      end
    end

 I don't know that you can do that in python, and maybe you would have to
explicitely call each
method like setConfigurationRequired() that you wanted to call.

These calls all work fine in Ruby:

      setAcceptDrops(true)
      setBackgroundHints(Plasma::Applet::NoBackground)
      setHasConfigurationInterface(true)
      setIsContainment(false)
      setConfigurationRequired(false)
      setFailedToLaunch(false)


I also
> cannot easily handle resize events at my leisure, mouse presses, mouse
> moves, etc. I guess that one could install an event filter and create
> an intercepting QObject subclass, but that's not going to make for
> nice code at all.

That is exactly what I've done, but from the point of view of the applet
application programmer, it looks exactly the same. You can hide the event
filtering code, and call event handlers like mousePressEvent() from within
the event filter.

In Ruby the code looks like this. I keep a Hash of event types against
methods to handle them in @event_handlers. I use Ruby introspection when the
applet is initialized to find which event handlers the applet author has
implemented, if any:

      if @applet_script.respond_to?(:mousePressEvent)
        @event_handlers[Qt::Event::GraphicsSceneMousePress.to_i] =
:mousePressEvent
      end

Then I can quickly retrieve the name of the event handler given the event
type and call it with send():

    def eventFilter(obj, event)
      handler = @event_handlers[event.type.to_i]
      if handler
        @applet_script.send(handler, event)
        return true
      else
        return false
      end
    end

A further issue to be considered is how to call methods in the api which
expect a QGraphicsWidget or a QGraphicsLayoutItem, when the script engine
applet isn't really a Plasma::Applet. So I've intervened in the api calls
and whenever I find an instance of the script applet, I substitute the
instance of the applet from the script engine instead. For instance, to make
this code work:

      @layout = Qt::GraphicsLinearLayout.new(Qt::Vertical, self)

      @button = Plasma::PushButton.new(self)
      @button.text = "Hello world"
      connect(@button, SIGNAL(:clicked), self, SLOT(:button_pressed))

      @label = Plasma::Label.new(self)
      @label.text = "My Label"

      @layout.addItem(@button)
      @layout.addItem(@label)

'self' is expected to be a kind of Plasma::Applet, and so in the
Plasma::PushButton constructor I change the way it is called like this:

  class PushButton < Qt::Base
    def initialize(parent = nil)
      if parent.kind_of?(PlasmaScripting::Applet)
        super(parent.applet_script.applet)
      else
        super
      end
    end
  end



> Is it intentional that non-native applets are this crippled, or were
> tasks as fundamental as these just not considered?

 I've implemented both complete bindings for the C++ api, and a ScriptEngine
version using techniques like the ones described above to make it pretty
well the same as the C++ api. I'm now pretty happy than 95% of users
requirements are going to be met by the ScriptEngine version. If you really
want to write a containment in Ruby which can't be done with the
ScriptEngine api, you still can, and we'll have wait to see exactly how
common that sort of requirement is going to be.

-- Richard
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.kde.org/pipermail/panel-devel/attachments/20080630/d75936d1/attachment-0001.html 


More information about the Panel-devel mailing list