Chapter 16
Qt_widget

Laurent Rineau and Radu Ursu

A Nice Screen Shoot

Qt is a GUI toolkit1 for cross-platform application development.

16.1   Introduction

This chapter describes the Qt_widget package which provides an interface between CGAL and the GUI toolkit Qt . The Qt_widget package allows to build Qt applications showing two dimensional CGAL objects and algorithms.

The atom of the Qt user interface is called a widget. A widget receives mouse, keyboard and other events from the window system, and paints a representation of itself on the screen. Widgets are rectangular, and the different widgets of an application are sorted in a Z-order. Widgets can have a parent widget and children. A widget is clipped by its parent and by the widgets in front of it.

The most important class in the package is the class Qt_widget which implements a widget providing a drawing area and output stream operators for CGAL two dimensional objects. Qt_widget also provides zooming and panning functionalities.

The Qt_widget allows to attach layers. Layers usually draw on the drawing area of the widget. Layers can be activated and deactivated, and what you see in the drawing area is the overlay of all attached activated layers. Layers can also be used for entering input, and CGAL provides input layers for the two-dimensional CGAL objects.

The package includes also the class Qt_widget_standard_toolbar providing a standard toolbar for controlling the basic functionality of the Qt_widget.

The following sections describe the main class as well as the helper classes in more detail and give examples that can be taken as starting points for new applications.

Remark: The Qt_widget is distributed under the QPL, which is Trolltech's open source license. For more details on the QPL see http://www.trolltech.com/developer/licensing/qpl.html.

16.2   Qt_widget

The class Qt_widget is derived from the class QWidget which is the base class of all Qt user interface objects.

The Qt_widget provides output operators for two dimensional CGAL objects. There are operators defined for output of: points, segments, lines, rays, circles, triangles, rectangles, polygons, conics, and all type of triangulations. Also some operators are defined to set Qt_widget's properties, like background and fill color, as well as line width and point size.

As the following examples show, simple applications can be written without the layers.

16.2.1   Example: Hello Segment

The first example draws a red segment on an orange background.
//demo/Qt_widget/Examples/hellosegment.C
#ifndef CGAL_USE_QT
#include <iostream>
int main(int, char*){
  std::cout << "Sorry, this demo needs QT..." << std::endl; return 0;}
#else
#include <CGAL/Cartesian.h>
#include <CGAL/IO/Qt_widget.h>

#include <qapplication.h>

typedef CGAL::Cartesian<int> Rep;
typedef CGAL::Point_2<Rep> Point_2;
typedef CGAL::Segment_2<Rep> Segment;

int main( int argc, char **argv )
{
  QApplication app( argc, argv );
  CGAL::Qt_widget *w = new CGAL::Qt_widget();
  app.setMainWidget( w );
  w->resize(600, 600);
  w->set_window(0, 600, 0, 600);
  w->show();
  w->lock();
  *w << CGAL::BackgroundColor(CGAL::ORANGE) << CGAL::RED;
  *w << Segment(Point_2(100,100), Point_2(400,400));
  w->unlock();
  return app.exec();
}
#endif

We follow the Qt naming conventions for material properties, for example, the CGAL::BackgroundColor above.

All the drawing code should be put between Qt_Widget's lock() and unlock() functions. See the manual reference pages of Qt_widget. Doing like this, the window will be updated only once, when Qt_widget finds the last unlock(). This way you can avoid the window flickering.

This example has a severe drawback: when you resize the window it is empty, as nothing is redrawn. This style of programs makes only sense, if you quickly want to validate output of a geometric computation. As in any event driven GUI application, Qt provides a callback mecanism so that the window system can update the drawing whenever necessary. This is the topic of the next example.

16.2.2   Example: Signals and Slots

This example is slightly more involved and uses the signal/slots mecanism of Qt .

The main widget shows a Delaunay triangulation. Every time the mouse button is pressed over the widget, a point is input and inserted in the Delaunay triangulation. The result of this insertion appears immediately. Furthermore, the drawing is updated every time the window is resized.

