This post is in addition to the article
“Creating Applications on GTK + Using the Glade Environment” . When I started to read it, and came across words that the example would be in C ++, I was delighted in advance, because at that time I was looking for examples of the Glade bundle with gtkmm, the C ++ wrapper for GTK +. What was my disappointment when it turned out that the author for some incomprehensible to me code in C, using the GTK + API, put the file in ".cpp" and called it an example in C ++. As a result, I decided to independently transform the sample example from that article in C ++. The result is submitted to the readers.
It is assumed that the reader is familiar with the basic concepts of the GTK + library. Also in this article I will not repeat, therefore before reading I recommend to get acquainted with the content of the original article.
Component installation
To use C ++, we need the gtkmm library, which is a wrapper for the GTK + library. When installed, it will pull the dependencies itself, which we also need, for example, the cairomm library, which, by analogy, is C ++ wrapper for the cairo library (2D graphics rendering). For a debian-based distribution, install with the command:
sudo apt-get install libgtkmm-2.4-dev
The version number may be different for your distribution.
Source
Below is the complete source code of the program, which in terms of functionality fully corresponds to the original example, but is written in C ++. Next, I will explain some parts of the code separately.
')
#include <gtkmm.h> #include <cairomm/cairomm.h> /** Main window class. */ class MainWindow: public Gtk::Window { private: /** Subclass for drawing area. */ class CDrawingArea: public Gtk::DrawingArea { public: typedef enum { SHAPE_RECTANGLE, SHAPE_ELLIPSE, SHAPE_TRIANGLE } shape_t; private: shape_t _curShape = SHAPE_RECTANGLE; /** Drawing event handler. */ virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) { switch (_curShape) { case SHAPE_RECTANGLE: cr->rectangle(20, 20, 200, 100); cr->set_source_rgb(0, 0.8, 0); cr->fill_preserve(); break; case SHAPE_ELLIPSE: cr->arc(150, 100, 90, 0, 2 * 3.14); cr->set_source_rgb(0.8, 0, 0); cr->fill_preserve(); break; case SHAPE_TRIANGLE: cr->move_to(40, 40); cr->line_to(200, 40); cr->line_to(120, 160); cr->line_to(40, 40); cr->set_source_rgb(0.8, 0, 0.8); cr->fill_preserve(); cr->set_line_cap(Cairo::LINE_CAP_ROUND); cr->set_line_join(Cairo::LINE_JOIN_ROUND); break; } cr->set_line_width(3); cr->set_source_rgb(0, 0, 0); cr->stroke(); return true; } public: CDrawingArea(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder): Gtk::DrawingArea(cobject) { } void SetShape(shape_t shape) { if (_curShape != shape) { _curShape = shape; /* Request re-drawing. */ queue_draw(); } } }; Glib::RefPtr<Gtk::Builder> _builder; Gtk::RadioButton *_rbRect, *_rbEllipse, *_rbTriangle; CDrawingArea *_drawingArea; public: /** Signal handler which is called when any radio button is clicked. */ void OnRadiobuttonClick() { if (_rbRect->get_active()) { _drawingArea->SetShape(CDrawingArea::SHAPE_RECTANGLE); } else if (_rbEllipse->get_active()) { _drawingArea->SetShape(CDrawingArea::SHAPE_ELLIPSE); } else if (_rbTriangle->get_active()) { _drawingArea->SetShape(CDrawingArea::SHAPE_TRIANGLE); } } /** "quit" action handler. */ void OnQuit() { hide(); } MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder): Gtk::Window(cobject), _builder(builder) { /* Retrieve all widgets. */ _builder->get_widget("rbRectangle", _rbRect); _builder->get_widget("rbEllipse", _rbEllipse); _builder->get_widget("rbTriangle", _rbTriangle); _builder->get_widget_derived("drawing_area", _drawingArea); /* Connect signals. */ _rbRect->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick)); _rbEllipse->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick)); _rbTriangle->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick)); /* Actions. */ Glib::RefPtr<Gtk::Action>::cast_dynamic(_builder->get_object("action_quit"))-> signal_activate().connect(sigc::mem_fun(*this, &MainWindow::OnQuit)); } }; int main(int argc, char **argv) { Gtk::Main app(argc, argv); Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("sample.glade"); MainWindow *mainWindow = 0; builder->get_widget_derived("main_wnd", mainWindow); app.run(*mainWindow); delete mainWindow; return 0; }
Everything begins with the initialization of the library by creating an object of the class “Gtk :: Main”. Next, a builder object is created, which is initialized with a file describing the graphical interface obtained by the Glade editor (see the original example).
Gtk::Main app(argc, argv); Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("sample.glade");
Note the use of the “Glib :: RefPtr” class. This is the implementation of smart pointers in the glibmm library - C ++ wrappers around the low-level glib library. The pointer that is assigned to this object will be automatically released when the object is destroyed.
class MainWindow: public Gtk::Window
For the presentation of the main window in our application, we will use our class, which inherits from the standard window class in gtkmm - “Gtk :: Window”. This technique is called subclassing and, in particular, is one of the ways to capture events for a widget, which will be shown below.
MainWindow *mainWindow = 0; builder->get_widget_derived("main_wnd", mainWindow);
Here we create an object for the main window according to its description in the builder. To get an object using subclassing, the get_widget_derived method is called. If a standard class is used (in this case it could be “Gtk :: Window”), then the builder’s get_widget method should be used.
app.run(*mainWindow); delete mainWindow;
The "run" method takes as its argument a window object with which it will work, and returns control only after it is hidden. Note that in order to avoid memory leaks, the window object must be deleted. This requirement applies only to top-level widgets, and does not apply to nested widgets (for example, to all widgets inside our main window, which will be obtained below by the same method, but will not be explicitly deleted).
We now turn to the class of the main window. The designer of all widgets always has one prototype:
MainWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder): Gtk::Window(cobject), _builder(builder)
The first argument is a pointer to the original GTK + object - the type “BaseObjectType” is always defined this way for all gtkmm classes (in this case, it will be GtkWindow). It must be passed to the base class constructor. The second argument is the builder object, which, in particular, can be used to get objects for nested widgets in the manner described above, which is done below:
_builder->get_widget("rbRectangle", _rbRect); _builder->get_widget("rbEllipse", _rbEllipse); _builder->get_widget("rbTriangle", _rbTriangle); _builder->get_widget_derived("drawing_area", _drawingArea);
Next, the signals for the click event on the radiobuttons are connected to the OnRadiobuttonClick method of our class:
_rbRect->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick)); _rbEllipse->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick)); _rbTriangle->signal_clicked().connect(sigc::mem_fun(*this, &MainWindow::OnRadiobuttonClick));
Note the use of the sigc :: mem_fun method from the sigc ++ library, which is the main framework for switching signals in gtkmm. This method returns a functor for a class method. If you need to use a function that is not a member of the class, you can use the "sigc :: ptr_fun" method. The described method of binding signals to handlers is the only one for widgets for which subclassing is not used, as in our case with radiobuttons.
The following three-storey construction binds the activation signal of the exit action from the program to the OnQuit method of our class:
Glib::RefPtr<Gtk::Action>::cast_dynamic(_builder->get_object("action_quit"))-> signal_activate().connect(sigc::mem_fun(*this, &MainWindow::OnQuit));
In this case, in Glade, you need to make sure that the action “action_quit” is created, to which the “Quit” element in the main menu should refer. In the original article, the moment with the actions was omitted, so I will comment on it. Actions in GTK + are called, in fact, actions that can be performed on events from different sources - menu items, toolbar buttons, hotkeys. The action object also describes common attributes for its external presentation (for example, in the menu and toolbar) - a label, an icon, and a tooltip text. The action in gtkmm corresponds to the class “Gtk :: Action”. To get it from the builder, you should use the get_object method, which returns an object of the base class Glib :: Object, so you also have to use the cast_dynamic method of the Glib :: RefPtr class for explicit type conversion. The “OnQuit” method itself is extremely simple:
void OnQuit() { hide(); }
As mentioned above, to exit the “run” method in the “main” function, it is enough to hide the window passed to it as an argument, which is done in the body of this method.
The next interesting point is a subclass for the “Gtk :: DrawingArea” class that implements drawing of figures in the corresponding widget. Of the new features, we have an on_draw method:
virtual bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
It is an example of another way of intercepting signals to events, which is applicable only to widgets that use subclassing. Its essence lies in the fact that in each class of widgets in gtkmm virtual methods are defined for each signal supported by the widget, which are called when the corresponding signal is received. A subclass can override the desired virtual method, thus intercepting the processing of the desired signal, which is done in this example.
Specifically, the data handles the event of redrawing the contents of the widget. As an argument, the cairomm library rendering context is passed to it, the work with which is completely analogous to the original example.
This post ends. Most of the information on the use of the mentioned libraries is taken from the self-documented source code of the libraries themselves.