Garmaine Staff asked 2 years ago

I am using Gtkmm3 (Ubuntu) to make a small GUI application. In this application, I have a couple windows to create which basically all follow the same pattern:

  1. registering a main layout (Gtk::Grid);
  2. configuring window properties (icon, title, etc);
  3. configuring layout properties (expanding, sub-layouts, etc);
  4. configuring window widgets (adding them to layouts, labels, etc).

In hope of not having to rewrite all of this logic every time I create a new window, I have written the following base class:

template<typename GtkmmWindow>
class Window
{

public:

    Window();

    virtual int Show() = 0;


protected:

    virtual void ConfigureWindow() = 0;
    virtual void ConfigureLayouts() = 0;
    virtual void ConfigureWidgets() = 0;

    void Init();


    Gtk::Grid m_mainLayout;
    GtkmmWindow m_window;

};

template<typename GtkmmWindow>
Window<GtkmmWindow>::Window()
{
    m_window.add(m_mainLayout);

    // When signal 'realize' is sent, 'Init' will be triggered.
    // This happens after construction, so virtual methods can
    // be used safely:
    m_window.signal_realize().connect([this](){Init();});
}

// Initialize child window according to its own needs:
template<typename GtkmmWindow>
void Window<GtkmmWindow>::Init()
{
    ConfigureWindow();
    ConfigureLayouts();
    ConfigureWidgets();

    // If this line is removed, no widgets are shown.
    m_window.show_all_children();
}

The goal of this class is to make sure points 1 to 4 are implemented by all windows the same way. It does so by calling appropriate virtual methods (to be redefined in concrete child classes) when the realize signal is sent. This is because when the realize signal is sent, I know the window constructors have been called and that I can safely use virtual methods.

For example, here is how I use it to create an application main window:

class MyWindow : public Window<Gtk::ApplicationWindow>
{

public:

    MyWindow(Gtk::Application& p_app) : m_app{p_app} {}

    int Show() override
    {
        m_window.show_all();
        return m_app.run(m_window);
    }

private:

    Gtk::Application& m_app;
    Gtk::Button m_button;

    void ConfigureWindow() override
    {
        m_window.set_title("SO Question");

        // If I set this to false, the window shrinks to fit the button size:
        m_window.set_resizable(false);
    }

    void ConfigureLayouts() override
    {
        m_mainLayout.override_background_color(Gdk::RGBA("yellow"));
    }

    void ConfigureWidgets() override
    {
        m_mainLayout.attach(m_button, 0, 0, 1, 1);
        m_button.set_label("Hello");
    }

};

This main window sets the main layout to have a yellow background, has a Gtk::Button with label "Hello" registered in the main layout. The problem I have with this strategy is that when I run the code, I get weird window/layout sizing:

enter image description here

Notice the yellow layout is way bigger than the only widget (the button) contained in it. This is the result I would have expected:

enter image description here

That is, the window and main layout should shrink to the size of their only contained widget. Weirdly, if I make the window set_resizable(false), I get the sizing I want, but then I can't resize it anymore, which is often not acceptable.

Questions:

  1. Why is it no so?
  2. How can I achieve this without duplicating the base code for every window?

You can build this code using g++ by adding it to:

#include <memory>
#include <gtkmm.h>

// Add here...

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

  std::unique_ptr<MyWindow> mainWindow = std::make_unique<MyWindow>(*(app.get()));

  return mainWindow->Show();
}

and running:

g++ -std=c++17 main.cpp -o example.out `pkg-config gtkmm-3.0 --cflags --libs`