Difference between revisions of "Customize auto-generated GUIs with custom widgets"

From TASTE
Jump to: navigation, search
(Filling the skeleton)
(Filling the skeleton)
Line 67: Line 67:
 
     @staticmethod
 
     @staticmethod
 
     def editorIsApplicable(editor):
 
     def editorIsApplicable(editor):
         ''' Return true if this particular editor is compatible with this
+
         # Return true if this particular editor is compatible with this widget
        widget'''
 
 
         return editor.messageName == "Color"
 
         return editor.messageName == "Color"
  
Line 86: Line 85:
 
     @Slot()
 
     @Slot()
 
     def new_tm(self):
 
     def new_tm(self):
         ''' Slot called when a TM has been received in the editor '''
+
         # Slot called when a TM has been received in the editor
 
         pass
 
         pass
  
 
     def update(self, value):
 
     def update(self, value):
         ''' Receive ASN.1 value '''
+
         # Receive ASN.1 value
 
         color = value.Get()
 
         color = value.Get()
  
Line 109: Line 108:
 
         self.setWindowTitle(parent.treeItem.text())
 
         self.setWindowTitle(parent.treeItem.text())
 
         self.show()
 
         self.show()
 +
 +
== TC: sending messages to the running system ==
 +
 +
When you want to send a message, there are two cases:
 +
 +
1. The message has no parameter
 +
2. The message has a parameter
 +
 +
In the case of the simple button we want to add here to request the traffic light, there is no parameter. In the init function of the class, we have done the following:
 +
 +
    ...
 +
    self.widget = QPushButton("Request passage")
 +
    self.widget.clicked.connect(self.clicked)
 +
    self.setWidget(self.widget)
 +
 +
 +
... to create a simple button widget and associate the click the to function "self.clicked" we have to provide:
 +
 +
    def clicked(self):
 +
        ''' Called when user clicks on the button '''
 +
        self.parent.sendTC()
 +
 +
 +
As you can see the call to '''self.parent.sendTC()''' is the API to send the message to the running system.
 +
 +
 +
If there was a parameter, that would be very similar, except we would first need to fill in the ASN.1 value of the parameter before calling SendTC. The ASN.1 instance is stored in self.parent.asn1Instance.
 +
 +
You can see a full example with a parameter in the ~/tool-src/kazoo/test/tetris use case :
 +
 +
    def clicked(self, name):
 +
        ''' Called when user clicks on one of the buttons '''
 +
        if name == "Rotate":
 +
            self.parent.asn1Instance.Set(DV.rotate)
 +
        elif name == "Drop":
 +
            self.parent.asn1Instance.Set(DV.down)
 +
        elif name == "Right":
 +
            self.parent.asn1Instance.Set(DV.right)
 +
        elif name == "Left":
 +
            self.parent.asn1Instance.Set(DV.left)
 +
        self.parent.updateVariable()
 +
        self.parent.sendTC()
  
 
= Build the system =
 
= Build the system =

Revision as of 07:44, 23 May 2022

Introduction

This page explains how to generate custom user interfaces to interact with your application. This is based on the selection of the "GUI" implementation language for a function in the interface view:

ClipCapIt-220523-085615.PNG

By default this has the effect of generating for you a complete user interface with editors for filling data and exchanging messages with a running system:

ClipCapIt-220523-085109.PNG

This interface is convenient as it comes with built-in features such as plotting data and MSC tracing. However in some cases you may want to customize this interface, for example by adding your own widgets and make a better looking dashboard for the control of your system.

In this page we will see how to do that very easily using TASTE.

How to create user-defined widgets ?

When the auto-generated GUI executes, the first thing it looks for is the presence of user-defined widgets. Creating these widgets is done the same was as for the implementation of other functions in the system. Right click on the GUI component and select "Edit implementation" as you would do for any other function:

ClipCapIt-220523-090029.PNG


This will open a Python editor in Space Creator, with a documented template (code skeleton) to let you create your own GUI, based on the Qt bindings for Python (Pyside):

ClipCapIt-220523-090844.PNG


Filling the skeleton

The User Widgets skeletons allows you to create one class for each TC (message sent from the GUI to the running system) and one per TM (message received from the system). Inside these class you are free to create as many widgets as you want. There are just a few methods you must provide to activate your class and associate it with the right message.

We will use this example as an illustration of the process:

~/tool-src/kazoo/test/TrafficLight_Basic

It is a simple traffic light controller. We will create a custom GUI that shows the traffic light for the cars, the one for the pedestrian, and the button to request a stop signal to the cars. We will associate these three elements to three of the messages defined in the interface view, therefore we will create three classes:

   class PedestrianButton(UserWidgetsCommon.TC):
      ...
   class TrafficLight(UserWidgetsCommon.TM):
      ...
   class Pedestrian(UserWidgetsCommon.TM):
      ...

