Building GTK + / gtkmm Applications Using the Glade Environment

This post is in addition to the article “Creating GTK + Applications Using the Glade Environment” . When I started reading it, and stumbled upon the words that the example would be in C ++, I was delighted in advance, because at that time I was looking for examples of the link between Glade and gtkmm - a wrapper C ++ library for GTK +. Imagine my disappointment when it turned out that the author, for reasons unknown to me, C code using the GTK + API put in a ".cpp" file and called it an example in C ++. As a result, I decided to independently transform the syclic example from that article in C ++. The result is presented to readers.

It is assumed that the reader is familiar with the basic concepts of the GTK + library. Also in this article I will not be repeated, therefore, before reading, I recommend that you read the contents of the original article.

Component Installation


To use C ++, we need the gtkmm library, which is a wrapper for the GTK + library. During installation, it itself will pull the dependencies that we will also need, for example, the cairomm library, which, by analogy, is a C ++ wrapper for the cairo library (rendering 2D graphics). For a debian-based distribution, the installation is done with the command:
sudo apt-get install libgtkmm-2.4-dev

The version number may differ for your distribution.

Source


Below is the full source code of the program, which in terms of functionality is fully consistent with the original example, but is written in C ++. Next, I will explain some parts of the code separately.

#include 
#include 
/** 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& 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& builder):
            Gtk::DrawingArea(cobject)
        {
        }
        void
        SetShape(shape_t shape)
        {
            if (_curShape != shape) {
                _curShape = shape;
                /* Request re-drawing. */
                queue_draw();
            }
        }
    };
    Glib::RefPtr _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& 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::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 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;
}


It all starts with the initialization of the library by creating an object of the "Gtk :: Main" class. Next, a builder object is created, which is initialized by a file with a description of the graphical interface received by the Glade editor (see the original example).
Gtk::Main app(argc, argv);
Glib::RefPtr 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

To represent the main window in our application, we will use our own class, which is inherited from the standard window class in gtkmm - “Gtk :: Window”. This technique is called subclassing and, in particular, is one way to intercept events for the 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 an argument to the 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 embedded widgets (for example, to all widgets inside our main window, which below will be obtained by the same method, but will not be explicitly deleted).

Now let's move on to the main window class. The constructor of all widgets always has one prototype:
MainWindow(BaseObjectType* cobject, const Glib::RefPtr& 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 constructor of the base class. The second argument is the builder object, which, in particular, can be used to obtain objects for embedded 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 event of clicking on the radiobutton’s 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));

Pay attention to 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 the 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-story structure attaches the activation signal for exiting the program to the OnQuit method of our class:
Glib::RefPtr::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 is created "action_quit", which should be referred to by the element "Quit" in the main menu. In the original article, the moment with actions was omitted, so I will comment. Actions in GTK + are called, in fact, actions that can be performed on events from different sources - menu items, toolbar buttons, hot keys. The action object also describes the general attributes for its external representation (for example, in the menu and toolbar) - label, icon, tooltip text. The action in gtkmm corresponds to the class "Gtk :: Action". To get it from the builder, you must 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 to explicitly convert the type.
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 the subclass for the “Gtk :: DrawingArea” class, which implements the rendering of shapes in the corresponding widget. Of the new features, we have the on_draw method:
virtual bool
on_draw(const Cairo::RefPtr& cr)

It is an example of another way of intercepting signals for events, which applies only to widgets using subclassing. Its essence is 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 method handles the event of redrawing the contents of the widget. As an argument, the cairomm library rendering context is passed to him, the work with which is completely analogous to the original example.

This post ends. Most of the information on using the mentioned libraries is taken from the self-documented source code of the libraries themselves.

Also popular now: