4.3.1. Introduction

Widgets are the control and display elements of graphical application. Most of the visible elements are widgets. They are buttons, combo boxes, line edit, tables that make up the GUI. They have drawing instructions, and interaction instructions.

Widget can also contain another widgets: these are called container widgets, and examples of them are frames, and group boxes. In these widget, you can put other widgets, even another container. This allows the developer to create a structure or organization for the GUI.

In this document, we will see how to create new widgets, what different artifacts should produce, and how to integrate them into a WAF project.

Though this tutorial will start gradually, we present you the final structure of a widget library:

widget_library/
├── bindings
│   ├── bin
│   │   └── generate
│   ├── src
│   │   ├── bindings.h
│   │   └── bindings.xml
│   └── wscript
├── plugin
│   ├── src
│   │   ├── include
│   │   │   └── cut
│   │   │       └── examples
│   │   │           └── widgets
│   │   │               └── plugin
│   │   │                   ├── QeExampleDmsLabelPlugin.hpp
│   │   │                   └── QeExamplesWidgetCollection.hpp
│   │   ├── QeExampleDmsLabelPlugin.cpp
│   │   └── QeExamplesWidgetCollection.cpp
│   └── wscript
├── showcase
│   ├── src
│   │   ├── include
│   │   │   └── cut
│   │   │       └── examples
│   │   │           └── widgets
│   │   │               └── showcase
│   │   │                   ├── mainwindow.hpp
│   │   │                   └── mainwindow.ui
│   │   ├── main.cpp
│   │   └── mainwindow.cpp
│   └── wscript
├── showcasepy
│   ├── src
│   │   ├── cut
│   │   │   └── examples
│   │   │       └── widgets
│   │   │           └── showcasepy
│   │   │               ├── __init__.py
│   │   │               ├── mainappwindow.py
│   │   │               └── mainwindow.ui
│   │   └── showcasepy.py
│   └── wscript
├── taurus
│   ├── src
│   │   └── cut
│   │       └── examples
│   │           └── widgets
│   │               └── taurus
│   │                   ├── __init__.py
│   │                   ├── taurusexampledmslabel.py
│   │                   └── taurusexamplesimpledmslabel.py
│   └── wscript
├── widgets
│   ├── src
│   │   ├── include
│   │   │   ├── cut
│   │   │   │   └── examples
│   │   │   │       └── widgets
│   │   │   │           └── widgets
│   │   │   │               └── QeExampleDmsLabel.hpp
│   │   │   └── QeExamplesWidgets.h
│   │   └── QeExampleDmsLabel.cpp
│   └── wscript
└── wscript

The project shall be called “libraryname”. It is also how the module for this library is called. The project will produce the following artefacts:

  • A c++ shared library, with the widgets.

  • A c++ shared library that is a plugin for the Qt Designer.

  • A c shared library, that is a CPython module (widgets bound to python)

  • A cpp application, that shows how to use the widgets

  • A python application, that shows how to use the widgets.

  • A python module that provides declarative databinding widgets using Taurus.

Tip

When developing widgets, you can forgo for a while the plugin. You can use Widget Promotion in the Qt Designer in the meantime. But in order to allow other developers to design Uis using the widgets, you need to provide a plugin for the Designer.

Warning

Please do not bundle the widget and the plugin together in one WAF module.

Warning

If you have not programmed a graphical application before, do not start with this document. Creating widgets is not the starting point to learn how to write graphical application.

If you need a starting point, we recommend the Python Application Tutorial.

Tip

If you have already read this document, we recommend the use of CUT templates to produce a skeleton widget library:

(base) [eeltdev@eltdev ~]$ git clone https://gitlab.eso.org/ahoffsta/cut.git
(base) [eeltdev@eltdev tmp]$ cookiecutter ../repos/cut/templates/widget_library

