User:Dipesh/Kross-Tutorial: Difference between revisions

From KDE TechBase
(use {{qt}} when necessary, and the right <code> for the various code snippets)
(Finally moved thr Kross tutorial from the ko-wiki to techbase)
Line 1: Line 1:
{{Improve}}
{{Improve}}
==Intro==
==Intro==


The purpose of this tutorial is to offer a step-by-step introduction how to integrate Kross into
The purpose of this tutorial is to offer a step-by-step introduction how to integrate Kross into your application. While you can integrate Kross also in non-kpartified applications, it's easier to do with the KPart system. This tutorial will assume that you have a kpart application and what we will do is to go step by step through the process of creating a KPart plugin that integrates into your application and provides all the scripting. The scripting functionality is strictly separated from the application. The plugin that implements scripting is optional and the application does not need to know any details about what the plugin does.
your application. While you can integrate Kross also in non-kpartified applications, it's easier
to do with the KPart system. This tutorial will assume that you have a kpart application and
what we will do is to go step by step through the process of creating a KPart plugin that integrates
into your application and provides all the scripting. The scripting functionality is strictly
separated from the application. The plugin that implements scripting is optional and the
application does not need to know any details about what the plugin does.


This tutorial needs kdelibs4 based on Qt 4.2. While Kross and the KDE Javascript backend are included
This tutorial needs kdelibs4 based on Qt 4.2. While Kross and the KDE Javascript backend are included in kdelibs4, it is needed to compile the KOffice2 libraries to install the Ruby and Python support (will be moved to kdebindings soon).
in kdelibs4, it is needed to compile the KOffice2 libraries to install the Ruby and Python
support (will be moved to kdebindings soon).


