Custom Widgets
| Chapter 28. Custom Widgets |

## Custom Widgets

By deriving directly from `Gtk::Widget` you can do all the drawing for your widget directly, instead of just arranging child widgets. For instance, a `Gtk::Label` draws the text of the label, but does not do this by using other widgets.

When deriving from `Gtk::Widget`, you should override the following virtual methods. The methods marked (optional) need not be overridden in all custom widgets. The base class's methods may be appropriate.

- `get_request_mode_vfunc()`: (optional) Return what `Gtk::SizeRequestMode` is preferred by the widget.

- `get_preferred_width_vfunc()`: Calculate the minimum and natural width of the widget.

- `get_preferred_height_vfunc()`: Calculate the minimum and natural height of the widget.

- `get_preferred_width_for_height_vfunc()`: Calculate the minimum and natural width of the widget, if it would be given the specified height.

- `get_preferred_height_for_width_vfunc()`: Calculate the minimum and natural height of the widget, if it would be given the specified width.

- `on_size_allocate()`: Position the widget, given the height and width that it has actually been given.

- `on_realize()`: Associate a `Gdk::Window` with the widget.

- `on_unrealize()`: (optional) Break the association with the `Gdk::Window`.

- `on_map()`: (optional)

- `on_unmap()`: (optional)

- `on_draw()`: Draw on the supplied `Cairo::Context`.

The first 6 methods in the previous table are also overridden in custom containers. They are briefly described in the Custom Containers section.

Most custom widgets need their own `Gdk::Window` to draw on. Then you can call `Gtk::Widget::set_has_window(true)` in your constructor. (This is the default value.) If you do not call `set_has_window(false)`, you must override `on_realize()` and call `Gtk::Widget::set_realized()` and `Gtk::Widget::set_window()` from there.

### Custom Style Properties

You can add style properties to your widget class, whether it's derived directly from `Gtk::Widget` or from another widget class. The values of the style properties can be read from a CSS (Cascading Style Sheets) file. The users of your widget, or the users of an application program with your widget, can then modify the style of your widget without modifying the source code. Useful classes are `Gtk::StyleProperty` and `Gtk::CssProvider`. With `Gtk::Widget::get_style_property()` you can read the values of both your own style properties and those of your widget's base class. Note that style properties are not wrapped in gtkmm. See GTK+'s documentation for lists of existing style properties. The following example shows a simple use of a custom style property.

### Example

This example implements a widget which draws a Penrose triangle.

Figure 28.2. Custom Widget

Source Code

File: `examplewindow.h` (For use with gtkmm 3)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>
    #include "mywidget.h"

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      virtual ~ExampleWindow();

    protected:
      //Signal handlers:
      void on_button_quit();

      //Child widgets:
      Gtk::Box m_VBox;
      MyWidget m_MyWidget;
      Gtk::ButtonBox m_ButtonBox;
      Gtk::Button m_Button_Quit;
    };

    #endif //GTKMM_EXAMPLEWINDOW_H

File: `myextrainit.h` (For use with gtkmm 3)

    #ifndef GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H
    #define GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H

    #include <glibmmconfig.h>

    #define HAS_EXTRA_CLASS_INIT (GLIBMM_MAJOR_VERSION > 2 || \
      (GLIBMM_MAJOR_VERSION == 2 && GLIBMM_MINOR_VERSION >= 60))

    #if HAS_EXTRA_CLASS_INIT

    #include <glibmm/extraclassinit.h>
    #include <glibmm/ustring.h>

    // Calls gtk_widget_class_set_css_name() in the class init function
    // and gtk_set_has_window() in the instance init function.
    class MyExtraInit : public Glib::ExtraClassInit
    {
    public:
      MyExtraInit(const Glib::ustring& css_name);

    private:
      Glib::ustring m_css_name;
    };

    #endif // HAS_EXTRA_CLASS_INIT

    #endif //GTKMM_CUSTOM_WIDGET_MYEXTRAINIT_H

