Reputation: 1048
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
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
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
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