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

From KDE TechBase
(Mark for updating)
 
(24 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Review|Port to KF5}}
==Introduction==
==Introduction==


This tutorial provides a step-by-step introduction on how to integrate scripts as plugins into a C++/Qt/KDE application. This way, an application can be extended with plugins written in scripting languages such as [http://www.python.org/ Python], [http://www.ruby-lang.org/ Ruby] and [http://xmelegance.org/kjsembed KDE JavaScript].
This tutorial provides a step-by-step introduction on how to integrate scripts as plugins into a C++/Qt/KDE application. This way, an application can be extended with plugins written in scripting languages such as [http://www.python.org/ Python], [http://www.ruby-lang.org/ Ruby] and [http://xmelegance.org/kjsembed KDE JavaScript].
See also...
* [[Development/Tutorials/Kross/Introduction|Introduction to Kross]]
* [[Development/Tutorials/KWord_Scripting|KWord Scripting Tutorial]]
* [[Development/Tutorials/KSpread_Scripting|KSpread Scripting Tutorial]]
* [[Development/Tutorials/Krita_Scripting|Krita Scripting Tutorial]]
* [[Development/Tutorials/SuperKaramba|SuperKaramba Tutorial]]


==The C++ code==
==The C++ code==
Line 9: Line 18:
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.
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.


<code cppqt>
===The QObject code===
 
Here we just define the MyObject class that inherits a QObject. Please note, that this part is not Kross related and it's just a class that does provide us some functionality we may like to use and control from within scripting code.
 
So, it depends on the use-cases of your application what QObject instances may needed for the scripting code and how they look like. You may also like to look at the tutorials for [[Development/Tutorials/KWord_Scripting|KWord]], [[Development/Tutorials/KSpread_Scripting|KSpread]], [[Development/Tutorials/Krita_Scripting|Krita]] and [[Development/Tutorials/SuperKaramba|SuperKaramba]] to get an idea how scripting was done there.
 
<syntaxhighlight lang="cpp-qt">
#include <QObject>
#include <QObject>
#include <QTimer>
#include <QTimer>
#include <kross/core/action.h>


// This is our QObject our scripting code will access
// This is our QObject our scripting code will access
Line 19: Line 33:
   public:
   public:


     MyObject(QObject* parent)
    // The Constructor does set up some stuff.
      : QObject(parent), m_timer(new QTimer(this))
     MyObject(QObject* parent) : QObject(parent) {
    {
      // Create the QTimer instance we will us to emit
      // the update() signal with the defined interval.
      m_timer = new QTimer(this);
 
       // On timeout call the update() scripting function
       // On timeout call the update() scripting function
       // if available.
       // if available.
       connect(m_timer, SIGNAL(timeout()), SIGNAL(update()));
       connect(m_timer, SIGNAL(timeout()), SIGNAL(update()));
      // Calls the init() scripting function if available.
      // We pass our used QTimer instance and an integer
      // as arguments.
      emit init(m_timer, 2000);


       // Normaly we would need to start the timer with  
       // Normaly we would need to start the timer with  
Line 36: Line 48:
     }
     }


     virtual ~MyObject() {}
     // Calls the init() scripting function if available.
    // We pass our used QTimer instance and an interval
    // integer value as arguments.
    void callInit() { emit init(timer(), 2000); }


   Q_SLOTS:
   public Q_SLOTS:
 
    // Return the QTimer instance.
    QObject* timer() const { return m_timer; }


     // Return the timers interval in milliseconds
     // Return the timers interval in milliseconds
Line 50: Line 68:
     // If emitted calls the init(timer,interval) scripting
     // If emitted calls the init(timer,interval) scripting
     // function if available.
     // function if available.
     void init(QTimer* timer, int interval);
     void init(QObject*, int);


     // If emitted calls the update() scripting function
     // If emitted calls the update() scripting function
Line 57: Line 75:


   private:
   private:
     Q_Timer* m_timer;
     QTimer* m_timer;
};
};
</syntaxhighlight>
===The Kross code===
Now we demonstrate how the MyObject class we defined above could be made scriptable using Kross.
<syntaxhighlight lang="cpp-qt">
#include <kross/core/action.h>