The whole sourcecode we will produce within this tutorial could also be downloaded as
The whole sourcecode we will produce within this tutorial could also be downloaded as [http://kross.dipe.org/kross2tutorial/kross2tutorial.tar.gz kross2tutorial.tar.gz] and contains all files needed to build a simple example that demonstrates how Kross could be used. Download and extract the tarball. Compile, install and run the kross2tutorialapp application and its kross2tutorial KPart plugin now with;
[http://kross.dipe.org/kross2tutorial/kross2tutorial.tar.gz kross2tutorial.tar.gz]
and contains all files needed to build a simple example that demonstrates how Kross could be used. Download and extract the tarball. Compile, install and run the kross2tutorialapp application and its kross2tutorial KPart plugin now with;
<pre>
<pre>
cd src && mkdir _build && cd _build
cd src && mkdir _build && cd _build
Line 26: Line 17:


For additional examples where Kross is used you may also like to look at;
For additional examples where Kross is used you may also like to look at;
* [[KSpread/Scripting]]
* [http://wiki.koffice.org/index.php?title=KSpread/Scripting KSpread Scripting]
* [[Krita/Scripting]]
* [http://wiki.koffice.org/index.php?title=Krita/Scripting Krita Scripting]
* [[KWord/Scripting]]
* [http://wiki.koffice.org/index.php?title=KWord/Scripting KWord Scripting]


<!-- ################################################################################ //-->
<!-- ################################################################################ //-->
Line 41: Line 32:
with a new scripting-backend without any new line of code and even without any recompile. To
with a new scripting-backend without any new line of code and even without any recompile. To
achieve this internally Qt's introspection-functionality like signals, slots, properties,
achieve this internally Qt's introspection-functionality like signals, slots, properties,
enums, {{qt|QVariant}}, {{qt|QObject}}, {{qt|QMetaObject}}, {{qt|QMetaType}}, etc. are used to deal with functionality
enums, QVariant, QObject, QMetaObject, QMetaType, etc. are used to deal with functionality
at runtime.
at runtime.


Line 88: Line 79:
===The Module plugins===
===The Module plugins===


Modules are plugins which are loaded on demand, and expose application or library functionality to scripting backends.
Modules are plugins loaded on demand at runtime to provide additional functionality. They are
 
somewhat wrappers/bindings/adaptors to offer access to functionality your application or
Since Qt's introspection functionality is used, we are able to simply use {{qt|QObject}}s
a library of your application likes to expose to scripting backends. '''TODO: this parag doesn't parse for me'''
and have them act as classes or objects within a scripting backend. Slots are member methods,
while properties and enumerations are member variables.


If your application offers [http://hal.freedesktop.org/wiki/Software/dbus DBus] support, it may appropriate to reuse existing
Since Qt's introspection functionality is used, we are able to throw in just QObject's
and have them act as classes/objects within a scripting backend. Slots are membermethods
while properties and enumerations are membervariables. If your application also likes
to offer [http://hal.freedesktop.org/wiki/Software/dbus DBus] support it may be
an idea to reuse the
[http://doc.trolltech.com/4.2/qdbusabstractadaptor.html QDBusAbstractAdaptor]
[http://doc.trolltech.com/4.2/qdbusabstractadaptor.html QDBusAbstractAdaptor]
implementations for scripting, as was done with
implementations your application has also for scripting, like for example
[http://wiki.koffice.org/index.php?title=KSpread/Scripting KSpread].
[http://wiki.koffice.org/index.php?title=KSpread/Scripting KSpread] did.


Let's take a look at the following code that implements such a module;
Let's take a look at the following code that implements such a module;
<code cppqt>
<pre>
class MyObject : public QObject {
class MyObject : public QObject {
        Q_OBJECT
  Q_OBJECT
        Q_PROPERTY(QString name READ name WRITE setName)
  Q_PROPERTY(QString name READ name WRITE setName)
    public:
  public:
        MyObject(QObject* parent = 0) : QObject(parent) {}
    MyObject(QObject* parent) : QObject(parent) {}
        virtual ~MyObject() {}
    virtual ~MyObject() {}
        QString name() const { return objectName(); }
    QString name() const { return objectName(); }
        void setName(const QString& name) { return setObjectName(name); }
    void setName(const QString& name) {
    public slots:
      return setObjectName(name);
        QObject* create(const QString& name, QObject* parent = 0) {
    }
            MyObject* obj = new MyObject(parent);
  public slots:
            obj->setObjectName(name);
    QObject* create(const QString& name,
            return obj;
                    QObject* parent=0) {
        }
      MyObject* obj = new MyObject(parent);
        QObject* parent() const { return parent(); }
      obj->setObjectName(name);
        void setParent(QObject* parent) {
      return obj;
            setParent(parent);
    }
            emit parentChanged();  
    QObject* parent() const { return parent(); }
        }
    void setParent(QObject* parent) {
    signals:
      setParent(parent);
        void parentChanged();
      emit parentChanged();  
    }
  signals:
    void parentChanged();
};
};
extern "C" {
extern "C" {
    QObject* krossmodule() { return new MyObject(); }
  QObject* krossmodule() {
    return new MyObject();
  }
}
}
</code>
</pre>
Then we just need to have our myobject.h and myobject.cpp files, filled with the content
Then we just need to have our myobject.h and myobject.cpp files, filled with the content
above, defined in the CMakeLists.txt file. The library needs to be named "krossmodule..." where
above, defined in the CMakeLists.txt file. The library needs to be named "krossmodule..." where
Line 133: Line 131:
as "myobjectmod". The example does not depend on Kross, so you may like to replace
as "myobjectmod". The example does not depend on Kross, so you may like to replace
${KROSS_INCLUDES} with whatever else your module depends on.
${KROSS_INCLUDES} with whatever else your module depends on.
'''TODO: Make the following block less wide, pre tends to come across best at, say 60 chars max'''
: It may be wise to insert whatever CMake uses for line continuations if you want to break lines in this sort of example. --[[User:Argonel|Argonel]] 06:06, 19 January 2007 (CET)
<pre>
<pre>
include_directories(${CMAKE_SOURCE_DIR} ${KROSS_INCLUDES})
include_directories(${CMAKE_SOURCE_DIR}
set(krossmodulemyobjectmod_PART_SRCS myobject.cpp)
  ${KROSS_INCLUDES})
set(krossmodulemyobjectmod_PART_SRCS
  myobject.cpp)
kde4_automoc(${krossmodulemyobjectmod_PART_SRCS})
kde4_automoc(${krossmodulemyobjectmod_PART_SRCS})
kde4_add_plugin(krossmodulemyobjectmod ${krossmodulemyobjectmod_PART_SRCS})
kde4_add_plugin(krossmodulemyobjectmod
target_link_libraries(krossmodulemyobjectmod ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS})
  ${krossmodulemyobjectmod_PART_SRCS})
install(TARGETS krossmodulemyobjectmod DESTINATION ${PLUGIN_INSTALL_DIR})
target_link_libraries(krossmodulemyobjectmod
  ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS})
install(TARGETS krossmodulemyobjectmod
  DESTINATION ${PLUGIN_INSTALL_DIR})
</pre>
</pre>
The following Python sample code accesses then the module at runtime and uses the QObject,
The following Python sample code accesses then the module at runtime and uses the QObject,
calls it's slots and properties and connects a signal with a python function (e.g. save as
calls it's slots and properties and connects a signal with a python function (e.g. save as
file named "myobjecttest.py" and execute with "kross ./myobjecttest.py");
file named "myobjecttest.py" and execute with "kross ./myobjecttest.py");
<code python>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
import Kross
import Kross
Line 155: Line 155:
def myCallbackFunc(args):
def myCallbackFunc(args):
     print "The parent of obj1 changed"
     print "The parent of obj1 changed"
obj1.connect("parentChanged()", myCallbackFunc)
obj1.connect("parentChanged()",myCallbackFunc)
obj1.setParent(m)
obj1.setParent(m)
print "obj1.name=%s m.name=%s" % (obj1.name, obj1.parent().name)
print "%s %s" % (obj1.name,obj1.parent().name)
</code>
</pre>


===Manager, GuiClient, Action===
===Manager, GuiClient, Action===
Line 167: Line 167:
script uses the Kross module to create a new Kross::Action instance, fills it with JavaScript
script uses the Kross module to create a new Kross::Action instance, fills it with JavaScript
code and executes that JavaScript code.
code and executes that JavaScript code.
<code javascript>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
import Kross
import Kross
jsaction = Kross.action("MyKjsScript")
a = Kross.action("MyKjsScript")
jsaction.setInterpreter("javascript")
a.setInterpreter("javascript")
jsaction.setCode( "println(\"Hello world from Kjs\");" )
a.setCode("println(\"Hello world from Kjs\");")
jsaction.trigger()
a.trigger()
</code>
</pre>


The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/guiclient.h?view=markup Kross::GuiClient]
The [http://websvn.kde.org/trunk/KDE/kdelibs/kross/core/guiclient.h?view=markup Kross::GuiClient]
Line 188: Line 188:
instance. Following Python script accesses the Kross module and the self variable which is
instance. Following Python script accesses the Kross module and the self variable which is
our Kross::Action instance that provides the context for the running Python script.
our Kross::Action instance that provides the context for the running Python script.
<code python>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
import Kross
import Kross
print "Kross.objectName=%s" % Kross.objectName()
print "objectName=%s" % Kross.objectName()
print "Kross.interpreters=%s" % Kross.interpreters()
print "interpreters=%s" % Kross.interpreters()
print "self.objectName=%s" % self.objectName()
print "objectName=%s" % self.objectName()
print "self.text=%s" % self.text
print "text=%s" % self.text
print "self.enabled=%s" % self.enabled
print "enabled=%s" % self.enabled
print "self.currentPath=%s" % self.currentPath()
print "currentPath=%s" % self.currentPath()
print "self.interpreter=%s" % self.interpreter()
print "interpreter=%s" % self.interpreter()
print "self.description=%s" % self.description()
print "description=%s" % self.description()
print "self.code=%s" % self.code()
print "code=%s" % self.code()
</code>
</pre>


===DBus and Kross===
===DBus and Kross===
Line 254: Line 254:
include(KDE4Defaults)
include(KDE4Defaults)
find_package(Perl REQUIRED)
find_package(Perl REQUIRED)
add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS} -DHAVE_CONFIG_H=1)
add_definitions(${QT_DEFINITIONS}
  ${KDE4_DEFINITIONS} -DHAVE_CONFIG_H=1)
link_directories(${KDE4_LIB_DIR})
link_directories(${KDE4_LIB_DIR})
set(CMAKE_REQUIRED_DEFINITIONS ${_KDE4_PLATFORM_DEFINITIONS})
set(CMAKE_REQUIRED_DEFINITIONS
include_directories(${CMAKE_SOURCE_DIR} ${KDE4_KDECORE_INCLUDES} ${KDE4_INCLUDES} ${KDE4_KDEUI_INCLUDES} ${KDE4_KPARTS_INCLUDES})
  ${_KDE4_PLATFORM_DEFINITIONS})
include_directories(${CMAKE_SOURCE_DIR}
  ${KDE4_KDECORE_INCLUDES} ${KDE4_INCLUDES}
  ${KDE4_KDEUI_INCLUDES}
  ${KDE4_KPARTS_INCLUDES})


add_subdirectory( plugin )
add_subdirectory( plugin )


set(kross2tutorialapp_SRCS mainwindow.cpp main.cpp)
set(kross2tutorialapp_SRCS
  mainwindow.cpp main.cpp)
kde4_automoc(${kross2tutorialapp_SRCS})
kde4_automoc(${kross2tutorialapp_SRCS})
kde4_add_executable(kross2tutorialapp ${kross2tutorialapp_SRCS})
kde4_add_executable(kross2tutorialapp
target_link_libraries(kross2tutorialapp ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS} kparts krosscore )
  ${kross2tutorialapp_SRCS})
target_link_libraries(kross2tutorialapp
  ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS}
  kparts krosscore )
</pre>
</pre>


The [http://kross.dipe.org/kross2tutorial/src/plugin/CMakeLists.txt src/plugin/CMakeLists.txt] file;
The [http://kross.dipe.org/kross2tutorial/src/plugin/CMakeLists.txt src/plugin/CMakeLists.txt] file;
<pre>
<pre>
include_directories( ${CMAKE_SOURCE_DIR} ${KROSS_INCLUDES} )
include_directories(${CMAKE_SOURCE_DIR}
set(krossmoduletutorial_PART_SRCS module.cpp part.cpp)
  ${KROSS_INCLUDES})
set(krossmoduletutorial_PART_SRCS
  module.cpp part.cpp)
kde4_automoc(${krossmoduletutorial_PART_SRCS})
kde4_automoc(${krossmoduletutorial_PART_SRCS})
kde4_add_plugin(krossmoduletutorial ${krossmoduletutorial_PART_SRCS})
kde4_add_plugin(krossmoduletutorial
target_link_libraries(krossmoduletutorial ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS} kparts )
  ${krossmoduletutorial_PART_SRCS})
install(TARGETS krossmoduletutorial DESTINATION ${PLUGIN_INSTALL_DIR})
target_link_libraries(krossmoduletutorial
install( FILES krossmoduletutorial.desktop DESTINATION ${SERVICES_INSTALL_DIR})
  ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS}
  kparts )
install(TARGETS krossmoduletutorial
  DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES krossmoduletutorial.desktop
  DESTINATION ${SERVICES_INSTALL_DIR})
</pre>
</pre>


===The KApplication===
===The KApplication===
The main function ([http://kross.dipe.org/kross2tutorial/src/main.cpp main.cpp]) creates the KApplication and shows the MainWindow.
The main function ([http://kross.dipe.org/kross2tutorial/src/main.cpp main.cpp]) creates the KApplication and shows the MainWindow.
<code cppqt>
<pre>
int main(int argc, char **argv) {
int main(int argc, char **argv) {
    KAboutData about();
  KAboutData about();
    KCmdLineArgs::init(argc, argv, &about);
  KCmdLineArgs::init(argc,argv,&about);
    KApplication app();
  KApplication app();
    MainWindow *mainWin = new MainWindow();
  MainWindow *mainWin = new MainWindow();
    mainWin->show();
  mainWin->show();
    return app.exec();
  return app.exec();
}
}
</code>
</pre>


===The KPart main window===
===The KPart main window===
Line 295: Line 311:
[http://kross.dipe.org/kross2tutorial/src/main.cpp mainwindow.cpp]) contains the
[http://kross.dipe.org/kross2tutorial/src/main.cpp mainwindow.cpp]) contains the
top-level KParts::MainWindow implementation.
top-level KParts::MainWindow implementation.
<code cppqt>
<pre>
class MainWindow : public KParts::MainWindow {
class MainWindow
    public:
    : public KParts::MainWindow
        MainWindow() : KParts::MainWindow() {
{
            KLibFactory* factory = KLibLoader::self()->factory("krossmoduletutorial");
  public:
            KParts::ReadWritePart* part = dynamic_cast<KParts::ReadWritePart*>( factory->create(this) );
    MainWindow()
            part->openUrl( KUrl("file:///home/myuser/myscript.py") );
      : KParts::MainWindow()
        }
    {
        virtual ~MainWindow() {}
      KLibFactory* factory =
        KLibLoader::self()->factory(
          "krossmoduletutorial");
      KParts::ReadWritePart* part =
        dynamic_cast<KParts::ReadWritePart*>
          ( factory->create(this) );
      part->openUrl(
        KUrl("file:///path/myscript.py"));
    }
    virtual ~MainWindow() {}
};
};
</code>
</pre>


<!-- ################################################################################ //-->
<!-- ################################################################################ //-->
Line 315: Line 340:
[http://kross.dipe.org/kross2tutorial/src/plugin/part.cpp part.cpp]) implements
[http://kross.dipe.org/kross2tutorial/src/plugin/part.cpp part.cpp]) implements
a KParts::ReadWritePart.
a KParts::ReadWritePart.
<code cppqt>
<pre>
class Part : public KParts::ReadWritePart {
class Part
    public:
  : public KParts::ReadWritePart
        Part(QWidget* widget, QObject* parent, const QStringList&)
{
            : KParts::ReadWritePart(parent)
  public:
            , m_guiclient( new Kross::GUIClient(this, this) )
    Part(QWidget*, QObject* parent,
            , m_action(0) {}
      const QStringList&)
        virtual ~Part() { delete m_action; }
      : KParts::ReadWritePart(parent)
        virtual bool openFile() {
      , m_guiclient(
            delete m_action;
          new Kross::GUIClient(this,this))
            m_action = new Kross::Action(m_file);
      , m_action(0) {}
            m_action->trigger();
    virtual ~Part() { delete m_action; }
        }
    virtual bool openFile() {
        virtual bool saveFile() { return false; } // not implemented yet
      delete m_action;
    private:
      m_action = new Kross::Action(m_file);
        Kross::GUIClient* m_guiclient;
      m_action->trigger();
        QPointer &lt; Kross::Action &gt; m_action;
    }
    virtual bool saveFile() {return false;}
  private:
    Kross::GUIClient* m_guiclient;
    Kross::Action* m_action;
};
};
</code>
</pre>


===The Module===
===The Module===
Line 339: Line 368:
[http://kross.dipe.org/kross2tutorial/src/plugin/module.cpp module.cpp]) implements
[http://kross.dipe.org/kross2tutorial/src/plugin/module.cpp module.cpp]) implements
the "KrossModuleTutorial" module.
the "KrossModuleTutorial" module.
<code cppqt>
<pre>
class Module : public QObject {
class Module : public QObject {
        Q_OBJECT
  Q_OBJECT
    public:
  public:
        Module(Part* part = 0) : QObject(part), m_widget(0) {}
    Module(Part* part=0)
        virtual ~Module() {}
      : QObject(part), m_widget(0) {}
    public slots:
    virtual ~Module() {}
        QWidget* widget() {
  public slots:
            if( ! m_widget ) {
    QWidget* widget() {
                Part* part = dynamic_cast<Part*>( parent() );
      if(m_widget) return m_widget;
                m_widget = new QWidget( part ? part->widget() : 0 );
      Part* part =  
                m_widget->setLayout( new QVBoxLayout(m_widget) );
        dynamic_cast<Part*>(parent());
                if( part && part->widget() && part->widget()->layout() )
      m_widget = new QWidget(
                    part->widget()->layout()->addWidget(m_widget);
        part ? part->widget() : 0 );
                m_widget->show();
      m_widget->setLayout(
            }
        new QVBoxLayout(m_widget) );
            return m_widget;
      QWidget* w =
        }
        part ? part->widget() : 0;
    private:
      if(w && w->layout())
        QPointer &lt; QWidget &gt; m_widget;
        w->layout()->addWidget(m_widget);
      m_widget->show();
      return m_widget;
    }
  private:
    QWidget* m_widget;
};
};
</code>
</pre>


<!-- ################################################################################ //-->
<!-- ################################################################################ //-->


==Samples==
==Samples==
Line 373: Line 406:
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.py sample_forms.py]
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.py sample_forms.py]
Python script demonstrates usage of into an application embedded Kross forms.
Python script demonstrates usage of into an application embedded Kross forms.
<code python>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
import Kross
import Kross
import KrossModuleTutorial
import KrossModuleTutorial
forms = Kross.module("forms")
forms = Kross.module("forms")
mainwidget = KrossModuleTutorial.widget()
w = KrossModuleTutorial.widget()
label = forms.createWidget(mainwidget, "QLabel")
l = forms.createWidget(w,"QLabel")
label.wordWrap = True
l.wordWrap = True
label.text = "The labels text."
l.text = "The labels text."
btn = forms.createWidget(mainwidget, "QPushButton")
b = forms.createWidget(w,"QPushButton")
def buttonClicked():
def buttonClicked():
    global forms
  global forms
    forms.showMessageBox("Information", "Caption", "the message text")
  forms.showMessageBox("Information",
btn.connect("clicked()", buttonClicked)
    "Caption", "the message text")
btn.text = "Show messagebox"
b.connect("clicked()", buttonClicked)
</code>
b.text = "Show messagebox"
</pre>


===Python Tkinter script===
===Python Tkinter script===
The [http://kross.dipe.org/kross2tutorial/src/sample_tkinter.py sample_tkinter.py]
The [http://kross.dipe.org/kross2tutorial/src/sample_tkinter.py sample_tkinter.py]
Python script uses the Tkinter to show a modal dialog.
Python script uses the Tkinter to show a modal dialog.
<code python>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
class TkTest:
class TkTest:
    def __init__(self):
  def __init__(self):
        import Tkinter
    import Tkinter
        self.root = Tkinter.Tk()
    self.root = Tkinter.Tk()
        self.root.title("TkTest")
    self.root.title("TkTest")
        self.root.deiconify()
    self.root.deiconify()
        self.mainframe = Tkinter.Frame(self.root)
    self.mainframe =
        self.mainframe.pack()
      Tkinter.Frame(self.root)
        self.button1 = Tkinter.Button(self.mainframe, text="Button1", command=self.callback1)
    self.mainframe.pack()
        self.button1.pack(side=Tkinter.LEFT)
    self.button1 = Tkinter.Button(
        self.root.mainloop()
      self.mainframe,
    def callback1(self):
      text="Button1",
        import tkMessageBox
      command=self.callback1)
        tkMessageBox.showinfo("Callback1", "Callback1 called.")
    self.button1.pack(side=Tkinter.LEFT)
    self.root.mainloop()
  def callback1(self):
    import tkMessageBox
    tkMessageBox.showinfo(
      "Callback1", "Callback1 called.")
TkTest()
TkTest()
</code>
</pre>


===Ruby forms script===
===Ruby forms script===
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.rb sample_forms.rb]
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.rb sample_forms.rb]
Ruby script uses the Kross forms module to create and embedded a QLabel instance.
Ruby script uses the Kross forms module to create and embedded a QLabel instance.
<code ruby>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
require 'Kross'
require 'Kross'
require 'KrossModuleTutorial'
require 'KrossModuleTutorial'
forms = Kross.module("forms")
forms = Kross.module("forms")
mainwidget = KrossModuleTutorial.widget()
w = KrossModuleTutorial.widget()
label = forms.createWidget(mainwidget, "QLabel")
l = forms.createWidget(w,"QLabel")
label.wordWrap = true
l.wordWrap = true
label.text = "This is the labels text."
l.text = "Some labels text"
</code>
</pre>


===JavaScript with KjsEmbed script===
===JavaScript with KjsEmbed script===
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.rb sample_kjsembed.js]
The [http://kross.dipe.org/kross2tutorial/src/sample_forms.rb sample_kjsembed.js]
JavaScript script creates and embeddes a QFrame using KjsEmbed.
JavaScript script creates and embeddes a QFrame using KjsEmbed.
<code javascript>
<pre>
#!/usr/bin/env kross
#!/usr/bin/env kross
mainwidget = KrossModuleTutorial.widget()
w = KrossModuleTutorial.widget()
var frame = new Widget("QFrame", mainwidget);
var f = new Widget("QFrame", w);
frame.frameShape = frame.StyledPanel;
f.frameShape = f.StyledPanel;
frame.frameShadow = frame.Sunken;
f.frameShadow = f.Sunken;
frame.lineWidth = 4;
f.lineWidth = 4;
frame.show();
f.show();
</code>
</pre>

Revision as of 21:49, 16 March 2007

Warning
This section needs improvements: Please help us to

cleanup confusing sections and fix sections which contain a todo


Intro

The purpose of this tutorial is to offer a step-by-step introduction how to integrate Kross into your application. While you can integrate Kross also in non-kpartified applications, it's easier to do with the KPart system. This tutorial will assume that you have a kpart application and what we will do is to go step by step through the process of creating a KPart plugin that integrates into your application and provides all the scripting. The scripting functionality is strictly separated from the application. The plugin that implements scripting is optional and the application does not need to know any details about what the plugin does.

This tutorial needs kdelibs4 based on Qt 4.2. While Kross and the KDE Javascript backend are included in kdelibs4, it is needed to compile the KOffice2 libraries to install the Ruby and Python support (will be moved to kdebindings soon).

The whole sourcecode we will produce within this tutorial could also be downloaded as kross2tutorial.tar.gz and contains all files needed to build a simple example that demonstrates how Kross could be used. Download and extract the tarball. Compile, install and run the kross2tutorialapp application and its kross2tutorial KPart plugin now with;

cd src && mkdir _build && cd _build
cmake -DCMAKE_INSTALL_PREFIX=`kde4-config --prefix` ..
make
sudo make install
./kross2tutorialapp

For additional examples where Kross is used you may also like to look at;


Kross

The Kross scripting framework provides full Python, Ruby and KDE JavaScript scripting support. The goal was to limit the work needed on applications to have them fully scriptable and to provide a modular way to transparently integrate additional interpreters and in that way extend your application with a new scripting-backend without any new line of code and even without any recompile. To achieve this internally Qt's introspection-functionality like signals, slots, properties, enums, QVariant, QObject, QMetaObject, QMetaType, etc. are used to deal with functionality at runtime.

The Interpreter plugins

Kross offers a plugin-interface to integrate interpreter backends like Python, Ruby and KDE-Javascript. They are loaded on demand at runtime. Neither the application nor Kross needs to know any details about the backends. This clear separation between the application and scripting details enables at the one hand, to deal with scripting at an abstract level without being bound to only one backend, and at the other hand makes it easy to integrate scripting into an already existing application.

Currently Kross comes with support for 3 scripting backends;

Each interpreter plugin needs to implement two abstract classes;

  • The Kross::Interpreter class is a singleton controlled by the Kross::Manager and could be used to setup the interpreter or do other things to share functionality between instances of the Script class.
  • The Kross::Script class handles exactly one script instance. An application is able to deal with multiple scripts at the same time where each of them has it's own instance of the Kross::Script class controlled by an instance of the more abstract Kross::Action class.

The Module plugins

Modules are plugins loaded on demand at runtime to provide additional functionality. They are somewhat wrappers/bindings/adaptors to offer access to functionality your application or a library of your application likes to expose to scripting backends. TODO: this parag doesn't parse for me

Since Qt's introspection functionality is used, we are able to throw in just QObject's and have them act as classes/objects within a scripting backend. Slots are membermethods while properties and enumerations are membervariables. If your application also likes to offer DBus support it may be an idea to reuse the QDBusAbstractAdaptor implementations your application has also for scripting, like for example KSpread did.

Let's take a look at the following code that implements such a module;

class MyObject : public QObject {
  Q_OBJECT
  Q_PROPERTY(QString name READ name WRITE setName)
  public:
    MyObject(QObject* parent) : QObject(parent) {}
    virtual ~MyObject() {}
    QString name() const { return objectName(); }
    void setName(const QString& name) {
      return setObjectName(name);
    }
  public slots:
    QObject* create(const QString& name,
                    QObject* parent=0) {
      MyObject* obj = new MyObject(parent);
      obj->setObjectName(name);
      return obj;
    }
    QObject* parent() const { return parent(); }
    void setParent(QObject* parent) {
      setParent(parent);
      emit parentChanged(); 
    }
  signals:
    void parentChanged();
};
extern "C" {
  QObject* krossmodule() {
    return new MyObject();
  }
}

Then we just need to have our myobject.h and myobject.cpp files, filled with the content above, defined in the CMakeLists.txt file. The library needs to be named "krossmodule..." where the "..." is then the name the module is accessible as. For our example we use "krossmodulemyobjectmod" and therefore we are able to access the module if installed as "myobjectmod". The example does not depend on Kross, so you may like to replace ${KROSS_INCLUDES} with whatever else your module depends on.

include_directories(${CMAKE_SOURCE_DIR}
  ${KROSS_INCLUDES})
set(krossmodulemyobjectmod_PART_SRCS
  myobject.cpp)
kde4_automoc(${krossmodulemyobjectmod_PART_SRCS})
kde4_add_plugin(krossmodulemyobjectmod
  ${krossmodulemyobjectmod_PART_SRCS})
target_link_libraries(krossmodulemyobjectmod
  ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS})
install(TARGETS krossmodulemyobjectmod
  DESTINATION ${PLUGIN_INSTALL_DIR})

The following Python sample code accesses then the module at runtime and uses the QObject, calls it's slots and properties and connects a signal with a python function (e.g. save as file named "myobjecttest.py" and execute with "kross ./myobjecttest.py");

#!/usr/bin/env kross
import Kross
m = Kross.module("myobjectmod")
m.name = "MyObjectModuleName"
obj1 = m.create("OtherObjectName")
def myCallbackFunc(args):
    print "The parent of obj1 changed"
obj1.connect("parentChanged()",myCallbackFunc)
obj1.setParent(m)
print "%s %s" % (obj1.name,obj1.parent().name)

Manager, GuiClient, Action

The Kross::Manager class is a singleton that provides access to the interpreters, to actions and to the modules. The Kross::Manager is available within scripting code as module named "Kross". Following Python script uses the Kross module to create a new Kross::Action instance, fills it with JavaScript code and executes that JavaScript code.

#!/usr/bin/env kross
import Kross
a = Kross.action("MyKjsScript")
a.setInterpreter("javascript")
a.setCode("println(\"Hello world from Kjs\");")
a.trigger()

The Kross::GuiClient class implements KXMLGUIClient to provide GUI functionality, handling of XML configuration files on a more abstract level and offers some predefined actions that could be optionally used in your application.

The Kross::Action class offers an abstract container to deal with scripts like a single standalone scriptfile. Each action holds a reference to the by the matching Kross::Interpreter instance created by Kross::Script instance. Following Python script accesses the Kross module and the self variable which is our Kross::Action instance that provides the context for the running Python script.

#!/usr/bin/env kross
import Kross
print "objectName=%s" % Kross.objectName()
print "interpreters=%s" % Kross.interpreters()
print "objectName=%s" % self.objectName()
print "text=%s" % self.text
print "enabled=%s" % self.enabled
print "currentPath=%s" % self.currentPath()
print "interpreter=%s" % self.interpreter()
print "description=%s" % self.description()
print "code=%s" % self.code()

DBus and Kross

With the QtDBus module Qt provides a library that a Qt/KDE application is able to use to make Inter-Process Communication using the D-BUS protocol.

The QtDBus module uses the Qt Signals and Slots mechanism. Applications that like to provide parts of there functionality to the DBus world are able to do so by implementing classes that inherit from the QDBusAbstractAdaptor class.

Kross is able to reuse such by an application provided bindings. So, once your application supports dbus, Kross is able to reuse those already existing code and offers transparent access to the scripting-backends to them. How does this differ from e.g. the python-dbus package? Well, first method-calls don't go through the dbus-socket and then it's not dbus-related at all except, that we are able to reuse what your application offers anyway: a clean interface to the outside world. But that's not all, we are not limited to what's possible with dbus. We are also able to exchange instance-pointers to QObject or QWidget instances.

For an example you may like to take a look at how it was done in the KSpread ScriptingModule class.


The application

This section deals with the question how to integrate Kross into a KPart-application to be able to extend your application with scripting.

For testing purposes we first create a simple KPart application. If you already have an application you may like to skip this section and continue with the KPart Plugin.

Relevant files within the kross2tutorial.tar.gz are;

The CMake build system

The src/CMakeLists.txt file;

project(kross2tutorial)

find_package(KDE4 REQUIRED)
include(KDE4Defaults)
find_package(Perl REQUIRED)
add_definitions(${QT_DEFINITIONS}
  ${KDE4_DEFINITIONS} -DHAVE_CONFIG_H=1)
link_directories(${KDE4_LIB_DIR})
set(CMAKE_REQUIRED_DEFINITIONS
  ${_KDE4_PLATFORM_DEFINITIONS})
include_directories(${CMAKE_SOURCE_DIR}
  ${KDE4_KDECORE_INCLUDES} ${KDE4_INCLUDES}
  ${KDE4_KDEUI_INCLUDES}
  ${KDE4_KPARTS_INCLUDES})

add_subdirectory( plugin )

set(kross2tutorialapp_SRCS
  mainwindow.cpp main.cpp)
kde4_automoc(${kross2tutorialapp_SRCS})
kde4_add_executable(kross2tutorialapp
  ${kross2tutorialapp_SRCS})
target_link_libraries(kross2tutorialapp
  ${KDE4_KDECORE_LIBS} ${KDE4_KDEUI_LIBS}
  kparts krosscore )

The src/plugin/CMakeLists.txt file;

include_directories(${CMAKE_SOURCE_DIR}
  ${KROSS_INCLUDES})
set(krossmoduletutorial_PART_SRCS
  module.cpp part.cpp)
kde4_automoc(${krossmoduletutorial_PART_SRCS})
kde4_add_plugin(krossmoduletutorial
  ${krossmoduletutorial_PART_SRCS})
target_link_libraries(krossmoduletutorial
  ${KDE4_KDECORE_LIBS} ${KDE4_KROSSCORE_LIBS}
  kparts )
install(TARGETS krossmoduletutorial
  DESTINATION ${PLUGIN_INSTALL_DIR})
install(FILES krossmoduletutorial.desktop
  DESTINATION ${SERVICES_INSTALL_DIR})

The KApplication

The main function (main.cpp) creates the KApplication and shows the MainWindow.

int main(int argc, char **argv) {
  KAboutData about();
  KCmdLineArgs::init(argc,argv,&about);
  KApplication app();
  MainWindow *mainWin = new MainWindow();
  mainWin->show();
  return app.exec();
}

The KPart main window

The MainWindow class (mainwindow.h mainwindow.cpp) contains the top-level KParts::MainWindow implementation.

class MainWindow
    : public KParts::MainWindow
{
  public:
    MainWindow()
      : KParts::MainWindow()
    {
      KLibFactory* factory =
        KLibLoader::self()->factory(
          "krossmoduletutorial");
      KParts::ReadWritePart* part =
        dynamic_cast<KParts::ReadWritePart*>
          ( factory->create(this) );
      part->openUrl(
        KUrl("file:///path/myscript.py"));
    }
    virtual ~MainWindow() {}
};


The plugin

The KPart plugin

The Part class (part.h part.cpp) implements a KParts::ReadWritePart.

class Part
  : public KParts::ReadWritePart
{
  public:
    Part(QWidget*, QObject* parent,
      const QStringList&)
      : KParts::ReadWritePart(parent)
      , m_guiclient(
          new Kross::GUIClient(this,this))
      , m_action(0) {}
    virtual ~Part() { delete m_action; }
    virtual bool openFile() {
      delete m_action;
      m_action = new Kross::Action(m_file);
      m_action->trigger();
    }
    virtual bool saveFile() {return false;}
  private:
    Kross::GUIClient* m_guiclient;
    Kross::Action* m_action;
};

The Module

The Module class (module.h module.cpp) implements the "KrossModuleTutorial" module.

class Module : public QObject {
  Q_OBJECT
  public:
    Module(Part* part=0)
      : QObject(part), m_widget(0) {}
    virtual ~Module() {}
  public slots:
    QWidget* widget() {
      if(m_widget) return m_widget;
      Part* part = 
        dynamic_cast<Part*>(parent());
      m_widget = new QWidget(
        part ? part->widget() : 0 );
      m_widget->setLayout(
        new QVBoxLayout(m_widget) );
      QWidget* w = 
        part ? part->widget() : 0;
      if(w && w->layout())
        w->layout()->addWidget(m_widget);
      m_widget->show();
      return m_widget;
    }
  private:
    QWidget* m_widget;
};


Samples

Following sample scripts are also included in the kross2tutorial.tar.gz and should be executed using the "kross2tutorialapp" application.

Python forms script

The sample_forms.py Python script demonstrates usage of into an application embedded Kross forms.

#!/usr/bin/env kross
import Kross
import KrossModuleTutorial
forms = Kross.module("forms")
w = KrossModuleTutorial.widget()
l = forms.createWidget(w,"QLabel")
l.wordWrap = True
l.text = "The labels text."
b = forms.createWidget(w,"QPushButton")
def buttonClicked():
  global forms
  forms.showMessageBox("Information",
    "Caption", "the message text")
b.connect("clicked()", buttonClicked)
b.text = "Show messagebox"

Python Tkinter script

The sample_tkinter.py Python script uses the Tkinter to show a modal dialog.

#!/usr/bin/env kross
class TkTest:
  def __init__(self):
    import Tkinter
    self.root = Tkinter.Tk()
    self.root.title("TkTest")
    self.root.deiconify()
    self.mainframe =
      Tkinter.Frame(self.root)
    self.mainframe.pack()
    self.button1 = Tkinter.Button(
      self.mainframe,
      text="Button1",
      command=self.callback1)
    self.button1.pack(side=Tkinter.LEFT)
    self.root.mainloop()
  def callback1(self):
    import tkMessageBox
    tkMessageBox.showinfo(
      "Callback1", "Callback1 called.")
TkTest()

Ruby forms script

The sample_forms.rb Ruby script uses the Kross forms module to create and embedded a QLabel instance.

#!/usr/bin/env kross
require 'Kross'
require 'KrossModuleTutorial'
forms = Kross.module("forms")
w = KrossModuleTutorial.widget()
l = forms.createWidget(w,"QLabel")
l.wordWrap = true
l.text = "Some labels text"

JavaScript with KjsEmbed script

The sample_kjsembed.js JavaScript script creates and embeddes a QFrame using KjsEmbed.

#!/usr/bin/env kross
w = KrossModuleTutorial.widget()
var f = new Widget("QFrame", w);
f.frameShape = f.StyledPanel;
f.frameShadow = f.Sunken;
f.lineWidth = 4;
f.show();