Search code examples
c++gtkgtkmmgtkmm3

Using button mnemonic doesn't update second spinbutton value


I made a sample Gtk::Spinbutton dialog program where the user can modify Width/Height values and see the changes (text) outputted on the main Gtk::Window

Main Window

I added two buttons; Cancel and OK, with mnemonics turned on set_use_underline()

enter image description here

For testing purposes, I'm assuming the user is modifying both values. The program works fine when I use the mouse to click on OK button. Both values are updated. But when OK button mnemonic (Alt + O) is used to accept changes, either height or width value doesn't get updated properly. For instance, on Width field I enter 1366 and on height field I enter 768, then I hit Alt + O to activate OK button action, width is updated but not height. Instead, previous value of height is retained which at the start of the program is 1.

Core class

  • Declaration
#ifndef WIDTHHEIGHT_H
#define WIDTHHEIGHT_H

class WidthHeight
{
    int width, height;

public:
    void set_width_core(int width = 1);
    int  get_width_core();
    void set_height_core(int height = 1);
    int  get_height_core();
};

#endif // WIDTHHEIGHT_H

  • Definition
#include "widthheight.h"

void WidthHeight::set_width_core(int width)
{
    this->width = width;
}

int WidthHeight::get_width_core()
{
    return width;
}

void WidthHeight::set_height_core(int height)
{
    this->height = height;
}

int WidthHeight::get_height_core()
{
    return height;
}

Dialog class

  • Declaration
#ifndef WIDTHHEIGHTDIALOG_H
#define WIDTHHEIGHTDIALOG_H

#include <gtkmm/adjustment.h>
#include <gtkmm/dialog.h>
#include <gtkmm/label.h>
#include <gtkmm/spinbutton.h>

class WidthHeight;
class ExampleWindow;

class WidthHeightDialog : public Gtk::Dialog
{
    WidthHeight &core;
    ExampleWindow &window;
    Gtk::Label width, height;
    Glib::RefPtr<Gtk::Adjustment> w_adjustment, h_adjustment;
    Gtk::SpinButton *w_entry, *h_entry;

    enum response_id {
        REJECT,
        ACCEPT
    };

    void on_action_signal_response(int);

public:
    WidthHeightDialog(WidthHeight &, ExampleWindow &);
};

#endif // WIDTHHEIGHTDIALOG_H

  • Definition
#include <iostream>

#include "widthheightdialog.h"
#include "widthheight.h"
#include "examplewindow.h"

void WidthHeightDialog::on_action_signal_response(int id)
{
    switch (id)
    {
    case Gtk::RESPONSE_CLOSE:
    case response_id::REJECT:
        std::cout << "Canceled\n";
        break;
    case response_id::ACCEPT:
        std::cout << "OKayed\n";
        break;
    }

    core.set_width_core(w_entry->get_value_as_int());
    core.set_height_core(h_entry->get_value_as_int());
    window.refresh_label();
    std::cout << "Width: " << w_entry->get_value_as_int() << std::endl;
    std::cout << "Height: " << h_entry->get_value_as_int() << std::endl;

    hide();
}

WidthHeightDialog::WidthHeightDialog(WidthHeight &core, ExampleWindow &window)
    : Dialog("Change Width/Height value")
    , core(core)
    , window(window)
    , width("Width:")
    , height("Height:")
    , w_adjustment(Gtk::Adjustment::create(1, 1, 100000))
    , h_adjustment(Gtk::Adjustment::create(1, 1, 100000))
    , w_entry(manage(new Gtk::SpinButton(w_adjustment, 1, 0)))
    , h_entry(manage(new Gtk::SpinButton(h_adjustment, 1, 0)))
{
    auto box = get_content_area();
    box->add(width);
    box->add(*w_entry);
    box->add(height);
    box->add(*h_entry);

    box->show_all();

    add_button("_Cancel", response_id::REJECT)->set_use_underline();
    add_button("_OK"    , response_id::ACCEPT)->set_use_underline();

    signal_response().connect(sigc::mem_fun(*this, &WidthHeightDialog::on_action_signal_response));
}

Window class

  • Declaration
#ifndef EXAMPLEWINDOW_H
#define EXAMPLEWINDOW_H

#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/label.h>
#include <gtkmm/window.h>

#include "widthheight.h"
#include "widthheightdialog.h"

class ExampleWindow : public Gtk::Window
{
    Gtk::Box vbox;
    Gtk::Label label;
    Gtk::Button button;
    WidthHeight core;
    WidthHeightDialog dialog;

    void on_button_clicked();

public:
    ExampleWindow();
    void refresh_label();
};

#endif // EXAMPLEWINDOW_H

  • Definition
#include "examplewindow.h"

void ExampleWindow::on_button_clicked()
{
    dialog.show();

    dialog.present();
}

ExampleWindow::ExampleWindow()
    : vbox(Gtk::ORIENTATION_VERTICAL)
    , label("\n\tOutput printed on console...\t\n")
    , button("Change Width/Height value")
    , dialog(core, *this)
{
    set_title("Example");

    add(vbox);

    core.set_width_core();
    core.set_height_core();
    refresh_label();

    vbox.pack_start(label);
    label.set_line_wrap(true);
    label.set_selectable(true);

    vbox.pack_start(button);
    button.signal_clicked().connect(sigc::mem_fun(*this, &ExampleWindow::on_button_clicked));

    dialog.set_transient_for(*this);

    show_all_children();

    button.grab_focus();
}

void ExampleWindow::refresh_label()
{
    std::string width = "\n Width: " + std::to_string(core.get_width_core()) + "\n";
    std::string height = "Height: " + std::to_string(core.get_height_core()) + "\n";

    label.set_text(width + height);
}

Main

#include <gtkmm/application.h>

#include "examplewindow.h"

int main()
{
    auto app = Gtk::Application::create("org.gtkmm.example");

    ExampleWindow window;

    return app->run(window);
}

Solution

  • There seems to be an update problem the the spinboxes. For example, try the following:

    1. Launch the application.
    2. Modify width, then height.
    3. Click inside the width edit box.
    4. Hit Alt+O.

    In this case, you will see that both values are updated (just like when clicked). I believe that when you click, the same thing happen, but with the button. Clicking on it is the equivalent of step 3 (i.e. giving focus to another widget), which seems to automatically update the spinbox.

    To get around this, you can use the Gtk::SpinButton::update() method for force an update on the spinboxes. In your case, you would have:

    void WidthHeightDialog::on_action_signal_response(int id)
    {
        switch (id)
        {
        case Gtk::RESPONSE_CLOSE:
        case response_id::REJECT:
            std::cout << "Canceled\n";
            break;
        case response_id::ACCEPT:
            std::cout << "OKayed\n";
            break;
        }
    
        w_entry->update();  // Forces an update
        h_entry->update();  // Forces an update
    
        core.set_width_core(w_entry->get_value_as_int());
        core.set_height_core(h_entry->get_value_as_int());
        window.refresh_label();
        std::cout << "Width: " << w_entry->get_value_as_int() << std::endl;
        std::cout << "Height: " << h_entry->get_value_as_int() << std::endl;
    
        hide();
    }
    

    which would ensure that for both spinboxes, even if the user does not give the focus to another widget (and hence trigger an automatic update of the last modified spinbox), an update still occurs.