// Execute a script file.
// Execute a script file.
Line 79: Line 105:
   // Execute the script.
   // Execute the script.
   action->trigger();
   action->trigger();
  // Now we emit the init(QTimer*,int) signal which in turn
  // should call our connected init(timer,interval) scripting
  // function if available.
  myobject->callInit();
}
}
</code>
</syntaxhighlight>


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 {{qt|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's slots, emit it's signals or just connect signals with signals, signals with slots or signals with scripting functions.


Cause [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/childreninterface.h?view=markup Kross::ChildrenInterface::AutoConnectSignals] is defined, the init(QTimer*,int) 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(QTimer*,int) 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.
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 *.rb for Ruby. You are also able to set the interpreter explicit with e.g. action->setInterpreter("python"). The scripting code that should be executed can be also set direct by using something like action->setCode("print 'hello world'").


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. Please note, that we delay anything related to interpreter-backends till the scripting code is actualy executed. This also includes calling scripting functions if signals are emitted. So, you always need to execute the script before any scripting related thing is done.
<code cppqt>
 
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.
<syntaxhighlight lang="cpp-qt">
   if( action->hadError() )
   if( action->hadError() )
     kDebug() << action->errorMessage() << endl;
     kDebug() << action->errorMessage();
</code>
</syntaxhighlight>
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.
<code cppqt>
<syntaxhighlight lang="cpp-qt">
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*) ));
</code>
</syntaxhighlight>


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.


==The Python scripting code==
==The scripting code==
 
===Python===


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 {{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 will emit the update() signal then 2 seconds later what in turn calls our update() Python function.
First we import the 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. Then we start the {{qt|QTimer}} of our MyObject instance by calling it's start() slot. Then each 2 seconds the update() signal will be emitted what in turn calls our update() Python function.


<code python>
<syntaxhighlight lang="python">
# import the published MyObject instance.
# import the published MyObject instance.
import myobject
import myobject
Line 137: Line 172:
# got emitted.
# got emitted.
def update():
def update():
   # just print the interval.
   # just print the interval using the interval() slot
  # the myobject instance provides us.
   print "interval=%i" % myobject.interval()
   print "interval=%i" % myobject.interval()
</code>


==The Ruby scripting code==
  # also print the interval but this time we ask
  # the myobject instance to return us the QTimer
  # instance and then use the interval Q_PROPERTY.
  print "interval=%i" % myobject.timer().interval
</syntaxhighlight>
 
===Ruby===


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.


<code ruby>
<syntaxhighlight lang="ruby">
# import the published MyObject instance.
# import the published MyObject instance.
require 'myobject'
require 'myobject'
Line 172: Line 213:
# got emitted.
# got emitted.
def update()
def update()
   # just print the interval.
   # just print the interval using the interval() slot
  # the myobject instance provides us.
   puts "interval=%i" % Myobject.interval()
   puts "interval=%i" % Myobject.interval()
  # also print the interval but this time we ask
  # the myobject instance to return us the QTimer
  # instance and then use the interval Q_PROPERTY.
  puts "interval=%i" % Myobject.timer().interval
end
end
</code>
</syntaxhighlight>


==The JavaScript scripting code==
===JavaScript===


The following JavaScript script does the same as the Python and the Ruby scripting code above but uses Kjs+KjsEmbed (both included in kdelibs). So, the same rich API functionality is also accessible from within the JavaScript language.
The following JavaScript script does the same as the Python and the Ruby scripting code above but uses Kjs+KjsEmbed (both included in kdelibs). So, the same rich API functionality is also accessible from within the JavaScript language.


<code javascript>
<syntaxhighlight lang="javascript">
// this function got called if the init(QTimer*,int)
// this function got called if the init(QTimer*,int)
// signal got emitted.
// signal got emitted.
Line 207: Line 254:
function update()
function update()
{
{
   // just print the interval.
   // just print the interval using the interval() slot
  // the myobject instance provides us.
   println( "interval=" + myobject.interval() );
   println( "interval=" + myobject.interval() );
  // also print the interval but this time we ask
  // the myobject instance to return us the QTimer
  // instance and then use the interval Q_PROPERTY.
  println( "interval=" + myobject.timer().interval );
}
}
</code>
</syntaxhighlight>

Latest revision as of 08:21, 31 May 2019

Warning
This page needs a review and probably holds information that needs to be fixed.

Parts to be reviewed:

Port to KF5

Introduction

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

See also...

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.

The QObject code

Here we just define the MyObject class that inherits a QObject. Please note, that this part is not Kross related and it's just a class that does provide us some functionality we may like to use and control from within scripting code.

So, it depends on the use-cases of your application what QObject instances may needed for the scripting code and how they look like. You may also like to look at the tutorials for KWord, KSpread, Krita and SuperKaramba to get an idea how scripting was done there.

#include <QObject>
#include <QTimer>

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

    // The Constructor does set up some stuff.
    MyObject(QObject* parent) : QObject(parent) {
      // Create the QTimer instance we will us to emit
      // the update() signal with the defined interval.
      m_timer = new QTimer(this);

      // On timeout call the update() scripting function
      // if available.
      connect(m_timer, SIGNAL(timeout()), SIGNAL(update()));

      // Normaly we would need to start the timer with 
      // something like m_timer->start() but we leave that
      // job up to the script.
    }

    // Calls the init() scripting function if available.
    // We pass our used QTimer instance and an interval
    // integer value as arguments.
    void callInit() { emit init(timer(), 2000); }

  public Q_SLOTS:

    // Return the QTimer instance.
    QObject* timer() const { return m_timer; }

    // 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(timer,interval) scripting
    // function if available.
    void init(QObject*, int);

    // If emitted calls the update() scripting function
    // if available.
    void update();

  private:
    QTimer* m_timer;
};

The Kross code

Now we demonstrate how the MyObject class we defined above could be made scriptable using Kross.

#include <kross/core/action.h>

// 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();

  // Now we emit the init(QTimer*,int) signal which in turn
  // should call our connected init(timer,interval) scripting
  // function if available.
  myobject->callInit();
}

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's slots, emit it's signals or just connect signals with signals, signals with slots or signals with scripting functions.

