John Smith
John Smith

Reputation: 1048

Accelerators stop responding when menubar gets hidden

I have a glade-based UI for gtk3, and I set accelerators fields for several menuitems. I'm not sure what GtkBuilder does exactly behind the scenes when it's loading the glade file (use a global GtkAccelGroup?), but the end result is, when I hide the menubar, the accelerator shortcuts stop working.

I'm wondering whether there is a way of getting the accelerators working even when the menu is not visible, while still sticking to glade as much as possible.

Upvotes: 2

Views: 662

Answers (3)

fool
fool

Reputation: 518

There is a workaround in https://bugzilla.gnome.org/show_bug.cgi?id=129349:

g_signal_connect(menu, "can_activate_accel", G_CALLBACK(gtk_true), NULL);

Upvotes: 0

daw
daw

Reputation: 2049

Here is my solution for xournalpp which walks the menubar and rebinds every accelerator to the main window:

Header

class MainWindow: public GladeGui {
public:
   void rebindMenubarAccelerators();
private:
    static void rebindAcceleratorsMenuItem(GtkWidget* widget, gpointer user_data);
    static void rebindAcceleratorsSubMenu(GtkWidget* widget, gpointer user_data);
    static gboolean isKeyForClosure(GtkAccelKey* key, GClosure* closure, gpointer data);
    static gboolean invokeMenu(GtkWidget* widget);
    GtkAccelGroup* globalAccelGroup;
}

Implementation

gboolean MainWindow::isKeyForClosure(GtkAccelKey* key, GClosure* closure, gpointer data) { return closure == data; }

gboolean MainWindow::invokeMenu(GtkWidget* widget) {
    // g_warning("invoke_menu %s", gtk_widget_get_name(widget));
    gtk_widget_activate(widget);
    return TRUE;
}

void MainWindow::rebindAcceleratorsMenuItem(GtkWidget* widget, gpointer user_data) {
    if (GTK_IS_MENU_ITEM(widget)) {
        GtkAccelGroup* newAccelGroup = reinterpret_cast<GtkAccelGroup*>(user_data);
        GList* menuAccelClosures = gtk_widget_list_accel_closures(widget);
        for (GList* l = menuAccelClosures; l != NULL; l = l->next) {
            GClosure* closure = reinterpret_cast<GClosure*>(l->data);
            GtkAccelGroup* accelGroup = gtk_accel_group_from_accel_closure(closure);
            GtkAccelKey* key = gtk_accel_group_find(accelGroup, isKeyForClosure, closure);

            // g_warning("Rebind %s : %s", gtk_accelerator_get_label(key->accel_key, key->accel_mods),
            // gtk_widget_get_name(widget));

            gtk_accel_group_connect(newAccelGroup, key->accel_key, key->accel_mods, GtkAccelFlags(0),
                                    g_cclosure_new_swap(G_CALLBACK(MainWindow::invokeMenu), widget, NULL));
        }

        MainWindow::rebindAcceleratorsSubMenu(widget, newAccelGroup);
    }
}

void MainWindow::rebindAcceleratorsSubMenu(GtkWidget* widget, gpointer user_data) {
    if (GTK_IS_MENU_ITEM(widget)) {
        GtkMenuItem* menuItem = reinterpret_cast<GtkMenuItem*>(widget);
        GtkWidget* subMenu = gtk_menu_item_get_submenu(menuItem);
        if (GTK_IS_CONTAINER(subMenu)) {
            gtk_container_foreach(reinterpret_cast<GtkContainer*>(subMenu), rebindAcceleratorsMenuItem, user_data);
        }
    }
}

// When the Menubar is hidden, accelerators no longer work so rebind them to the MainWindow
// It should be called after all plugins have been initialised so that their injected menu items are captured
void MainWindow::rebindMenubarAccelerators() {
    this->globalAccelGroup = gtk_accel_group_new();
    gtk_window_add_accel_group(GTK_WINDOW(this->getWindow()), this->globalAccelGroup);

    GtkMenuBar* menuBar = (GtkMenuBar*)this->get("mainMenubar");
    gtk_container_foreach(reinterpret_cast<GtkContainer*>(menuBar), rebindAcceleratorsSubMenu, this->globalAccelGroup);
}

Upvotes: 0

Arthur Borsboom
Arthur Borsboom

Reputation: 341

Maybe you can try to stick the accelerators not to the menu, but one level higher in your application, for example the window? In my own application I do it like this.

accel_group = gtk_accel_group_new ();
gtk_window_add_accel_group (GTK_WINDOW (pad), accel_group);

pad->priv->menu = menu_get_popup_no_highlight (pad, accel_group);
pad->priv->highlight_menu = menu_get_popup_highlight (pad, accel_group);

gtk_accel_group_connect (accel_group, GDK_KEY_Q, GDK_CONTROL_MASK, 0, g_cclosure_new_swap (G_CALLBACK (xpad_app_quit), pad, NULL));

The two menu assignments have their own accelerators which are working even when not visible.

Does this help you?

Upvotes: 1

Related Questions