Search code examples
cgtkgtk3

Set the state of a menu item on shortcut press


A program has a menu bar, which has a menu, which has a menu item. The menu item is enabled or disabled depending on some circumstances. Checking of circumstances and enabling/disabling of the menu item is done when a user clicks on the menu that holds that menu item.

But, there is also a shortcut associated with that menu item. The shortcut functions only when the menu item is enabled.

How can we set the state of a menu item without clicking on the menu that holds it, when we want to use a shortcut?

Here is an example program that will hopefully make my question more clear:

#include <gtk/gtk.h>

struct check_sensitivity
{
        GtkWidget *menuitem;
        GtkTextBuffer *buffer;
};

void sensitivity(GtkWidget *menu, struct check_sensitivity *sens)
{
        if (gtk_text_buffer_get_modified(sens->buffer))
                gtk_widget_set_sensitive(sens->menuitem, TRUE);
        else
                gtk_widget_set_sensitive(sens->menuitem, FALSE);
}

void menuitem_click(GtkWidget *menuitem, GtkTextBuffer *buffer)
{
        gtk_text_buffer_set_text(buffer, "", -1);
        gtk_text_buffer_set_modified(buffer, FALSE);
}

int main(int argc, char **argv)
{
        gtk_init(&argc, &argv);

        struct check_sensitivity sens;

        GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);

        GtkWidget *view = gtk_text_view_new();
        sens.buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));

        GtkWidget *menubar = gtk_menu_bar_new();
        GtkWidget *menu = gtk_menu_new();

        GtkAccelGroup *shortcuts = gtk_accel_group_new();
        gtk_window_add_accel_group(GTK_WINDOW(window), shortcuts);

        GtkWidget *menuitem = gtk_menu_item_new_with_label("Menu");
        gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
        gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
        g_signal_connect(menuitem, "activate", G_CALLBACK(sensitivity), &sens);
        menuitem = gtk_menu_item_new_with_label("Clear");
        gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
        gtk_widget_add_accelerator(menuitem, "activate", shortcuts, GDK_KEY_D, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
        sens.menuitem = menuitem;
        g_signal_connect(sens.menuitem, "activate", G_CALLBACK(menuitem_click), sens.buffer);

        GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
        gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(box), view, TRUE, TRUE, 0);

        gtk_container_add(GTK_CONTAINER(window), box);

        gtk_widget_show_all(window);

        gtk_main();
}

In this program, the state of menu item "Clear" depends on that whether the buffer is modified or not. The state is, of course, set when the user clicks on the menu "Menu".

There is also a shortcut Ctrl+D that does the same thing as clicking on "Clear". The shortcut works as it should depending on the state of the menu item, but what is wrong is the state of the menu item. Let me explain:

Since the menu item is (un)set when a user click on "Menu", to update the state of "Clear" before using a shortcut, a user must click on it. If the shortcut is used before updating of the state of the menu item, it may not be able to do its function when it should.

Here is what you can try: open the program and write something. Try clearing it with Ctrl+D. You'll see that nothing happens even though it should, because the state of the menu item is not updated. If you click on "Menu", then try using the shortcut again, the text buffer will clear.

I'm just asking for a why to update the state when the shortcut is pressed as well. I tried a few ways that base on setting the state when Ctrl is pressed (I can't check only for Ctrl+D, I may have another shortcuts in a program), but all of them failed or produced bugs. One of those ways you can see here.


Solution

  • Finally found a working method.

    #include <gtk/gtk.h>
    
    struct check_sensitivity
    {
            GtkWidget *menuitem;
            GtkTextBuffer *buffer;
    };
    
    void sensitivity(GtkWidget *menu, struct check_sensitivity *sens);
    
    void check_sensitivity_on_ctrl(GtkWidget *window, GdkEventKey *key, struct check_sensitivity *sens)
    {
        if(key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R)
        {
            sensitivity(NULL, sens);
        }
    }
    
    void sensitivity(GtkWidget *menu, struct check_sensitivity *sens)
    {
            if (gtk_text_buffer_get_modified(sens->buffer))
                    gtk_widget_set_sensitive(sens->menuitem, TRUE);
            else
                    gtk_widget_set_sensitive(sens->menuitem, FALSE);
    }
    
    void menuitem_click(GtkWidget *menuitem, GtkTextBuffer *buffer)
    {
            gtk_text_buffer_set_text(buffer, "", -1);
            gtk_text_buffer_set_modified(buffer, FALSE);
    }
    
    int main(int argc, char **argv)
    {
            gtk_init(&argc, &argv);
    
            struct check_sensitivity sens;
    
            GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
            g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
    
            GtkWidget *view = gtk_text_view_new();
            sens.buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
        gtk_text_buffer_set_modified(sens.buffer, FALSE);
    
            GtkWidget *menubar = gtk_menu_bar_new();
            GtkWidget *menu = gtk_menu_new();
    
            GtkAccelGroup *shortcuts = gtk_accel_group_new();
            gtk_window_add_accel_group(GTK_WINDOW(window), shortcuts);
    
            GtkWidget *menuitem = gtk_menu_item_new_with_label("Menu");
            gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
            gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
            g_signal_connect(menuitem, "activate", G_CALLBACK(sensitivity), &sens);
            menuitem = gtk_menu_item_new_with_label("Clear");
            gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
            gtk_widget_add_accelerator(menuitem, "activate", shortcuts, GDK_KEY_D, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
            sens.menuitem = menuitem;
            g_signal_connect(sens.menuitem, "activate", G_CALLBACK(menuitem_click), sens.buffer);
        g_signal_connect_after(window, "key-press-event", G_CALLBACK(check_sensitivity_on_ctrl), &sens);
    
            GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
            gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, FALSE, 0);
            gtk_box_pack_start(GTK_BOX(box), view, TRUE, TRUE, 0);
    
            gtk_container_add(GTK_CONTAINER(window), box);
    
            gtk_widget_show_all(window);
    
            gtk_main();
    }