Die neuen Popovers aus GTK 3.12 sollen die klassischen Menüs ersetzen, denn sie sehen besser aus und bilden ein konsistentes Design mit den Menüs der GNOME Shell. Zudem sind sie sehr flexibel: in ihnen können viele verschiedene Widgets platziert werden.

Minimalbeispiel

GTK Popover im Minimalbeispiel aus dem letzten Tutorial.
GTK Popover im Minimalbeispiel aus dem letzten Tutorial.

Wie schon im letzten Tutorial zu HeaderBars, will ich euch die Verwendung der Popovers an einem Minimalbeispiel näherbringen. Das oben gezeigte Programm kann mit dem nur um einige Zeilen erweiterten Quelltext aus dem HeaderBar-Tutorial erzeugt werden:

class Example : Gtk.Application {
	public Example () {
		Object (application_id: "com.example.app", flags: ApplicationFlags.FLAGS_NONE);
	}

	protected override void activate () {
		Gtk.ApplicationWindow window = new Gtk.ApplicationWindow (this);
		window.set_default_size (500, 500);
		
		/*
		 * HeaderBar - siehe letztes Tutorial
		 */
		
		Gtk.HeaderBar headerBar = new Gtk.HeaderBar ();
		headerBar.set_show_close_button (true);
	
		Gtk.SearchEntry entry = new Gtk.SearchEntry ();
		entry.set_placeholder_text ("Suchen...");
		headerBar.set_custom_title (entry);
	
		Gtk.Button openButton = new Gtk.Button.from_icon_name ("document-open", Gtk.IconSize.LARGE_TOOLBAR);
		headerBar.pack_start (openButton);
	
		Gtk.Button closeButton = new Gtk.Button.from_icon_name ("document-save-as", Gtk.IconSize.LARGE_TOOLBAR);
		headerBar.pack_end (closeButton);
	
		window.set_titlebar (headerBar);
		
		/*
		 *	Ab hier Popover
		 */
		
		GLib.SimpleAction test = new GLib.SimpleAction ("test", null);
		this.add_action (test);
		
		GLib.Menu menu = new GLib.Menu ();
		menu.append ("Test", "test");
		
		Gtk.Popover pop = new Gtk.Popover (closeButton);
		pop.bind_model (menu, "app");
		
		closeButton.clicked.connect (() => {
			pop.set_visible (true);
		});
		
		window.show_all ();
	}
}

int main (string[] args) {
	return new Example ().run (args);
}

Drei Dinge sind hierbei besonders zu beachten:

  1. Ein Popover ist immer einem Widget zugeordnet, das dem Konstruktor übergeben wird. Hiervon ist auch die Ausrichtung des Popovers abhängig, die allerdings auch nachträglich geändert werden kann.
  2. Ein Popover darf nicht gleich beim Öffnen des Fensters gezeigt werden, dies führt zu Fehlverhalten. Erst nachträglich sollte es in einem Event-listener mit set_visible (true) gezeigt werden. Um das Verbergen des Popovers muss man sich nicht kümmern, es erfolgt automatisch.
  3. Wird ein Menü an das Popover gebunden, werden Menüeinträge, zu denen keine GLib.Action gefunden wird, ausgegraut und inaktiv. Dafür erwartet die Funktion bind_model als zweiten Parameter ein Präfix, das allen Aktionen vorangestellt wird. Diese Präfixe werden automatisch durch add_action erzeugt und lauten app, wenn die Aktion der Gtk.Application zugeordnet wird, und win, wenn sie einem Gtk.Window zugeordnet wird.

Am Befehl für die Kompilierung hat sich natürlich nichts geändert: Einfach als popover.vala speichern und mit

valac --pkg gtk+-3.0 -o popover popover.vala

kompilieren. Zu beachten ist, dass die aktuellen Versionen libgtk-3-0 und libgtk-3-dev (>= 3.12) benötigt werden, um das Programm zu übersetzen.

Aufhübschen

Damit die Menüeinträge ein bisschen mehr her machen, können sie mit Icons versehen werden, dafür muss das menu.append () durch ein menu.append_item () ersetzt und die entsprechenden Menü-Items gebaut werden:

GLib.SimpleAction save = new GLib.SimpleAction ("save", null);
save.activate.connect (() => {
	// Datei speichern
});
this.add_action (save);

GLib.MenuItem saveItem = new GLib.MenuItem ("Speichern", "save");
saveItem.set_icon (new GLib.ThemedIcon ("document-save"));

GLib.SimpleAction save_as = new GLib.SimpleAction ("save_as", null);
save_as.activate.connect (() => {
	// Datei speichern unter
});
this.add_action (save_as);

GLib.MenuItem saveAsItem = new GLib.MenuItem ("Speichern unter", "save_as");
saveAsItem.set_icon (new GLib.ThemedIcon ("document-save-as"));

GLib.Menu menu = new GLib.Menu ();
menu.append_item (saveItem);
menu.append_item (saveAsItem);

Das funktioniert zwar, allerdings entspricht das Ergebnis wohl nicht ganz dem, was der Benutzer von einem Popover erwartet:

Nicht ganz passend, aber möglich: Icons im GTK Popover.
Nicht ganz passend, aber möglich: Icons im GTK Popover.

Auswahl

Ein besonders passender Anwendungsfall für Popover sind Auswahlmöglichkeiten, die das Verhalten eines anderes Widgets beeinflussen, da sie genau hierfür konzipiert wurden: Sie sollen eine Möglichkeit bieten, einfache und häufig geänderte Einstellungen ‘an Ort und Stelle’ erreichbar zu machen.

Sehen wir uns also das Suchfeld – welches im Übrigen der Aktualität halber durch ein Gtk.SearchEntry ersetzt wurde – der Beispielanwendung an: Vorstellbar ist zum Beispiel, dass es eine Auswahlmöglichkeit gibt, ob im Internet oder nur lokal gesucht werden soll. Dies lässt sich leicht durch die Verwendung einer Gtk.Box oder eines Gtk.Grid erreichen:

Gtk.Grid search_grid = new Gtk.Grid ();
search_grid.set_column_spacing (10);
search_grid.set_margin_top (10);
search_grid.set_margin_end (10);
search_grid.set_margin_bottom (10);
search_grid.set_margin_start (10);

Gtk.Label search_label = new Gtk.Label ("Im Internet suchen");
search_grid.attach (search_label, 0, 0, 1, 1);

Gtk.Switch search_switch = new Gtk.Switch ();
search_grid.attach (search_switch, 1, 0, 2, 1);

search_grid.show_all ();

Gtk.Popover search_pop = new Gtk.Popover (entry);
search_pop.add (search_grid);

entry.enter_notify_event.connect ((e) => {
	search_pop.set_visible (true);
	return true;
});
Auch Gtk.Box und Gtk.Grid sind im Popover möglich.
Auch Gtk.Box und Gtk.Grid sind im Popover möglich.

Über diese Container sind natürlich sehr komplexe Inhalte für Popover möglich, fast alle Widgets, die in eine Gtk.Box oder ein Gtk.Grid gepackt werden können, können somit auch Inhalt eines Popover werden.

In diesem Sinne viel Spaß mit dieser neuen Fähigkeit von GTK und erfolgreiches Coden!