//demo/Qt_widget/Examples/tutorial2.C
#ifndef CGAL_USE_QT
#include <iostream>
int main(int, char*){
  std::cout << "Sorry, this demo needs QT..." << std::endl; return 0;}
#else
#include <CGAL/Cartesian.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/IO/Qt_widget_Triangulation_2.h>
#include <CGAL/IO/Qt_widget.h>
#include <qapplication.h>
#include <qmainwindow.h>

typedef CGAL::Cartesian<double>             K;
typedef K::Point_2                          Point_2;
typedef CGAL::Delaunay_triangulation_2<K>   Delaunay;

Delaunay dt;

class My_window : public QMainWindow {
  Q_OBJECT
public:
  My_window(int x, int y)
  {
    widget = new CGAL::Qt_widget(this);
    widget->resize(x,y);
    widget->set_window(0, x, 0, y);

    connect(widget, SIGNAL(redraw_on_back()),
	   this, SLOT(redraw_win()));

    connect(widget, SIGNAL(s_mousePressEvent(QMouseEvent*)),
	    this, SLOT(mousePressEvent(QMouseEvent*)));

    setCentralWidget(widget);
  };
private slots:  
  void redraw_win()
  {
    *widget << dt;
  }

  void mousePressEvent(QMouseEvent *e)
  {
    dt.insert(Point_2(widget->x_real(e->x()), widget->y_real(e->y())));
    widget->redraw();
  }

private: // private data member
  CGAL::Qt_widget* widget;
};

//moc_source_file : tutorial2.C
#include "tutorial2.moc"

int main( int argc, char **argv )
{
    QApplication app( argc, argv );
    My_window *w = new My_window(400,400);
    app.setMainWidget( w);
    w->show();
    return app.exec();
}
#endif