project_name [CUT Widget Library]:
project_name_slug [cut-widget-library]:
project_version [0.0.1-dev]:
widget_name [CutDartBoard]:
object_name [cutDartBoard]:
lib_name [CutWidgets]:
pkg_name [cutwidgets]:
showcase_name [CutWidgetsShowcase]:

(base) [eeltdev@eltdev ~]$ cd cut-widget-library
(base) [eeltdev@eltdev ~]$ waf configure build install
(base) [eeltdev@eltdev ~]$ CutWidgetsShowcase
(base) [eeltdev@eltdev ~]$ CutWidgetsShowcasePy

4.3.2. Kind of Widgets

Before we start developing new widgets, the reader needs to determine what is actually needed. We strongly suggest to take a look into Qt widget library and CCS UI Toolkit widget library before starting to develop a new widget. Sometime with the correct configuration, the necessary functionality can be achieved.

If this is not the case, then the next step would be: what kind of widget is needed?

  • Extending an existing one: A similar widget exists, but does not provide a way to achieve what is needed, or requires too much boilerplate code. For example, a Label that presents an angle in DMS units.

  • A drawing widget: The form or graphical design of the required widget is complex. No present widgets can achieve this, or requires much instructions. For example, a DartBoard, which is a target-like representation of radial plots.

  • A composite widget: The developer can achieve this through Frames or GroupBoxes, but it is required in so many places of the application, that refactoring it to a composite widget is better for code separation and maintenance.

Once the developers has in mind what is needed to be accomplished, we can continue. Start with one widget. The example in which this code is based, and also a later section of this document will expand on what is needed for multiple widgets.

Tip

For an extended discussion on how to create the different kind of widgets, please refer to: Widget Creation

4.3.2.1. WAF Project

We recommend to separate widgets from applications. Widgets should be self contained, and have a clear interface. You can put them in a separate project, request them to be integrated into CCS UI Toolkit, or in a package in your application. For sake of simplicity, and that this tutorial can be reproduced, we will create a new project.

The starting structure of the project is the following one:

cut-widget-library/
├── widgets
│   ├── src
│   │   ├── CutDmsLabel.cpp
│   │   └── include
│   │       ├── CutWidgets
│   │       │   └── CutDmsLabel.hpp
│   │       └── CutWidgets.h
│   └── wscript
└── wscript

The project directory is called cut-widget-library, and has one module, called widgets. This will be the first artifact this project produces, and it is a shared library. But first, lets see the contents of the top-level wscript: cut-widget-library/wscript.

 1"""
 2@file
 3@copyright ESO - European Southern Observatory
 4
 5@defgroup CUT Widget Library
 6@brief This project creates from a template a library to provide new widgets.
 7"""
 8
 9from wtools.project import declare_project
10
11def configure(cnf):
12    # NOTE: WAF autoimports Core Gui and Widgets. But eventually this will be removed.
13    #       developers should always explicitly declared dependencies.
14    for pkg in 'Qt5Core Qt5Gui Qt5Widgets'.split():
15        cnf.check_cfg(package=pkg, uselib_store=pkg)
16    cnf.check_cfg(package='slalib_c', uselib_store='slalib_c', args='--cflags --libs')
17
18declare_project('cut-widget-library', '0.0.1-dev',
19    requires='cxx qt5 python pyqt5',
20    recurse='widgets',
21    cxx_std='c++1z')

The wscript for the project start with depedencies resolution. It looks for the basic libraries needed for widget development: Qt5Core, Qt5Gui and Qt5Widgets. If you need extra modules from Qt, or another library, like slalib_c, please add them here.

The next method is declare_project(). We will skip most of the declaration (for this, please refer to: WAF Tools documentation) except for requires=’cxx qt5 python pyqt5’. The project is being set up to include support for C++, Python, Qt libraryes, and Python version of the Qt libraries. The pyqt5 name is unfortunate, but it is actually PySide2 libraries being used. Please refer to the PySide2 and PyQt5 sidebar for more information.