Gtk4 Gtk.PopoverMenu Sliding submenus disable app shortcuts and keyboard accelerators

,

Hi All,

Showing a submenu in a popovermenu in Gtk4 results in app accelerators not working. If a submenu is dismissed by choosing an action, or by clicking outside the menu to dismiss it, all app accelerator keyboard shortcuts stop working.

If the main menu is shown again, the accelerators work again. Choosing an item in the main menu or dismissing the main menu with escape or outside click does not cause the app accelerators to stop working, only showing, dismissing, or choosing an item in the submenu does.

A sample app is attached. Once the submenu is chosen and dismissed, keyboard shortcuts will not log any messages. Once the main menu is shown and dismissed, the keyboard shortcuts work again.

Is there something wrong with the program I am writing, gjs or gtk4?

       #!/usr/bin/env gjs

imports.gi.versions.Gtk = "4.0";
const { Gio, Gtk } = imports.gi;

class ImageViewerWindow {
    constructor(app) {
        this._app = app;
        this._window = null;
        this._box = null;
        this._fileChooserButton = null;
    }

    _buildUI() {
        this._window = new Gtk.ApplicationWindow({
            application: this._app,
            defaultHeight: 600,
            defaultWidth: 800
        });
        this._box = new Gtk.Box({
            orientation: Gtk.Orientation.VERTICAL
        });

        this._fileChooserButton = Gtk.Button.new_with_label('Menu');

        this._fileChooserButton.connect('clicked', (button) => {
            log('activated')
            let popupmenu = Gtk.PopoverMenu.new_from_model(this.desktopBackgroundGioMenu);
            //this.popupmenu = Gtk.PopoverMenu.new_from_model_full(this.desktopBackgroundGioMenu, Gtk.PopoverMenuFlags.NESTED);
            popupmenu.set_parent(this._fileChooserButton);
            popupmenu.popup();
        });

        this._box.append(this._fileChooserButton);
        this._box.show();

        this._window.set_child(this._box);
        
        this.desktopBackgroundGioMenu = Gio.Menu.new();

        this.desktopBackgroundGioMenu.append("New Folder", "app.doNewFolder");

        this.pasteUndoRedoMenu = Gio.Menu.new();
        this.pasteUndoRedoMenu.append("Paste", "app.doPaste");
        this.pasteUndoRedoMenu.append("Undo", "app.doUndo");
        this.pasteUndoRedoMenu.append("Redo", "app.doRedo");

        this.desktopBackgroundGioMenu.append_submenu("Paste Menu", this.pasteUndoRedoMenu);
        
        let newFolder = Gio.SimpleAction.new('doNewFolder', null);
        newFolder.connect('activate', () => {
            log('Creating New Folder');
        });
        this._app.add_action(newFolder);
        this._app.set_accels_for_action('app.doNewFolder', ['<Control><Shift>N']);
        
        this.doPasteSimpleAction = Gio.SimpleAction.new('doPaste', null);
        this.doPasteSimpleAction.connect('activate', () => {
            log('Pasting');
        });
        this._app.add_action(this.doPasteSimpleAction);
        this._app.set_accels_for_action('app.doPaste', ['<Control>V'])

        this.doUndoSimpleAction = Gio.SimpleAction.new('doUndo', null);
        this.doUndoSimpleAction.connect('activate', () => {
            log('Undoing');
        });
        this._app.add_action(this.doUndoSimpleAction);
        this._app.set_accels_for_action('app.doUndo', ['<Control>Z'])

        this.doRedoSimpleAction = Gio.SimpleAction.new('doRedo', null);
        this.doRedoSimpleAction.connect('activate', () => {
            log('Redoing');
        });
        this._app.add_action(this.doRedoSimpleAction);
        this._app.set_accels_for_action('app.doRedo', ['<Control><Shift>Z'])
    }

    getWidget() {
        this._buildUI();
        return this._window;
    }
}

const application = new Gtk.Application({
    application_id: 'org.gnome.Sandbox.MenuExample',
    flags: Gio.ApplicationFlags.FLAGS_NONE
});

application.connect('activate', app => {
    let activeWindow = app.activeWindow;

    if (!activeWindow) {
        let imageViewerWindow = new ImageViewerWindow(app);
        activeWindow = imageViewerWindow.getWidget();
    }

    activeWindow.present();
});

application.run(null);

This won’t ever work correctly: random widgets cannot have a random GtkPopover attached to them in GTK4, like they did in GTK3.

You need to create a subclass of GtkButton, and add the GtkPopover to it; then you need to modify the size_allocate virtual function so that the popover is correctly presented at the right location.

This program was just an example, but I guess the same concept applies- I was attaching it to a Gtk.Fixed as the parent.

The documentation is a little confusing- “It is primarily meant to provide context-dependent information or options. Popovers are attached to a parent widget. By default,
they point to the whole widget area, although this behavior can be changed with [method@Gtk.Popover.set_pointing_to].”

This seems to suggest that it is to be attached to a parent. And it can point to a rectangle in the parents co-ordinate system. Making a subclass every time you have to use a popover would become a little onerous.

Can it be attached to some widgets and not others? I ask, because the easiest thing to do would be to attach it to the Gtk.Window containing the widget. Can it be easily attached to a Gtk.Window?

Thanks for your help.

Actually the popovermenu works, it is presented at the right location and points to the correct rectangle, behaves perfectly, till you show a submenu, at which point the app accelerators stop working after dismissing it, everything else seems to work perfectly.

The suggested solution does not work. I can make a subclass and populate it with a popover-menu. I can popup the menu, populate it with a menu and show it. The menu works correctly and performs all the actions of the Gio Menu. However the app accelerators only work when the main menu is shown. If a sub menu is shown, all app accelerators stop working just like my original problem.

Unless of course there is something wrong with my code. I just created the subclass and added the popover menu. The destroy signals are not connected as I was just checking-

var gridWithPopover = GObject.registerClass(
class gridWithPopover extends Gtk.DrawingArea {
    _init(params) {
        super._init(params);
        this.menu = Gio.Menu.new();
        this.popOverMenu = Gtk.PopoverMenu.new_from_model(this.menu);
        this.popOverMenu.set_parent(this);
    }
});

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.