Qt applications are event driven and respond to user interaction. For example, when a user clicks on a menu item or on a toolbar button, the application executes some codes. The programmer of an application has to be able to relate events to the relevant code. Qt provide for that the signals/slots mecanism:

  • Signals. Each Qt widget declares a set of signals which, using the keyword emit can be emitted by member functions under some circumstances. Signals are declared by using the keyword signals: just like an access specifier in your class declaration.
  • Slots. A slot is just a member function declared under a public (or private) slots section.
  • Connect. Signals and slots can be connected together using the method connect. This method needs to know four things: the object that sends out the signal, the signal, the object to which belong the connected slot and the slot connected to the signal. For instance, the statement:
    connect(widget, SIGNAL(redraw_on_back()),
            this, SLOT(redraw_win()));
    

    connects the signal redraw_on_back() of the widget widget to the slot redraw_win() of the QMainWindow his. Signals and slots can have any type of arguments, but a signal and a slot connected together must have the same arguments types.

  • Every class that defines at least a signal or a slot must be derived from the class QObject and must use the macro Q_OBJECT inside the private section of its declaration. You also need to run moc, the Meta Object Compiler supplied with Qt on the file that contains the class declaration.

    moc is a pre-compiler that produces the meta object code of each class that uses the macro Q_OBJECT. This meta object code is needed by the signal/slot mecanism. There are several methods to compile the outputs of moc. In CGAL, we have chosen to include the outputs of moc in a source files. See the Qt documentation on moc for other possibilities.

    The line //moc_source_file : tutorial2.C is for users that use makefiles. This line tells to the CGAL makefile generator that tutorial2.C should be the file that moc should be run on.

    Let us come back to the control flow in the above example. The main widget of the application is the widget w of class My_window. The creator of My_window triggers the creation of a Qt_widget accessible through the pointer widget. When the mouse button is pressed in its drawing area, the Qt_widget emits the signal s_mousePressEvent(QMouseEvent*) connected to the slot mousePressEvent(QMouseEvent*) of My_window. This slot inserts the point in the Delaunay triangulation and calls the method redraw() of Qt_widget. The redraw() method of Qt_widget emits the signal redraw_on_back(). This signal is connected to the slot redraw_win() of My_window which actually draws the triangulation. Note that the Qt_widget emits the same signal redraw_on_back() when the window is resized. Thus, the signals/slots connection ensures that the triangulation is redrawn each time the redrawing is needed.


    begin of advanced section  advanced  begin of advanced section
    There are several ways to draw something with Qt_widget. One way is to use the signals redraw_on_back(), redraw_on_front(). This way you can bring your drawings before all or after all the other drawings. An other option is to use the QPainter instance of Qt_widget that you can get calling get_painter() method. The most recommended way is to use layers, that are described in the next section.
    end of advanced section  advanced  end of advanced section

    Note that in that example, the My_window constructor calls new to allocate a new Qt_widget object but delete is never called to deallocate it. This does not mean that there is a memory leak. It is in Qt's responsability to free widgets that have a parent. In that example the My_window object is the parent of widget and will deallocate it automatically at its destruction.

    16.3   Layers

    16.3.1   Using Layers to Draw

    In the examples from the previous section the code for drawing on the widget was in the redraw_win() function. As soon as the applications are more involved it leads to a more modular design if one delegates the drawing task to layers. For example, if the application displays a Delaunay triangulation, the corresponding Voronoi diagram, and at the same time highlights the nearest vertex to the mouse coordinates, it makes sense to have three independent layers. Besides better code, layers have the advantage that they can be activated and deactivated at runtime. Finally, more modularity means a higher potential for reuse.

    A layer can be attached to a Qt_widget. The redraw() member function of the Qt_widget calls the method Qt_widget_layer::draw() of all attached layers, in the order that they were attached. It is a very simple rule: the last layer attached will be drawn on top.

    Also a layer can be activated and deactivated. Only active layers are drawn, and by default a layer is activated when it gets attached. Note that deactivating and activating do not influence the order of layers. You can change the order only by attaching and detaching it.

    CGAL provides a base class so that users can write their own layers. All the layers have to derive from this base class Qt_widget_layer to have the functionality described.

    16.3.2   Example: Using a Layer to Draw

    Example

    #include <CGAL/Cartesian.h>
    #include <CGAL/Point_2.h>
    #include <CGAL/Delaunay_triangulation_2.h>
    #include <CGAL/IO/Qt_widget_Delaunay_triangulation_2.h>
    #include <CGAL/IO/Qt_widget.h>
    #include <CGAL/IO/Qt_widget_layer.h>
    #include <qapplication.h>
    
    typedef CGAL::Cartesian<double>             Rep;
    typedef CGAL::Point_2<Rep>                  Point;
    typedef CGAL::Delaunay_triangulation_2<Rep> Delaunay;
    
    Delaunay dt;
    
    class My_layer : public CGAL::Qt_widget_layer{
      void draw(){
        *widget << dt;
      }
    };
    
    class My_window : public CGAL::Qt_widget {
    public:
      My_window(int x, int y)
      {
        resize(x,y);
        attach(&layer);
      };
    private:
      //this method is called when the user presses the mouse
      void mousePressEvent(QMouseEvent *e)
      {
        Qt_widget::mousePressEvent(e);
        dt.insert(Point(x_real(e->x()), y_real(e->y())));
        redraw();
      }
      My_layer layer;
    };
    
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        My_window *W = new My_window(400,400);
        app.setMainWidget(W);
        W->show();
        W->set_window(0, 400, 0, 400);
        return app.exec();
    }
    

    This example defines a class derived from Qt_widget_layer. In the member function draw() is the code for drawing the triangulation. In My_Window class you need an instance of My_Layer and you will have to attach it, if you want to see what the layer draws on the screen.

    As you see, this example is very similar to the previous one, but the code for drawing the triangulation is no longer in the redraw_win() function, but in a layer.

    16.3.3   Using Layers to Build New Objects

    The main purpose of layers is to have more modular code for drawing on the widget. Things are similar for handling input. In the previous examples, input went through the Qt_widget::mousePressedEvent() callback, which interpreted the input. In applications where you have different kinds of input, e.g., segments and polygons in an arrangement demo, this quickly leads to unreadable, difficult to maintain code, especially as typically more than one event callback is involved. The proper way of decomposition, is delegation of the event handling to a layer.

    A layer for Qt_widget receives all the events from Qt_widget if it is active and can provide some functionality like input objects for Qt_widget. Notice that the layers receive events in the order they have been attached. Layers can have internal state, because for entering complex objects it needs several events. Therefore layers must have functions to initialize state when they are activated and to clean up when they are deactivated.

    CGAL provides some predefined input layers. You can have a lot of layers attached that could be active in the same time. You have to take care how you manage the events if you do not want to have conflicts. A conflict is when two attached layers that are active need both the same event and getting and using it might not have such a good effect in your application. For example the predefined layers that builds a new line and a new circle produce bad visual effects when are both active.

    You can resolve conflicts by using layers exclusive. If you have several layers that can not be active at the same time without creating conflicts, you can resolve that by letting the user not activate more than one of those at a time.


    begin of advanced section  advanced  begin of advanced section
    In all the CGAL demos that are provided, the layers are used with a toolbar and buttons. To activate and deactivate a layer you have to click one of the toolbar buttons. There are layers that need exclusive use. This is accomplished by grouping the buttons in one group, and making that group exclusive. The group class is QButtonGroup that comes with Qt .
    end of advanced section  advanced  end of advanced section

    We first show how to use layers and then how they work.

    16.3.4   Example: How to Use a Layer

    In the previous section, you could insert new points in the triangulation by clicking on the widget. This example shows how the same can be achieved with the help of a layer.

    We attach the predefined layer Qt_widget_get_point to the widget, and connect the signal emitted by the widget to the function that handles the input. When the user clicks with the left mouse button, the layer creates a point and passes it to the widget. The widget then emits a signal that gets passed to the connected slot My_Window::get_new_object(CGAL::Object).

    //demo/Qt_widget/Examples/layer.C
    #ifndef CGAL_USE_QT
    #include <iostream>
    int main(int, char*){
      std::cout << "Sorry, this demo needs QT..." << std::endl; return 0;}
    #else
    #include <CGAL/Cartesian.h>
    #include <CGAL/Delaunay_triangulation_2.h>
    #include <CGAL/IO/Qt_widget_Delaunay_triangulation_2.h>
    #include <CGAL/IO/Qt_widget.h>
    #include <CGAL/IO/Qt_widget_layer.h>
    #include <CGAL/IO/Qt_widget_get_point.h>
    
    #include <qapplication.h>
    #include <qmainwindow.h>
    
    typedef CGAL::Cartesian<double>             Rep;
    typedef CGAL::Point_2<Rep>                  Point_2;
    typedef CGAL::Delaunay_triangulation_2<Rep> Delaunay;
    
    Delaunay dt;
    
    class My_Layer : public CGAL::Qt_widget_layer{
      void draw(){
        *widget << dt;
      }
    };
    
    class My_Window : public QMainWindow {
      Q_OBJECT
    public:
      My_Window(int x, int y){
        widget = new CGAL::Qt_widget(this, "CGAL Qt_widget");
        setCentralWidget(widget);
        resize(x,y);
        widget->attach(&get_point);
        widget->attach(&v);
        connect(widget, SIGNAL(new_cgal_object(CGAL::Object)), 
                this, SLOT(get_new_object(CGAL::Object)));
        widget->set_window(0, 600, 0, 600);
      };
    private:	//members
      CGAL::Qt_widget_get_point<Rep> get_point;
      My_Layer v;
      CGAL::Qt_widget *widget;
    private slots:
      void get_new_object(CGAL::Object obj)
      {
        Point_2 p;
        if (CGAL::assign(p, obj)) { 
          dt.insert(p);
        }
        widget->redraw();
      }
    }; //endclass
    
    //  moc_source_file : layer.C
    #include "layer.moc"
    
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        My_Window *W = new My_Window(600,600);
        app.setMainWidget( W );
        W->show();
        return app.exec();
    }
    #endif
    

    The Qt_widget forwards all events that it receives to the attached and active layers. If a layer is attached but not active, it does not get the events. It is put in a passive state. Activating and deactivating the layer does not mean that the object is destructed.

    16.3.5   Example: How Layers Work

    The following is an example of a layer that creates CGAL points when the user clicks the left mouse button over the widget.

    #include <CGAL/IO/Qt_widget.h>
    #include <CGAL/IO/Qt_widget_layer.h>
    #include <qcursor.h>
    
    #ifndef CGAL_QT_WIDGET_GET_POINT_BUTTON
    #define CGAL_QT_WIDGET_GET_POINT_BUTTON Qt::LeftButton
    #endif
    
    namespace CGAL {
    template <class R>
    class Qt_widget_get_point : public Qt_widget_layer
    {
    public:
      typedef typename R::Point_2   Point;
      typedef typename R::FT        FT;
      
      Qt_widget_get_point(const QCursor c=QCursor(Qt::crossCursor),
                          QObject* parent = 0, const char* name = 0) :
        Qt_widget_layer(parent, name), cursor(c) {};
      
    private:
      bool is_pure(Qt::ButtonState s){
        if((s & Qt::ControlButton) ||
           (s & Qt::ShiftButton) ||
           (s & Qt::AltButton))
          return 0;
        else
          return 1;
      }
      void mousePressEvent(QMouseEvent *e)
      {
        if(e->button() == CGAL_QT_WIDGET_GET_POINT_BUTTON
           && is_pure(e->state()))
        {
          FT x, y;
          widget->x_real(e->x(), x);
          widget->y_real(e->y(), y);
          widget->new_object(make_object(Point(x, y)));
        }
      };
      void activating()
      {
        oldcursor = widget->cursor();
        widget->setCursor(cursor);
      };
      
      void deactivating()
      {
        widget->setCursor(oldcursor);
      };
    
      QCursor cursor;
      QCursor oldcursor;
    };
    } // namespace CGAL
    

    The Qt_widget forwards mouse and keyboard events to the attached layer. In the above example only the mousePressEvent member function is overloaded.

    Tools that create new CGAL objects, must call the member function Qt_widget::new_object(CGAL::Object). The Qt_widget then emits the signal new_cgal_object(CGAL::Object). This signal can be routed to any slot of other object accepting a CGAL::Object, with the following connect statement:

    connect(qt_widget_ptr, SIGNAL(new_cgal_object(CGAL::Object)), 
            any_other_object_ptr, SLOT(any_other_slot(CGAL::Object)));
    

    The first argument must be a pointer to an instance of Qt_widget. In the example we connect it to MyWindow::get_new_object(CGAL::Object).

    16.4   The Standard Toolbar

    The Qt_widget allows to zoom and pan. This functionality is accessible through the class Qt_widget_standard_toolbar. The example further down shows how to use it in your application.

    The
