gtkgnomegtk4gjs

Adwaitia preferences dialog that pops over the application window, rather than poping up within it


I have a GTK4 Adwaita application built with GJS. I'm trying to add a PreferencesDialog that pops up over the ApplicationWindow but blocks interaction with the window, similar to a lot of other Adwaita applications (such as Gnome Text Editor). I dont want it to popup up within the window, as it's then confined to the window dimensions.

From what I can see, there are two options:

So neither of these have the desired effect. Perhaps I'm missing some additional configuration required to match the behavior of Gnome Text Editors Preferences Dialog, or perhaps this is a limitation of GJS?

Basic example

Adw.init();

const app = new Adw.Application();

app.connect("activate", () => {
  const win = new Adw.ApplicationWindow({
    application: app,
    defaultHeight: 200,
    defaultWidth: 200,
    resizable: true,
  });

  const showDialog = new Gtk.Button({ label: "Show dialog" });

  showDialog.connect("clicked", () => {
    const dialog = new Adw.PreferencesDialog({
      presentationMode: Adw.DialogPresentationMode.FLOATING,
    });
    dialog.present(null); // becomes sibling, completely detached from win ❌
    // vs dialog.present(win); // becomes child, confined to win dimensions ❌
  });

  win.set_content(showDialog);

  win.connect("close-request", () => app.quit());

  win.present();
});

app.run([imports.system.programInvocationName].concat(ARGV));

Gnome Text Editor Behavior

I've tried to include a gif demonstrating how this works in Gnome Text Editor, but it keeps failing to upload. I've posted the example on Imgur.

The main thing I notice when inspecting the widgets of Gnome Text Editor while the PreferencesDialog is open, is that the dialog is a sibling of the window, not a child of it. Again, I havent been able to upload an image to StackOverflow showing this, so an image is included on Imgur

I'm not very familiar with C code, so I havent been able to fully understand how Gnome Text Editor implements this. Some things I notice, however:

So I'd really like to understand how Gnome Text Editor (and other GTK4 Adwaita apps) manage to create a preferences dialog pop over the window, block user interaction with the window, where moving the dialog also moves the window behind it, yet is not confined to the windows dimensions. And I'd like to understand how to implement the same behavior in my GJS app.


Solution

  • Thanks to StackOverflow finding a related question, the solution is:

    Related question: How to center a dialog window on the main window in GTK?

    This doesnt seem to be how things are done in Gnome Text Editor, or Gnome Settings (they deffo use a dialog rather than windows, according to the widget inspector), but it meets my requirements.

    However, note that Adw.PreferencesWindow is deprecated as of v1.6 (as per docs). While it mostly seems to work fine, I found that it wasnt being destroyed on close-request and left a bunch of hanging resources which caused critical errors when the app shut down. Because of this, I've went back to Adw.PreferencesWindow and just accept that the dialog will always be inside the window.

    Solution

    import Adw from "@girs/adw-1";
    import Gtk from "@girs/gtk-4.0";
    
    Adw.init();
    
    const app = new Adw.Application();
    
    app.connect("activate", () => {
      const win = new Adw.ApplicationWindow({
        application: app,
        defaultHeight: 200,
        defaultWidth: 200,
        resizable: true,
      });
    
      const showDialog = new Gtk.Button({ label: "Show dialog" });
    
      showDialog.connect("clicked", () => {
        // CHANGE: create preferences window, transient for main window
        const prefsWin = new Adw.PreferencesWindow({ transient_for: win }); 
        // CHANGE: present with no params
        prefsWin.present();
      });
    
      win.set_content(showDialog);
    
      win.connect("close-request", () => app.quit());
    
      win.present();
    });
    
    app.run([imports.system.programInvocationName].concat(ARGV));