Development/Tutorials/Kross/Connecting Signals and slots in Kross: Difference between revisions

    From KDE TechBase
    (Mark for updating)
     
    (10 intermediate revisions by 3 users not shown)
    Line 6: Line 6:
    }}
    }}


    {{Under Construction}}
    {{Review|Port to KF5}}


    This tutorial shows how to use a system of signals and slots to provide a scripting interface for a KDE application. It builds upon the [[Development/Tutorials/Kross/Hello World|Kross Hello World]] tutorial and again follows a 'Hello World' type format.
    This tutorial shows how to use a system of signals and slots to provide a scripting interface for a KDE application. It builds upon the [[Development/Tutorials/Kross/Hello World|Kross Hello World]] tutorial and again follows a 'Hello World' type format.


    == Update source files ==
    == Update source files ==
    This tutorial is based on the [[Development/Tutorials/Kross/Hello_World|Hello World]] tutorial and extends the codebase we wrote there with new functionality.


    === mainwindow.h ===
    === mainwindow.h ===
    Edit mainwindow.h to handle the changes to mainwindow.cpp. Note that the change is the addition of a private <tt>Kross::Action</tt> .
    First edit the mainwindow.h to handle the changes to mainwindow.cpp. Note that the change is the addition of a private <tt>Kross::Action</tt> .


    <code cpp>
    <syntaxhighlight lang="cpp">
    #ifndef MAINWINDOW_H
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    #define MAINWINDOW_H
    Line 25: Line 27:
    #include <kross/core/action.h>
    #include <kross/core/action.h>
       
       
    // The main window to display our combobox and the label.
    class MainWindow : public QWidget
    class MainWindow : public QWidget
    {
    {
         Q_OBJECT
         Q_OBJECT
       public:
       public:
        // The constructor.
         MainWindow(QWidget *parent=0);
         MainWindow(QWidget *parent=0);
       private Q_SLOTS:
       private Q_SLOTS:
        // This slot is called when the item in the combobox is changed.
         void interpreterActivated(const QString &);
         void interpreterActivated(const QString &);
       private:
       private:
    Line 36: Line 41:
         QLabel* lblMessage;
         QLabel* lblMessage;
         QComboBox* cmbInterpreters;
         QComboBox* cmbInterpreters;
        // We now have the action as class-member.
         Kross::Action* action;
         Kross::Action* action;
    };
    };


    #endif
    #endif
    </code>
    </syntaxhighlight>


    === mainwindow.cpp ===
    === mainwindow.cpp ===
    Using the same CmakeLists.txt and main.cpp from the previous tutorial, edit the mainwindow.cpp as follows:
    Using the same CmakeLists.txt and main.cpp from the previous [[Development/Tutorials/Kross/Hello_World|Hello World]] tutorial, edit the mainwindow.cpp as follows:


    <code cpp>
    <syntaxhighlight lang="cpp">
    #include "mainwindow.h"
    #include "mainwindow.h"


    Line 54: Line 60:
    #include <kross/core/action.h>
    #include <kross/core/action.h>


    // the constructor.
    MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
    MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
    {
    {
    Line 73: Line 80:
       setLayout(vLayout);
       setLayout(vLayout);


      // This time we create the Kross::Action already within the
      // constructor and add the objects that should be accessible
      // from within scripting code.
       action = new Kross::Action(this, "MyScript");
       action = new Kross::Action(this, "MyScript");


    Line 78: Line 88:
       action->addObject(cmbInterpreters, "MyInterpreter");
       action->addObject(cmbInterpreters, "MyInterpreter");
       action->addObject(lblMessage, "MyLabel");
       action->addObject(lblMessage, "MyLabel");
      action->trigger();
    }
    }


    // this slot is called when the active item of the combobox changes.
    void MainWindow::interpreterActivated(const QString &strSelectedInterpreter)
    void MainWindow::interpreterActivated(const QString &strSelectedInterpreter)
    {
    {
    Line 89: Line 98:
       }
       }


      // this time we are using external script files.
       QString filename;
       QString filename;
       if(strSelectedInterpreter == "python")
       if(strSelectedInterpreter == "python")
         filename = "krossSigsSlots.py";
         filename = "krossSigsSlots.py";
    Line 98: Line 107:
         return;
         return;


      // set the script file that should be executed.
       action->setFile(filename);
       action->setFile(filename);
      // finally execute the scripting code.
       action->trigger();
       action->trigger();
    }
    }
    </code>
    </syntaxhighlight>


    The changes are addition of a text entry field and the reorganisation of logic handling the Kross::Action. Note also that there is no code in mainwindow.cpp to set the label, as there was in the previous tutorial. Instead, Objects are simply made available to the scripting interface through the <tt>action->addObject</tt> calls, without any knowledge of what the script will use them for. This removes the need to know at the time of writing the application what function the scripts will perform, and is therefore suited to a plugin interface.
    The changes are addition of a text entry field and the reorganisation of logic handling the Kross::Action. Note also that there is no code in mainwindow.cpp to set the label, as there was in the previous tutorial. Instead, Objects are simply made available to the scripting interface through the <tt>action->addObject</tt> calls, without any knowledge of what the script will use them for. This removes the need to know at the time of writing the application what function the scripts will perform, and is therefore suited to a plugin interface.
    Line 108: Line 119:
    This script catches the textChanged SIGNAL of the QLineEdit, and connects it to a simple python function to reverse the string in the QLineEdit and display it in the QLabel.
    This script catches the textChanged SIGNAL of the QLineEdit, and connects it to a simple python function to reverse the string in the QLineEdit and display it in the QLabel.


    <code python>
    <syntaxhighlight lang="python">
    #!/usr/bin/env kross
    #!/usr/bin/env kross


    Line 120: Line 131:


    MyInputString.connect("textChanged(const QString &)", reverseString)
    MyInputString.connect("textChanged(const QString &)", reverseString)
    </code>
    </syntaxhighlight>


    === krossSigsSlots.js ===
    === krossSigsSlots.js ===
    This script does the same like the script above but using the JavaScript scripting language.
    This script does the same as the script above but using the JavaScript scripting language.


    <code javascript>
    <syntaxhighlight lang="javascript">
    function reverseString(s){
    function reverseString(s){
         MyLabel.text = s.split("").reverse().join("");
         MyLabel.text = s.split("").reverse().join("");
    Line 132: Line 143:
    connect(MyInputString, "textChanged(const QString &)", this, "reverseString(const QString &)");
    connect(MyInputString, "textChanged(const QString &)", this, "reverseString(const QString &)");


    </code>
    </syntaxhighlight>
     
    {{TODO|Write similar for other interpreters}}


    == Autoconnecting Signals and Slots ==
    == Autoconnecting Signals and Slots ==
    Line 138: Line 151:
    It is possible to connect signals from your Object to slots in your script file automatically. Edit the mainwindow.cpp file to add another argument to the addObject function:
    It is possible to connect signals from your Object to slots in your script file automatically. Edit the mainwindow.cpp file to add another argument to the addObject function:


    <code cpp>
    <syntaxhighlight lang="cpp">
    action->addObject(txtInputString, "MyInputString",
    action->addObject(txtInputString, "MyInputString",
                       Kross::ChildrenInterface::AutoConnectSignals);
                       Kross::ChildrenInterface::AutoConnectSignals);
    Line 144: Line 157:
    action->addObject(cmbInterpreters, "MyInterpreter");
    action->addObject(cmbInterpreters, "MyInterpreter");
    action->addObject(lblMessage, "MyLabel");
    action->addObject(lblMessage, "MyLabel");
    </code>
    </syntaxhighlight>
    The <tt>Kross::ChildrenInterface::AutoConnectSignals</tt> argument causes signals of the object to be automatically connected with scripting functions of the same name. Therefore, the scripts can again be simplified:
    The <tt>Kross::ChildrenInterface::AutoConnectSignals</tt> argument causes signals of the object to be automatically connected with scripting functions of the same name. Therefore, the scripts can again be simplified:


    === Simplified scripts ===
    === Simplified scripts ===
    The python function reverses the string:
    The following python code provides a function that reverses the string:
    <code python>
    <syntaxhighlight lang="python">
    #!/usr/bin/env kross
     
    import MyLabel
    import MyLabel


    Line 157: Line 168:
       s = s[::-1]
       s = s[::-1]
       MyLabel.text = s
       MyLabel.text = s
    </syntaxhighlight>


    </code>
    Now follows a javascript function that converts the string to [http://en.wikipedia.org/wiki/Pig_Latin pig latin]:


    The javascript function converts the string to [[pig latin]]:
    <syntaxhighlight lang="javascript">
     
    <code javascript>
    function textChanged(text)
    function textChanged(text)
    {
    {
       text = text.replace(/\b([aeiou][a-z]*)\b/gi, "$1way"); // Rule 2
       text = text.replace(/\b([aeiou][a-z]*)\b/gi, "$1way"); // Rule 2
       pigLatin = text.replace(/\b([bcdfghjklmnpqrstvwxy]+)([a-z]*)\b/gi, "$2$1ay"); // Rule 1
       pigLatin = text.replace(/\b([bcdfghjklmnpqrstvwxyz]+)([a-z]*)\b/gi, "$2$1ay"); // Rule 1
      MyLabel.text = pigLatin;
    }
    </syntaxhighlight>
     
    {{TODO|Write similar for other interpreters}}


       MyLabel.text = pigLatin;
    [[Image:Krosssigsandslots1.png]]
     
    == Emitting signals from within scripts ==
     
    So far this tutorial has described connecting signals in c++ objects with slots in scripts. The objects published to kross scripts also make their signals available to the scripts. The signals can then be emitted by calling them. This is equivalent to calling:
     
    <syntaxhighlight lang="cpp">
      emit signalName();
    </syntaxhighlight>
    in c++ code. To illustrate this, change the python script to emit the setEnabled(bool) signal of the QCombobox:
     
    <syntaxhighlight lang="python">
    #!/usr/bin/env kross
     
    import MyLabel
    import MyInterpreter
     
    def textChanged(s):
      if s == "off":
        MyInterpreter.setEnabled(False)
      elif s == "on":
        MyInterpreter.setEnabled(True)
      s = MyInputString.text[::-1]
       MyLabel.text = s
    </syntaxhighlight>
     
    If 'off' is written in the text field, the setEnabled signal is emitted and the combobox is greyed out. It is not re-enabled again until 'on' is written in the text field.


    }
    This is a simple demonstration of signal-slot relationships using kross. More complex interfaces may be written to complete a plugin architecture.
    </code>

    Latest revision as of 08:16, 31 May 2019

    Hello world in kross
    Tutorial Series   Kross tutorials
    Previous   Kross Hello World
    What's Next   Scripts as plugins
    Further Reading   n/a
    Warning
    This page needs a review and probably holds information that needs to be fixed.

    Parts to be reviewed:

    Port to KF5

    This tutorial shows how to use a system of signals and slots to provide a scripting interface for a KDE application. It builds upon the Kross Hello World tutorial and again follows a 'Hello World' type format.

    Update source files

    This tutorial is based on the Hello World tutorial and extends the codebase we wrote there with new functionality.

    mainwindow.h

    First edit the mainwindow.h to handle the changes to mainwindow.cpp. Note that the change is the addition of a private Kross::Action .

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
     
    #include <QComboBox>
    #include <QLabel>
    #include <QLineEdit>
    
    #include <kross/core/action.h>
     
    // The main window to display our combobox and the label.
    class MainWindow : public QWidget
    {
        Q_OBJECT
      public:
        // The constructor.
        MainWindow(QWidget *parent=0);
      private Q_SLOTS:
        // This slot is called when the item in the combobox is changed.
        void interpreterActivated(const QString &);
      private:
        QLineEdit* txtInputString;
        QLabel* lblMessage;
        QComboBox* cmbInterpreters;
        // We now have the action as class-member.
        Kross::Action* action;
    };
    
    #endif
    

    mainwindow.cpp

    Using the same CmakeLists.txt and main.cpp from the previous Hello World tutorial, edit the mainwindow.cpp as follows:

    #include "mainwindow.h"
    
    #include <QVBoxLayout>
    #include <QDebug>
    
    #include <kross/core/manager.h>
    #include <kross/core/action.h>
    
    // the constructor.
    MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
    {
      txtInputString = new QLineEdit();
      lblMessage = new QLabel("Hello");
      cmbInterpreters = new QComboBox ();
      cmbInterpreters->addItem("Choose Interpreter", "");
    
      foreach(QString s, Kross::Manager::self().interpreters())
        cmbInterpreters->addItem(s);
    
      connect(cmbInterpreters, SIGNAL(activated(const QString &)),
          SLOT(interpreterActivated(const QString &)));
    
      QVBoxLayout *vLayout = new QVBoxLayout;
      vLayout->addWidget(cmbInterpreters);
      vLayout->addWidget(txtInputString);
      vLayout->addWidget(lblMessage);
      setLayout(vLayout);
    
      // This time we create the Kross::Action already within the
      // constructor and add the objects that should be accessible
      // from within scripting code.
      action = new Kross::Action(this, "MyScript");
    
      action->addObject(txtInputString, "MyInputString");
      action->addObject(cmbInterpreters, "MyInterpreter");
      action->addObject(lblMessage, "MyLabel");
    }
    
    // this slot is called when the active item of the combobox changes.
    void MainWindow::interpreterActivated(const QString &strSelectedInterpreter)
    {
      if(strSelectedInterpreter.isEmpty()) {
        lblMessage->setText("-");
        return;
      }
    
      // this time we are using external script files.
      QString filename;
      if(strSelectedInterpreter == "python")
        filename = "krossSigsSlots.py";
      else if(strSelectedInterpreter == "javascript")
        filename = "krossSigsSlots.js";
      else
        return;
    
      // set the script file that should be executed.
      action->setFile(filename);
      // finally execute the scripting code.
      action->trigger();
    }
    

    The changes are addition of a text entry field and the reorganisation of logic handling the Kross::Action. Note also that there is no code in mainwindow.cpp to set the label, as there was in the previous tutorial. Instead, Objects are simply made available to the scripting interface through the action->addObject calls, without any knowledge of what the script will use them for. This removes the need to know at the time of writing the application what function the scripts will perform, and is therefore suited to a plugin interface.

    krossSigsSlots.py

    This script catches the textChanged SIGNAL of the QLineEdit, and connects it to a simple python function to reverse the string in the QLineEdit and display it in the QLabel.

    #!/usr/bin/env kross
    
    import MyLabel
    import MyInterpreter
    import MyInputString
    
    def reverseString(s):
      s = s[::-1]
      MyLabel.text = s
    
    MyInputString.connect("textChanged(const QString &)", reverseString)
    

    krossSigsSlots.js

    This script does the same as the script above but using the JavaScript scripting language.

    function reverseString(s){
        MyLabel.text = s.split("").reverse().join("");
    }
    
    connect(MyInputString, "textChanged(const QString &)", this, "reverseString(const QString &)");
    
    noframe
    noframe
     
    TODO
    Write similar for other interpreters

    Autoconnecting Signals and Slots

    It is possible to connect signals from your Object to slots in your script file automatically. Edit the mainwindow.cpp file to add another argument to the addObject function:

    action->addObject(txtInputString, "MyInputString",
                      Kross::ChildrenInterface::AutoConnectSignals);
    
    action->addObject(cmbInterpreters, "MyInterpreter");
    action->addObject(lblMessage, "MyLabel");
    

    The Kross::ChildrenInterface::AutoConnectSignals argument causes signals of the object to be automatically connected with scripting functions of the same name. Therefore, the scripts can again be simplified:

    Simplified scripts

    The following python code provides a function that reverses the string:

    import MyLabel
    
    def textChanged(s):
      s = s[::-1]
      MyLabel.text = s
    

    Now follows a javascript function that converts the string to pig latin:

    function textChanged(text)
    {
      text = text.replace(/\b([aeiou][a-z]*)\b/gi, "$1way"); // Rule 2
      pigLatin = text.replace(/\b([bcdfghjklmnpqrstvwxyz]+)([a-z]*)\b/gi, "$2$1ay"); // Rule 1
      MyLabel.text = pigLatin;
    }
    
    noframe
    noframe
     
    TODO
    Write similar for other interpreters

    Emitting signals from within scripts

    So far this tutorial has described connecting signals in c++ objects with slots in scripts. The objects published to kross scripts also make their signals available to the scripts. The signals can then be emitted by calling them. This is equivalent to calling:

      emit signalName();
    

    in c++ code. To illustrate this, change the python script to emit the setEnabled(bool) signal of the QCombobox:

    #!/usr/bin/env kross
    
    import MyLabel
    import MyInterpreter
    
    def textChanged(s):
      if s == "off":
        MyInterpreter.setEnabled(False)
      elif s == "on":
        MyInterpreter.setEnabled(True)
      s = MyInputString.text[::-1]
      MyLabel.text = s
    

    If 'off' is written in the text field, the setEnabled signal is emitted and the combobox is greyed out. It is not re-enabled again until 'on' is written in the text field.

    This is a simple demonstration of signal-slot relationships using kross. More complex interfaces may be written to complete a plugin architecture.