standard toolbar

    The functionality of the toolbar is as follows from the left to right:

    History back:
    Go back into the transformation history
    History forth:
    Go forth into the transformation history
    Zoom In:
    The scaling factor is multiplied by two, keeping the same center.
    Zoom Out:
    The scaling factor is divided by two, keeping the same center.
    Point tool:
    Deactivate the layers corresponding to the three following buttons which form an exclusive group
    Focus on Point:
    Lets you choose the center of the region where you want to focus.
    Focus on the Region:
    The area in the rectangle that you selected will be magnified to best fit in the window.
    Hand Tool:
    Used for translate. Click to select the first point of translation and drag to select the second point.
    Mouse Coordinates Layer:
    Mouse coordinates are displayed on the status bar of your window. You can deactivate this layer if you click on it. To activate it again just click one more time.

    Example

    //demo/Qt_widget/Examples/Standard_toolbar.C
    #ifndef CGAL_USE_QT
    #include <iostream>
    int main(int, char*){
      std::cout << "Sorry, this demo needs QT..." << std::endl; return 0;}
    #else
    #include <CGAL/Cartesian.h>
    #include <CGAL/Delaunay_triangulation_2.h>
    #include <CGAL/IO/Qt_widget_Delaunay_triangulation_2.h>
    
    #include <qapplication.h>
    #include <qmainwindow.h>
    
    #include <CGAL/IO/Qt_widget.h>
    #include <CGAL/IO/Qt_widget_standard_toolbar.h>
    #include <CGAL/point_generators_2.h>
    
    typedef CGAL::Cartesian<double>             Rep;
    typedef CGAL::Point_2<Rep>                  Point_2;
    typedef CGAL::Delaunay_triangulation_2<Rep> Delaunay;
    
    Delaunay dt;
    
    class My_window : public QMainWindow{
      Q_OBJECT
    public:
      My_window(int x, int y)
      {
        widget = new CGAL::Qt_widget(this);
        setCentralWidget(widget);
        resize(x,y);
        widget->show();
        widget->set_window(0, x, 0, y);
    
        CGAL::Random_points_in_disc_2<Point_2> g(500);
        for(int count=0; count<100; count++) {
          dt.insert(*g++);
        }
        
        //How to attach the standard toolbar
        std_toolbar = new CGAL::Qt_widget_standard_toolbar(widget, this,
    						       "Standard Toolbar");
    
        connect(widget, SIGNAL(redraw_on_back()),
    	    this, SLOT(redraw_win()) );
      }
    
    private slots:	//functions
      void redraw_win()
      {
        *widget << dt;
      }
    
    private:	//members
      CGAL::Qt_widget *widget;
      CGAL::Qt_widget_standard_toolbar *std_toolbar;
    };
    
    // moc_source_file: standard_toolbar.C
    #include "standard_toolbar.moc"
    
    int main( int argc, char **argv )
    {
        QApplication app( argc, argv );
        My_window W(600,600);
        app.setMainWidget( &W );
        W.show();
        W.setCaption("Using the Standard Toolbar");
        return app.exec();
    }
    #endif
    

    This example generates 100 points and inserts them in a Delaunay triangulation. Using the standard toolbar you can zoom in, zoom out, translate.

    16.5   The Help Window

    We provide a class in the Qt_widget library that was taken from an example of Qt and adapted to our needs. This class has the functionality of a rich text browser with hypertext navigation. You can also PRINT, GO BACK, GO FORWARD or GO HOME. This class is called Qt_help_window and you can use it to display hypertext support in your application. It is used in a lot of demos provided in the distribution.

    Example

        #include <CGAL/IO/Qt_help_window.h>
            ....
        QString home = "help/index.html";
        Qt_help_window *help = new Qt_help_window(home, ".", 0, "help viewer");
        help->resize(400, 400);
        help->setCaption("Demo HowTo");
        help->show();
    

    16.6   Some Predefined Icons

    CGAL provides some icons defined in some header files. The icons are pixmaps, having the extension .xpm. Their location is /include/CGAL/IO/pixmaps.

    To use a pixmap in your code you have to include the right file, and to know the names of the pixmaps. The names of the pixmaps are composed of two parts, the name of the file and the tag xpm. So for example the arrow pixmap has the name arrow_xpm, the line pixmap has the name line_xpm, and so on. There are also pixmaps files that contain small icons. The name of the smaller pixmaps contain a small at the middle of it like point_small_xpm. In the tutorials and demos, almost all the pixmaps are used for the toolbar buttons, like this:

    Example

        
        #include <CGAL/IO/pixmaps/point.xpm>
        
        QIconSet set(QPixmap( (const char**)point_small_xpm ),
                      QPixmap( (const char**)point_xpm ));
    
        QToolButton *point_button;
        point_button = new QToolButton(toolbar_ptr, "POINT INPUT BUTTON");
        point_button->setIconSet(set);
        point_button->setTextLabel("POINT INPUT LAYER");
    
    

    16.7   What Shall I Use?

    The previous sections presented different ways of writing Qt based applications. We recommend to use layers for the drawing task and for input handling, even if you write tiny applications, because in general they grow over time. Layers are a little bit more overhead, but it pays off in the long run, as you then do not have to completely reorganize your code, to add layers.


    Footnotes

     1  http://www.trolltech.com