Cause Kross::ChildrenInterface::AutoConnectSignals is defined, the init(QTimer*,int) 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 *.rb for Ruby. You are also able to set the interpreter explicit with e.g. action->setInterpreter("python"). The scripting code that should be executed can be also set direct by using something like action->setCode("print 'hello world'").

Finally the script is executed. This is done by triggering the action. Please note, that we delay anything related to interpreter-backends till the scripting code is actualy executed. This also includes calling scripting functions if signals are emitted. So, you always need to execute the script before any scripting related thing is done.

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();

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 scripting code

Python

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 the 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. Then we start the QTimer of our MyObject instance by calling it's start() slot. Then each 2 seconds the update() signal will be emitted what in turn calls our update() Python function.

# import the published MyObject instance.
import myobject

# this function got called if the init(QTimer*,int)
# signal got emitted.
def init(timer, interval):
  # the following line uses the passed QTimer instance
  # and set's the interval Q_PROPERTY to the as argument
  # passed interval integer.
  timer.interval = interval

  # the following lines does basicly the same as above,
  # that is to set the interval the QTimer should use
  # to call our update() signal. But compared to the
  # line above we are doing it by using the setInterval
  # slot the published MyObject instance provides us.
  myobject.setInterval(2000)

  # now let's start the QTimer by calling it's start()
  # slot.
  timer.start()

# this function got called if the update() signal
# got emitted.
def update():
  # just print the interval using the interval() slot
  # the myobject instance provides us.
  print "interval=%i" % myobject.interval()

  # also print the interval but this time we ask
  # the myobject instance to return us the QTimer
  # instance and then use the interval Q_PROPERTY.
  print "interval=%i" % myobject.timer().interval

Ruby

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.

# import the published MyObject instance.
require 'myobject'

# this function got called if the init(QTimer*,int)
# signal got emitted.
def init(timer, interval)
  # the following line uses the passed QTimer instance
  # and set's the interval Q_PROPERTY to the as argument
  # passed interval integer.
  timer.interval = interval

  # the following lines does basicly the same as above,
  # that is to set the interval the QTimer should use
  # to call our update() signal. But compared to the
  # line above we are doing it by using the setInterval
  # slot the published MyObject instance provides us.
  Myobject.setInterval(2000)

  # now let's start the QTimer by calling it's start()
  # slot.
  timer.start()
end

# this function got called if the update() signal
# got emitted.
def update()
  # just print the interval using the interval() slot
  # the myobject instance provides us.
  puts "interval=%i" % Myobject.interval()

  # also print the interval but this time we ask
  # the myobject instance to return us the QTimer
  # instance and then use the interval Q_PROPERTY.
  puts "interval=%i" % Myobject.timer().interval
end

JavaScript

The following JavaScript script does the same as the Python and the Ruby scripting code above but uses Kjs+KjsEmbed (both included in kdelibs). So, the same rich API functionality is also accessible from within the JavaScript language.

// this function got called if the init(QTimer*,int)
// signal got emitted.
function init(timer, interval)
{
  // the following line uses the passed QTimer instance
  // and set's the interval Q_PROPERTY to the as argument
  // passed interval integer.
  timer.interval = interval;

  // the following lines does basicly the same as above,
  // that is to set the interval the QTimer should use
  // to call our update() signal. But compared to the
  // line above we are doing it by using the setInterval
  // slot the published MyObject instance provides us.
  myobject.setInterval(2000);

  // now let's start the QTimer by calling it's start()
  // slot.
  timer.start();
}

// this function got called if the update() signal
// got emitted.
function update()
{
  // just print the interval using the interval() slot
  // the myobject instance provides us.
  println( "interval=" + myobject.interval() );

  // also print the interval but this time we ask
  // the myobject instance to return us the QTimer
  // instance and then use the interval Q_PROPERTY.
  println( "interval=" + myobject.timer().interval );
}