QT is a GUI toolkit1 for cross-platform application development.
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.
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.
File: demo/Qt_widget/hellosegment.cpp
#include <CGAL/basic.h> #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 mechanism so that the window system can update the drawing whenever necessary. This is the topic of the next example.
This example is slightly more involved and uses the signal/slots mechanism 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.
File: demo/Qt_widget/tutorial2.cpp
#include <CGAL/basic.h> #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.cpp #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 mechanism:
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 mechanism. 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.cpp is for users that use makefiles. This line tells to the CGAL makefile generator that tutorial2.cpp 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.
advanced |
advanced |
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.
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.
#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.
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.
advanced |
advanced |
We first show how to use layers and then how they work.
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).
File: demo/Qt_widget/layer.cpp
#include <CGAL/basic.h> #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.cpp #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.
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).
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 functionality of the toolbar is as follows from the left to right:
File: demo/Qt_widget/standard_toolbar.cpp
#include <CGAL/basic.h> #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.cpp #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.
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.
#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();
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:
#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");
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.
1 | http://www.trolltech.com |