File: `mywidget.h` (For use with gtkmm 3)

    #ifndef GTKMM_CUSTOM_WIDGET_MYWIDGET_H
    #define GTKMM_CUSTOM_WIDGET_MYWIDGET_H

    #include <gtkmm/widget.h>
    #include <gtkmm/cssprovider.h>
    #include <gtkmm/styleproperty.h>
    #include "myextrainit.h"

    #if HAS_EXTRA_CLASS_INIT
    class MyWidget : public MyExtraInit, public Gtk::Widget
    #else
    class MyWidget : public Gtk::Widget
    #endif
    {
    public:
      MyWidget();
      virtual ~MyWidget();

    protected:

      //Overrides:
      Gtk::SizeRequestMode get_request_mode_vfunc() const override;
      void get_preferred_width_vfunc(int& minimum_width, int& natural_width) const override;
      void get_preferred_height_for_width_vfunc(int width, int& minimum_height, int& natural_height) const  override;
      void get_preferred_height_vfunc(int& minimum_height, int& natural_height) const override;
      void get_preferred_width_for_height_vfunc(int height, int& minimum_width, int& natural_width) const override;
      void on_size_allocate(Gtk::Allocation& allocation) override;
      void on_map() override;
      void on_unmap() override;
      void on_realize() override;
      void on_unrealize() override;
      bool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;

      //Signal handler:
      void on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error);

      Gtk::StyleProperty<int> m_scale_prop;
      Glib::RefPtr<Gdk::Window> m_refGdkWindow;
      Glib::RefPtr<Gtk::CssProvider> m_refCssProvider;

      int m_scale;
    };

    #endif //GTKMM_CUSTOM_WIDGET_MYWIDGET_H

