Development/Tutorials/Kross/Scripts-as-Plugins: Difference between revisions

From KDE TechBase
(tutorial++)
(properly highlight the python, ruby, and cpp snippets using <code>; link to the {{qt}} classes)
Line 5: Line 5:
==The C++ code==
==The C++ code==


The following C++ code demonstrates how to execute scripting code in C++ and how to let scripting code deal with QObject instances.
The following C++ code demonstrates how to execute scripting code in C++ and how to let scripting code deal with {{qt|QObject}} instances.


For this we define the MyObject class that implements some signals and slots we will access from within scripting code. Scripts are able to access such QObject's as there are classinstances, call slots as they are memberfunctions, get and set properties as there are membervariables and connect scripting functions with signals.
For this we define the MyObject class that implements some signals and slots we will access from within scripting code. Scripts are able to access such {{qt|QObject}}'s as there are classinstances, call slots as they are memberfunctions, get and set properties as there are membervariables and connect scripting functions with signals.


<pre>
<code cppqt>
#include <QObject>
#include <QObject>
#include <QTimer>
#include <QTimer>
Line 66: Line 66:
   action->trigger();
   action->trigger();
}
}
</pre>
</code>


The execScriptFile function does create an instance of [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/action.h?view=markup Kross::Action] that is used as abstract container to deal with scripts / script files.
The execScriptFile function does create an instance of [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/action.h?view=markup Kross::Action] that is used as abstract container to deal with scripts / script files.


We then add our myobject instance to the action. That way scripting code is able to access the publish QObject instance, call it slots and connect with the signals.
We then add our myobject instance to the action. That way scripting code is able to access the publish {{qt|QObject}} instance, call it slots and connect with the signals.


Cause [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/childreninterface.h?view=markup Kross::ChildrenInterface::AutoConnectSignals] is defined, the init() and the update() signals the myobject instance provides will be automaticaly connected to matching scripting functions.
Cause [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/childreninterface.h?view=markup Kross::ChildrenInterface::AutoConnectSignals] is defined, the init() and the update() signals the myobject instance provides will be automaticaly connected to matching scripting functions.
Line 77: Line 77:


Finally the script is executed. This is done by triggering the action. Once executed you are also able to use [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/errorinterface.h?view=markup Kross::ErrorInterface] to check if the action was executed successfully like demonstrate below.
Finally the script is executed. This is done by triggering the action. Once executed you are also able to use [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/errorinterface.h?view=markup Kross::ErrorInterface] to check if the action was executed successfully like demonstrate below.
<pre>
<code cppqt>
   if( action->hadError() )
   if( action->hadError() )
     kDebug() << action->errorMessage() << endl;
     kDebug() << action->errorMessage() << endl;
</pre>
</code>
The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/manager.h?view=markup Kross::Manager] provides also the option to connect with the started and finished signals that got emitted if a script got executed.
The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/manager.h?view=markup Kross::Manager] provides also the option to connect with the started and finished signals that got emitted if a script got executed.
<pre>
<code cppqt>
connect(&Kross::Manager::self(), SIGNAL( started(Kross::Action*) ),
connect(&Kross::Manager::self(), SIGNAL( started(Kross::Action*) ),
         this, SLOT( started(Kross::Action*) ));
         this, SLOT( started(Kross::Action*) ));
connect(&Kross::Manager::self(), SIGNAL( finished(Kross::Action*) ),
connect(&Kross::Manager::self(), SIGNAL( finished(Kross::Action*) ),
         this, SLOT( finished(Kross::Action*) ));
         this, SLOT( finished(Kross::Action*) ));
</pre>
</code>


The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/actioncollection.h?view=markup Kross::ActionCollection] class manages collections of [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/action.h?view=markup Kross::Action] instances, enables hierachies and implements serializing from/to XML.
The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/actioncollection.h?view=markup Kross::ActionCollection] class manages collections of [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/action.h?view=markup Kross::Action] instances, enables hierachies and implements serializing from/to XML.
Line 95: Line 95:
The following Python script demonstrates how a Python plugin looks like. The init() and the update() functions will be called if the matching signals at the myobject instance are emitted.
The following Python script demonstrates how a Python plugin looks like. The init() and the update() functions will be called if the matching signals at the myobject instance are emitted.


First we import myobject module. This module provides us access to the MyObject QObject instance and it's slots, signals and properties. Within the init() Python function we set the interval to 2000 milliseconds = 2 seconds. The QTimer our MyObject instance starts till emit the update() signal then 2 seconds later what in turn calls our update() Python function.
First we import myobject module. This module provides us access to the MyObject {{qt|QObject}} instance and it's slots, signals and properties. Within the init() Python function we set the interval to 2000 milliseconds = 2 seconds. The {{qt|QTimer}} our MyObject instance starts till emit the update() signal then 2 seconds later what in turn calls our update() Python function.


<pre>
<code python>
import myobject
import myobject


Line 105: Line 105:
def update():
def update():
   print "interval=%i" % myobject.interval()
   print "interval=%i" % myobject.interval()