Depending on the direction of the message, the class with either inheric UserWidgetsCommon.TC or UserWidgetCommon.TM.

Before we enter the details of these class we have to make sure they are exposed to the main generated GUI by adding at the beginning of the file:

   __all__ = ['PedestrianButton', 'TrafficLight', 'Pedestrian']


Then let's look at the mandatory attributes and functions of the class:

Name/Description

Give a value to the "name" attribute. This will be used by the main GUI to let the user activate the widget.

   class TrafficLight(UserWidgetsCommon.TM):
       name = 'View Traffic Light'  # name for the combo button in the GUI

Association with a message

This is one of the most important attribute. The parameter is given by the main GUI and corresponds to the ASN.1 value editor. It holds a message name - each of your custom classes will be called with all possible messages. Using this function you can associate your class to one specific message:

   @staticmethod
   def editorIsApplicable(editor):
       # Return true if this particular editor is compatible with this widget
       return editor.messageName == "Color"


Automatic execution

When the main GUI starts you can decide if you want your class/widgets to be created automatically, or let the user trigger it (based on the "name" attribute specified before).

   @staticmethod
   def run_at_startup():
       return True

Reception of a message (TM)

Two functions will be called when the running system sends data to your GUI. The first one is a Qt signal (thread-safe) while the second one contains the ASN.1 value of the parameter. You can use it to update your widgets:

   @Slot()
   def new_tm(self):
       # Slot called when a TM has been received in the editor
       pass
   def update(self, value):
       # Receive ASN.1 value
       color = value.Get()

Class initialization

The class will be instantiated only if "editorIsApplicable()" as returned true. You are free to create your own widgets in the init method. For example:

   def __init__(self, parent=None):
        Initialise the widget 
       super(TrafficLight, self).__init__(parent)
       self.widget = QLabel()
       self.colorMap = { DV.red:    QPixmap ("red.png"),
                         DV.orange: QPixmap ("orange.png"),
                         DV.green:  QPixmap ("green.png") }
       self.widget.setPixmap (self.colorMap[DV.red])
       self.setWidget(self.widget)
       self.setWindowTitle(parent.treeItem.text())
       self.show()

TC: sending messages to the running system

When you want to send a message, there are two cases:

1. The message has no parameter 2. The message has a parameter

In the case of the simple button we want to add here to request the traffic light, there is no parameter. In the init function of the class, we have done the following:

   ...
   self.widget = QPushButton("Request passage")
   self.widget.clicked.connect(self.clicked)
   self.setWidget(self.widget)


... to create a simple button widget and associate the click the to function "self.clicked" we have to provide:

   def clicked(self):
        Called when user clicks on the button 
       self.parent.sendTC()


As you can see the call to self.parent.sendTC() is the API to send the message to the running system.


If there was a parameter, that would be very similar, except we would first need to fill in the ASN.1 value of the parameter before calling SendTC. The ASN.1 instance is stored in self.parent.asn1Instance.

You can see a full example with a parameter in the ~/tool-src/kazoo/test/tetris use case :

   def clicked(self, name):
        Called when user clicks on one of the buttons 
       if name == "Rotate":
           self.parent.asn1Instance.Set(DV.rotate)
       elif name == "Drop":
           self.parent.asn1Instance.Set(DV.down)
       elif name == "Right":
           self.parent.asn1Instance.Set(DV.right)
       elif name == "Left":
           self.parent.asn1Instance.Set(DV.left)
       self.parent.updateVariable()
       self.parent.sendTC()

Build the system

When you have designed your system with at least a GUI component, build it:

 $ make

Then go into the binary folder

$ cd work/binaries


You will see that a subfolder has been created for each GUI component of the system (here, the function is named user):

ClipCapIt-200907-130644.PNG

Inside this folder, a user-customizable file has been generated: UserWidgets.py

It contains all the necessary interface to create your own widgets, that you can associate to all the PIs and RIs connected to this function.

Add widgets: step by step

Name your classes and expose them

You will create one class (at least) per PI and RI to handle all the messages. Give them a name and expose them by adding them to the __all__ module variable:

ClipCapIt-200907-131153.PNG

Create the classes

The class need to inherit either from UserWidgetsCommon.TC or UserWidgetsCommon.TM, for exanmple:

ClipCapIt-200907-131748.PNG

Let's now explore the content of such a class:

ClipCapIt-200907-131927.PNG


In the editorIsApplicable method, you can associate one specific TC to this class. editor.messageName is the name of the required interface.

In the __init__ function you can create your layout and add widgets at will. The self.parent variable is the ASN.1 Value Editor that appears on the GUI, which allows you to send the TC to the system (via the call to self.parent.sendTC).


The TM work similarly:

ClipCapIt-200907-132653.PNG


Run the system and activate the custom widgets

When you are done with your custom design you may run the main GUI and from there you will see the custom widgets:

ClipCapIt-200907-133239.PNG