c++gtk3gtkmm3

Trigger checkbutton hover event from another widget


I made a custom Right-to-Left check button widget using a Gtk::CheckButton without the label and a Gtk::Label inside Gtk::EventBox.

The reason I went this way instead of simply calling set_direction(Gtk::TEXT_DIR_RTL) is that I want to manually specify the alignment for these widgets.

I've figured out activating/deactivating checkbutton state when the label's clicked but I'm having trouble triggering the hover event from label to checkbutton. The default Gtk::CheckButton behavior triggers the hover effect on the clickable square button to indicate it's being activated when text is hovered too. How do I achieve that same behavior on my custom right-to-left checkbutton widget?

Below is the minimal reproducible code for testing:

#include <gtkmm.h>

using Gtk::Application;
using Gtk::Button;
using Gtk::CheckButton;
using Gtk::EventBox;
using Gtk::Grid;
using Gtk::Label;
using Gtk::Window;

class MyWindow : public Window {
    Button      button;
    CheckButton checkbutton;
    EventBox    eventbox;
    Grid        grid;
    Label       label;

    bool on_label_clicked(GdkEventButton *);
    void arrange_content_layout();
    
public:
    MyWindow();
};

bool MyWindow::on_label_clicked(GdkEventButton *event)
{
    if (event->button == 1 && event->type == GDK_BUTTON_PRESS) {
        checkbutton.set_active(!checkbutton.get_active());
        return true;
    }

    return false;
}

void MyWindow::arrange_content_layout()
{
    checkbutton.set_margin_start(6);
    
    button.set_margin_top(24);
    grid.child_property_width(button) = 2;
    
    grid.set_margin_top(24);
    grid.set_margin_start(24);
    grid.set_margin_end(24);
    grid.set_margin_bottom(24);
}

MyWindow::MyWindow()
    : button("Click to steal focus")
    , label ("Our custom RTL label")
{
    eventbox.add(label);
    eventbox.signal_button_press_event().connect(sigc::mem_fun(
            *this, &MyWindow::on_label_clicked));
    
    grid.add(eventbox);
    grid.add(checkbutton);
    grid.attach(button, 0, 1);
    
    add(grid);
    
    arrange_content_layout();
    set_default_size(120, 60);
    
    show_all();
}

int main()
{
    auto app = Application::create("domain.reverse.sample.rtl-checkbutton");
    
    MyWindow window;
    
    return app->run(window);
}

Solution

  • One way is to use set_state_flags() and trick the visual output of your Right-to-Left checkbutton when the label is being hovered.

    First, you need to enable these two masks for eventbox to capture enter and leave event:

    eventbox.add_events(Gdk::ENTER_NOTIFY_MASK | Gdk::LEAVE_NOTIFY_MASK);
    

    And connect eventbox signals

    class MyWindow : public Window {
        ...
        bool on_label_entered(GdkEventCrossing *);
        bool on_label_exited(GdkEventCrossing *);
        ...
    }
    
    MyWindow::MyWindow()
        ...
    {
        ...
        eventbox.signal_enter_notify_event().connect(sigc::mem_fun(
                *this, &MyWindow::on_label_entered));
        eventbox.signal_leave_notify_event().connect(sigc::mem_fun(
                *this, &MyWindow::on_label_exited));
        ...
    

    And finally, use Gtk::STATE_FLAG_PRELIGHT to do the UI trick

    // Enter event
    bool MyWindow::on_label_entered(GdkEventCrossing *event)
    {
        if (event->mode != GDK_CROSSING_NORMAL) return false;
    
        checkbutton.set_state_flags(checkbutton.get_state_flags() | Gtk::STATE_FLAG_PRELIGHT);
    
        return true;
    }
    
    // Leave event
    bool MyWindow::on_label_exited(GdkEventCrossing *event)
    {
        if (event->mode != GDK_CROSSING_NORMAL) return false;
    
        checkbutton.unset_state_flags(Gtk::STATE_FLAG_PRELIGHT);
    
        return true;
    }