File: `examplewindow.cc` (For use with gtkmm 3)

    #include "examplewindow.h"

    ExampleWindow::ExampleWindow()
    : m_VBox(Gtk::ORIENTATION_VERTICAL),
      m_Button_Quit("Quit")
    {
      set_title("Custom Widget example");
      set_border_width(6);
      set_default_size(400, 200);

      add(m_VBox);
      m_VBox.pack_start(m_MyWidget, Gtk::PACK_EXPAND_WIDGET);
      m_MyWidget.show();

      m_VBox.pack_start(m_ButtonBox, Gtk::PACK_SHRINK);

      m_ButtonBox.pack_start(m_Button_Quit, Gtk::PACK_SHRINK);
      m_ButtonBox.set_border_width(6);
      m_ButtonBox.set_layout(Gtk::BUTTONBOX_END);
      m_Button_Quit.signal_clicked().connect( sigc::mem_fun(*this, &ExampleWindow::on_button_quit) );

      show_all_children();
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_button_quit()
    {
      hide();
    }

File: `main.cc` (For use with gtkmm 3)

    #include "examplewindow.h"
    #include <gtkmm/application.h>

    int main(int argc, char *argv[])
    {
      auto app = Gtk::Application::create(argc, argv, "org.gtkmm.example");

      ExampleWindow window;

      //Shows the window and returns when it is closed.
      return app->run(window);
    }

File: `myextrainit.cc` (For use with gtkmm 3)

    #include "myextrainit.h"
    #include <gtkmm/widget.h>
    #include <gtk/gtk.h>

    #if HAS_EXTRA_CLASS_INIT

    namespace
    {
    using BaseObjectType = GtkWidget;
    using BaseClassType = GtkWidgetClass;

    // Extra class init function.
    void class_init_function(void* g_class, void* class_data)
    {
      g_return_if_fail(GTK_IS_WIDGET_CLASS(g_class));

      const auto klass = static_cast<BaseClassType*>(g_class);
      const auto css_name = static_cast<Glib::ustring*>(class_data);

      gtk_widget_class_set_css_name(klass, css_name->c_str());
    }

    // Extra instance init function.
    void instance_init_function(GTypeInstance* instance, void* /* g_class */)
    {
      g_return_if_fail(GTK_IS_WIDGET(instance));

      gtk_widget_set_has_window(GTK_WIDGET(instance), true);
    }

    } // anonymous namespace

    MyExtraInit::MyExtraInit(const Glib::ustring& css_name)
    :
    Glib::ExtraClassInit(class_init_function, &m_css_name, instance_init_function),
    m_css_name(css_name)
    {
    }

    #endif // HAS_EXTRA_CLASS_INIT

File: `mywidget.cc` (For use with gtkmm 3)

    #include "mywidget.h"
    #include <gdkmm/general.h>  // for cairo helper functions
    #include <iostream>
    //#include <gtk/gtkwidget.h> //For GTK_IS_WIDGET()
    #include <cstring>

    // The MyWidget class uses API which was added in gtkmm 3.15.3 (Gtk::CssProviderError,
    // Gtk::CssProvider::signal_parsing_error() and Gtk::CssSection) and in gtkmm 3.15.2
    // (Gtk::StyleProperty) and in glibmm 2.60.0 (Glib::ExtraClassInit).

    MyWidget::MyWidget() :
      //The GType name will actually be gtkmm__CustomObject_mywidget
      Glib::ObjectBase("mywidget"),
    #if HAS_EXTRA_CLASS_INIT
      MyExtraInit("my-widget-class"), // CSS node name, which must be used in the CSS file.
    #endif
      Gtk::Widget(),
      //Install a style property so that an aspect of this widget may be themed
      //via a CSS style sheet file:
      m_scale_prop(*this, "example_scale", 500),
      m_scale(1000)
    {
    #if HAS_EXTRA_CLASS_INIT == 0
      set_has_window(true);
      // Set the widget name to use in the CSS file.
      set_name("my-widget-instance");
    #endif

      //This shows the GType name, which must be used in the CSS file.
      std::cout << "GType name: " << G_OBJECT_TYPE_NAME(gobj()) << std::endl;

      //This shows that the GType still derives from GtkWidget:
      //std::cout << "Gtype is a GtkWidget?:" << GTK_IS_WIDGET(gobj()) << std::endl;

      // The CSS name can be set either
      // - for a GType (in this case for your custom class) with gtk_widget_class_set_css_name(), or
      // - for a widget instance with gtk_widget_set_name() (Gtk::Widget::set_name()).
      //
      // gtk_widget_class_set_css_name() (new in gtk+ 3.19.1), if used, must be called
      // in the class init function. It has not been wrapped in a C++ function.
      // Gtk::Widget::set_name() can be called in a C++ constructor.
      //
      // Another alternative: The custom widget inherits the CSS name "widget" from
      // GtkWidget. That name can be used in the CSS file. This is not a very good
      // alternative. GtkWidget's CSS name is not documented. It can probably be
      // changed or removed in the future.

      m_refCssProvider = Gtk::CssProvider::create();
      auto refStyleContext = get_style_context();
      refStyleContext->add_provider(m_refCssProvider,
        GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
      m_refCssProvider->signal_parsing_error().connect(
        sigc::mem_fun(*this, &MyWidget::on_parsing_error));

      try
      {
        m_refCssProvider->load_from_path("custom_gtk.css");
      }
      catch(const Gtk::CssProviderError& ex)
      {
        std::cerr << "CssProviderError, Gtk::CssProvider::load_from_path() failed: "
                  << ex.what() << std::endl;
      }
      catch(const Glib::Error& ex)
      {
        std::cerr << "Error, Gtk::CssProvider::load_from_path() failed: "
                  << ex.what() << std::endl;
      }
    }

    MyWidget::~MyWidget()
    {
    }

    Gtk::SizeRequestMode MyWidget::get_request_mode_vfunc() const
    {
      //Accept the default value supplied by the base class.
      return Gtk::Widget::get_request_mode_vfunc();
    }

    //Discover the total amount of minimum space and natural space needed by
    //this widget.
    //Let's make this simple example widget always need minimum 60 by 50 and
    //natural 100 by 70.
    void MyWidget::get_preferred_width_vfunc(int& minimum_width, int& natural_width) const
    {
      minimum_width = 60;
      natural_width = 100;
    }

    void MyWidget::get_preferred_height_for_width_vfunc(int /* width */,
       int& minimum_height, int& natural_height) const
    {
      minimum_height = 50;
      natural_height = 70;
    }

    void MyWidget::get_preferred_height_vfunc(int& minimum_height, int& natural_height) const
    {
      minimum_height = 50;
      natural_height = 70;
    }

    void MyWidget::get_preferred_width_for_height_vfunc(int /* height */,
       int& minimum_width, int& natural_width) const
    {
      minimum_width = 60;
      natural_width = 100;
    }

    void MyWidget::on_size_allocate(Gtk::Allocation& allocation)
    {
      //Do something with the space that we have actually been given:
      //(We will not be given heights or widths less than we have requested, though
      //we might get more)

      //Use the offered allocation for this container:
      set_allocation(allocation);

      if(m_refGdkWindow)
      {
        m_refGdkWindow->move_resize( allocation.get_x(), allocation.get_y(),
                allocation.get_width(), allocation.get_height() );
      }
    }

    void MyWidget::on_map()
    {
      //Call base class:
      Gtk::Widget::on_map();
    }

    void MyWidget::on_unmap()
    {
      //Call base class:
      Gtk::Widget::on_unmap();
    }

    void MyWidget::on_realize()
    {
      //Do not call base class Gtk::Widget::on_realize().
      //It's intended only for widgets that set_has_window(false).

      set_realized();

      //Get the themed style from the CSS file:
      m_scale = m_scale_prop.get_value();
      std::cout << "m_scale (example_scale from the theme/css-file) is: "
          << m_scale << std::endl;

      if(!m_refGdkWindow)
      {
        //Create the GdkWindow:

        GdkWindowAttr attributes;
        memset(&attributes, 0, sizeof(attributes));

        Gtk::Allocation allocation = get_allocation();

        //Set initial position and size of the Gdk::Window:
        attributes.x = allocation.get_x();
        attributes.y = allocation.get_y();
        attributes.width = allocation.get_width();
        attributes.height = allocation.get_height();

        attributes.event_mask = get_events () | Gdk::EXPOSURE_MASK;
        attributes.window_type = GDK_WINDOW_CHILD;
        attributes.wclass = GDK_INPUT_OUTPUT;

        m_refGdkWindow = Gdk::Window::create(get_parent_window(), &attributes,
                GDK_WA_X | GDK_WA_Y);
        set_window(m_refGdkWindow);

        //make the widget receive expose events
        m_refGdkWindow->set_user_data(gobj());
      }
    }

    void MyWidget::on_unrealize()
    {
      m_refGdkWindow.reset();

      //Call base class:
      Gtk::Widget::on_unrealize();
    }

    bool MyWidget::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)
    {
      const Gtk::Allocation allocation = get_allocation();
      const double scale_x = (double)allocation.get_width() / m_scale;
      const double scale_y = (double)allocation.get_height() / m_scale;
      auto refStyleContext = get_style_context();

      // paint the background
      refStyleContext->render_background(cr,
        allocation.get_x(), allocation.get_y(),
        allocation.get_width(), allocation.get_height());

      // draw the foreground
      const auto state = refStyleContext->get_state();
      Gdk::Cairo::set_source_rgba(cr, refStyleContext->get_color(state));
      cr->move_to(155.*scale_x, 165.*scale_y);
      cr->line_to(155.*scale_x, 838.*scale_y);
      cr->line_to(265.*scale_x, 900.*scale_y);
      cr->line_to(849.*scale_x, 564.*scale_y);
      cr->line_to(849.*scale_x, 438.*scale_y);
      cr->line_to(265.*scale_x, 100.*scale_y);
      cr->line_to(155.*scale_x, 165.*scale_y);
      cr->move_to(265.*scale_x, 100.*scale_y);
      cr->line_to(265.*scale_x, 652.*scale_y);
      cr->line_to(526.*scale_x, 502.*scale_y);
      cr->move_to(369.*scale_x, 411.*scale_y);
      cr->line_to(633.*scale_x, 564.*scale_y);
      cr->move_to(369.*scale_x, 286.*scale_y);
      cr->line_to(369.*scale_x, 592.*scale_y);
      cr->move_to(369.*scale_x, 286.*scale_y);
      cr->line_to(849.*scale_x, 564.*scale_y);
      cr->move_to(633.*scale_x, 564.*scale_y);
      cr->line_to(155.*scale_x, 838.*scale_y);
      cr->stroke();

      return true;
    }

    void MyWidget::on_parsing_error(const Glib::RefPtr<const Gtk::CssSection>& section, const Glib::Error& error)
    {
      std::cerr << "on_parsing_error(): " << error.what() << std::endl;
      if (section)
      {
        const auto file = section->get_file();
        if (file)
        {
          std::cerr << "  URI = " << file->get_uri() << std::endl;
        }

        std::cerr << "  start_line = " << section->get_start_line()+1
                  << ", end_line = " << section->get_end_line()+1 << std::endl;
        std::cerr << "  start_position = " << section->get_start_position()
                  << ", end_position = " << section->get_end_position() << std::endl;
      }
    }

File: `custom_gtk.css` (For use with gtkmm 3)

    /* Example of a CSS style sheet with a custom style property.
     *
     * The name of the style property must have its canonical form, i.e. characters
     * other than ASCII letters, digits, and hyphens must be replaced by hyphens.
    */

    * {
      /* -<widget class name>-<style property canonical name>: <value>; */
      -gtkmm__CustomObject_mywidget-example-scale: 920;
    }

    my-widget-class {
      background-color: rgb(255,0,0);
      color:            rgb(0,0,255);
    }
    #my-widget-instance {
      background-color: rgb(255,128,0);
      color:            rgb(0,0,255);
    }

Chapter 28. Custom Widgets |  | Chapter 29. Multi-threaded programs