</pre>
</code>


==The Ruby scripting code==
==The Ruby scripting code==
Line 111: Line 111:
The following Ruby script does the same as the Python scripting code above except by using the Ruby scripting language. This shows, that the same rich API functionality is accessible independend of the used scripting language.
The following Ruby script does the same as the Python scripting code above except by using the Ruby scripting language. This shows, that the same rich API functionality is accessible independend of the used scripting language.


<pre>
<code ruby>
require 'myobject'
require 'myobject'


Line 121: Line 121:
   puts "interval=%i" % Myobject.interval()
   puts "interval=%i" % Myobject.interval()
end
end
</pre>
</code>

Revision as of 11:08, 28 May 2007

Introduction

This tutorial provides a step-by-step introduction how to integrate scripts as plugins into a C++/Qt/KDE application. That way an application can be extended with plugins written in scripting languages like Python, Ruby and KDE JavaScript.

The C++ code

The following C++ code demonstrates how to execute scripting code in C++ and how to let scripting code deal with QObject instances.

For this we define the MyObject class that implements some signals and slots we will access from within scripting code. Scripts are able to access such QObject's as there are classinstances, call slots as they are memberfunctions, get and set properties as there are membervariables and connect scripting functions with signals.

  1. include <QObject>
  2. include <QTimer>
  3. include <kross/core/action.h>

// This is our QObject our scripting code will access class MyObject : public QObject {

 public:
   MyObject(QObject* parent)
     : QObject(parent), m_timer(new QTimer(this))
   {
     // Calls the init() scripting function if available.
     emit init();
     // On timeout call the update() scripting function
     // if available.
     connect(m_timer, SIGNAL(timeout()), SIGNAL(update()));
     // Start the timer.
     m_timer->start();
   }
   virtual ~MyObject() {}
 Q_SLOTS:
   // Return the timers interval in milliseconds
   int interval() const { return m_timer.interval(); }
   // Set the timers interval in milliseconds
   void setInterval(int ms) { m_timer.setInterval(ms); }
 Q_SIGNAL:
   // If emitted calls the init() scripting function
   // if available.
   void init();
   // If emitted calls the update() scripting function
   // if available.
   void update();
 private:
   Q_Timer* m_timer;

};

// Execute a script file. static void execScriptFile(MyObject* myobject, const QString& file) {

 // Create the script container. myobject is the parent QObject,
 // so that our action instance will be destroyed once the myobject
 // is destroyed.
 Kross::Action* action = new Kross::Action(myobject, file);
 // Publish our myobject instance and connect signals with
 // scripting functions.
 action->addObject(
   myobject, "myobject",
   Kross::ChildrenInterface::AutoConnectSignals);
 // Set the file we like to execute.
 action->setFile(file);
 // Execute the script.
 action->trigger();

}

The execScriptFile function does create an instance of Kross::Action that is used as abstract container to deal with scripts / script files.

We then add our myobject instance to the action. That way scripting code is able to access the publish QObject instance, call it slots and connect with the signals.

Cause Kross::ChildrenInterface::AutoConnectSignals is defined, the init() and the update() signals the myobject instance provides will be automaticaly connected to matching scripting functions.

Then the script file that should be executed is set. The used interpreter will be determinated by the file-extension like e.g. *.py for Python or or *.rb for Ruby. You are also able to set the interpreter explicit with e.g. action->setInterpreter("python") or action->setInterpreter("ruby"). You are also able to use action->setCode("print 'hello world'") to set the scripting code direct.

Finally the script is executed. This is done by triggering the action. Once executed you are also able to use Kross::ErrorInterface to check if the action was executed successfully like demonstrate below.

 if( action->hadError() )
   kDebug() << action->errorMessage() << endl;

The Kross::Manager provides also the option to connect with the started and finished signals that got emitted if a script got executed. connect(&Kross::Manager::self(), SIGNAL( started(Kross::Action*) ),

       this, SLOT( started(Kross::Action*) ));

connect(&Kross::Manager::self(), SIGNAL( finished(Kross::Action*) ),

       this, SLOT( finished(Kross::Action*) ));

The Kross::ActionCollection class manages collections of Kross::Action instances, enables hierachies and implements serializing from/to XML.

The Python scripting code

The following Python script demonstrates how a Python plugin looks like. The init() and the update() functions will be called if the matching signals at the myobject instance are emitted.

First we import myobject module. This module provides us access to the MyObject QObject instance and it's slots, signals and properties. Within the init() Python function we set the interval to 2000 milliseconds = 2 seconds. The QTimer our MyObject instance starts till emit the update() signal then 2 seconds later what in turn calls our update() Python function.

import myobject

def init():

 myobject.setInterval(2000)

def update():

 print "interval=%i" % myobject.interval()

The Ruby scripting code

The following Ruby script does the same as the Python scripting code above except by using the Ruby scripting language. This shows, that the same rich API functionality is accessible independend of the used scripting language.

require 'myobject'

def init()

 Myobject.setInterval(2000)

end

def update()

 puts "interval=%i" % Myobject.interval()

end