Search code examples
c++gtk3gtkmm3

Turn widget's focus on and off based on mouse enter/leave event


I want to turn on Gtk::SpinButton entry focus when mouse pointer enters this widget and then turn it off again when the pointer leaves.

The signal event doesn't seem to respond while the window is still running. But the std::cout outputs are printed after I close the window.

How do I implement my expected widget behavior?

#include <gtkmm.h>
#include <iostream>

class SpinButtonExample : public Gtk::Window {
    Gtk::Grid                     grid;
    Gtk::Label                    label;
    Glib::RefPtr<Gtk::Adjustment> adjustment;
    Gtk::SpinButton               spinbutton;

public:
    SpinButtonExample();
    bool on_enter_notify_event(GdkEventCrossing *);
    bool on_leave_notify_event(GdkEventCrossing *);
};

SpinButtonExample::SpinButtonExample()
    : label     ("Hi")
    , adjustment(Gtk::Adjustment::create(0, 0, 10))
    , spinbutton(adjustment)
{
    add_events(Gdk::ENTER_NOTIFY_MASK);
    add_events(Gdk::LEAVE_NOTIFY_MASK);

    spinbutton.signal_enter_notify_event().connect(
                sigc::mem_fun(*this, &SpinButtonExample::on_enter_notify_event));
    spinbutton.signal_leave_notify_event().connect(
                sigc::mem_fun(*this, &SpinButtonExample::on_leave_notify_event));

    grid.set_orientation(Gtk::ORIENTATION_HORIZONTAL);
    grid.set_column_homogeneous(true);
    grid.set_margin_start(10);
    grid.set_margin_end(10);
    grid.set_margin_top(10);
    grid.set_margin_bottom(10);
    grid.add(label);
    grid.add(spinbutton);

    add(grid);

    show_all();
}

bool
SpinButtonExample::on_enter_notify_event(GdkEventCrossing *event)
{
    if (event->type == GDK_SCROLL) {
        std::cout << "Mouse entered\n";
        spinbutton.set_can_focus();
        return true;
    }
    return false;
}

bool
SpinButtonExample::on_leave_notify_event(GdkEventCrossing *event)
{
    if (event->type == GDK_LEAVE_NOTIFY) {
        std::cout << "Mouse left\n";
        spinbutton.set_can_focus(false);
        return true;
    }
    return false;
}

int
main()
{
    auto application = Gtk::Application::create("test.focus.spinbutton");

    SpinButtonExample test;

    return application->run(test);
}


Solution

  • Huge THANKS to underscore_d's comments above.

    The answer to this is to direct our focus to another widget when mouse pointer leaves Gtk::SpinButton widget and for enter event, grab the focus back.

    Just turning on and off with set_can_focus() won't work.

    Below is the corrected solution:

    #include <gtkmm.h>
    #include <iostream>
    
    class SpinButtonExample : public Gtk::Window {
        Gtk::Grid                     grid;
        Gtk::Label                    label;
        Glib::RefPtr<Gtk::Adjustment> adjustment;
        Gtk::SpinButton               spinbutton;
        Gtk::Button                   button;
    
    public:
        SpinButtonExample();
        bool on_enter_notify_event(GdkEventCrossing *);
        bool on_leave_notify_event(GdkEventCrossing *);
    };
    
    SpinButtonExample::SpinButtonExample()
        : label     ("Hi")
        , adjustment(Gtk::Adjustment::create(0, 0, 10))
        , spinbutton(adjustment)
        , button    ("Default")
    {
        add_events(Gdk::ENTER_NOTIFY_MASK);
        add_events(Gdk::LEAVE_NOTIFY_MASK);
    
        button.set_margin_start(10);
        button.set_can_default();
        button.grab_focus();
    
        spinbutton.set_can_default(false);
        spinbutton.signal_enter_notify_event().connect(
                    sigc::mem_fun(*this, &SpinButtonExample::on_enter_notify_event));
        spinbutton.signal_leave_notify_event().connect(
                    sigc::mem_fun(*this, &SpinButtonExample::on_leave_notify_event));
    
        grid.set_orientation(Gtk::ORIENTATION_HORIZONTAL);
        grid.set_column_homogeneous(true);
        grid.set_margin_start(10);
        grid.set_margin_end(10);
        grid.set_margin_top(10);
        grid.set_margin_bottom(10);
        grid.add(label);
        grid.add(spinbutton);
        grid.add(button);
    
        add(grid);
    
        show_all();
    }
    
    bool
    SpinButtonExample::on_enter_notify_event(GdkEventCrossing *event)
    {
        if (event->type == GDK_ENTER_NOTIFY) {
            std::cout << "Mouse entered" << std::endl;
            spinbutton.grab_focus();
            return true;
        }
        return false;
    }
    
    bool
    SpinButtonExample::on_leave_notify_event(GdkEventCrossing *event)
    {
        if (event->type == GDK_LEAVE_NOTIFY) {
            std::cout << "Mouse left" << std::endl;
            button.grab_focus();
            return true;
        }
        return false;
    }
    
    int
    main()
    {
        auto application = Gtk::Application::create("test.focus.spinbutton");
    
        SpinButtonExample test;
    
        return application->run(test);
    }