Springe zum Hauptinhalt

Selbständig

Programm als Gtk.Application laufen lassen

Gtk.Application handhabt verschiedene wichtige Aspekte einer GTK+-Anwendung, wie etwa der GTK+-Initialisierung, dem Sessionmanagement und der Desktopintegration.

/images/14_application.thumbnail.png

XML-Dateien

Glade

In Glade verändert sich im Prinzip nichts. Als Hauptfenster sollten Gtk.ApplicationWindows zum Einsatz kommen. Als Beispiel wird hier das Gladefile aus dem Artikel zu Dialogen wieder verwendet.

GMenu

Die GNOME-Shell unterstützt Appmenüs, erreichbar über das obere Panel. Die XML-Datei muss so formatiert sein, dass sie als GioMenu erkannt wird:

<?xml version="1.0"?>
<interface>
 <menu id="appmenu">
    <section>
      <item>
        <attribute name="label" translatable="yes">Menu Item</attribute>
        <attribute name="action">app.item</attribute>
      </item>
    </section>
  </menu>
</interface>

Von Glade selbst würde diese XML-Datei als veraltetes Format erkannt, aber sie lässt sich trotzdem von GtkBuilder laden und anschließend kann man die Identifier nutzen.

Bemerkung

Es ist geplant, dass die Appmenüs aufgrund der fehlenden Akzeptanz in naher Zukunft aus dem GNOME-Desktop entfernt werden. Siehe Ankündigung Farewell, application menus!.

Python

Initialisierung von GtkApplication

Bei der Initialisierung wird eine application_id- und flags-Angabe benötigt. Letztere können in der Regel bei 0 bzw. FLAGS_NONE belassen werden (siehe Gio.ApplicationFlags), die Konventionen für die application_id sind hier dokumentiert.

Die Application kann nun mit verschiedenen Signalen verbunden werden, die zu bestimmten Ereignissen ausgelöst werden, aber es muss mindestens activate verbunden werden:

def __init__(self):

    self.app = Gtk.Application.new("org.application.test", 0)
    # self.app.connect("startup", self.on_app_startup) # optional
    self.app.connect("activate", self.on_app_activate)
    # self.app.connect("shutdown", self.on_app_shutdown) # optional

def on_app_activate(self, app):

    # setting up GtkBuilder etc.
    ...
    ...
    ...

Appmenu

Wie oben bereits erwähnt, lässt sich die GMenu-XML von GtkBuilder laden, dann wird das Menü der Application zugewiesen:

builder.add_from_file("menu.ui")
app.set_app_menu(builder.get_object("appmenu"))

Die zu den Menüeinträgen verknüpften Funktionen müssen nun als Actions, genauer GioSimpleActions, erstellt und analog zur herkömmlichen Signalverknüpfung über connect verbunden werden.

def add_simple_action(self, name, callback):
    action = Gio.SimpleAction.new(name)
    action.connect("activate", callback)
    self.app.add_action(action)

Im Beispiel werden Actions zum Aufrufen der Dialoge erstellt.

Starten und Beenden

GtkApplication übernimmt die Handhabung des GTK+-Mainloops, das heißt, es nicht mehr notwendig GTK+ manuell zu starten oder zu beenden. Stattdessen werden run() und quit() verwendet:

Gtk.main()      ->  app.run(argv)
Gtk.main_quit() ->  app.quit()

Beendet man das Programm über den [X]-Button oder den "Schließen"-Eintrag des Appmenus (immer vorhanden), wird automatisch das "shutdown"-Signal ausgelöst (siehe oben). Das heißt, es müssen keine entsprechenden Signale definiert werden. "Shutdown" wird auch ausgelöst, wenn es bei der Initialisierung nicht mit einer Funktion verbunden wird.

Listings

Python

14_application.py (Source)

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio


class Handler:

    def on_window_destroy(self,window):
        window.close()

    def on_dialog_delete_event(self,widget,event):
        widget.hide_on_delete()
        return True

    def on_aboutbutton_clicked(self,widget):
        app.obj("aboutdialog").show_all()

    def on_messagebutton_clicked(self,widget):
        app.obj("messdialog").format_secondary_text("")
        app.obj("messdialog").show_all()

    def on_dialog_response(self,widget,response):
        if response == -8:
            widget.hide_on_delete()
        elif response == -9:
            widget.format_secondary_text("Doch!")

class ExampleApp:

    def __init__(self):

        self.app = Gtk.Application.new("org.application.test",
                                        Gio.ApplicationFlags(0),
                                       )
        self.app.connect("startup", self.on_app_startup)
        self.app.connect("activate", self.on_app_activate)
        self.app.connect("shutdown", self.on_app_shutdown)

    def on_app_startup(self, app):
        print("Gio.Application startup signal emitted")

    def on_app_activate(self, app):
        print("Gio.Application activate signal emitted")
        builder = Gtk.Builder()
        builder.add_from_file("13_dialoge.glade")
        builder.add_from_file("14_giomenu.ui")
        builder.connect_signals(Handler())

        app.set_app_menu(builder.get_object("appmenu"))
        self.obj = builder.get_object
        self.obj("window").set_application(app)

        # display application name in upper panel of the GNOME Shell (deprecated)
        # self.obj("window").set_wmclass("Application test","Application test")
        self.obj("window").show_all()

        self.add_simple_action("about", self.on_action_about_activated)
        self.add_simple_action("message", self.on_action_message_activated)
        self.add_simple_action("quit", self.on_action_quit_activated)

    def on_app_shutdown(self, app):
        print("Gio.Application shutdown signal emitted")

    def add_simple_action(self, name, callback):
        action = Gio.SimpleAction.new(name)
        action.connect("activate", callback)
        self.app.add_action(action)

    def on_action_about_activated(self, action, user_data):
        self.obj("aboutdialog").show_all()

    def on_action_message_activated(self, action, user_data):
        Handler().on_messagebutton_clicked(self)

    def on_action_quit_activated(self, action, user_data):
        self.app.quit()

    def run(self, argv):
        self.app.run(argv)


app = ExampleApp()
app.run(sys.argv)

Glade

13_dialoge.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkApplicationWindow" id="window">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <signal name="destroy" handler="on_window_destroy" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkButton" id="aboutbutton">
            <property name="label" translatable="yes">GtkAboutDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_aboutbutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="messagebutton">
            <property name="label" translatable="yes">GtkMessageDialog</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="on_messagebutton_clicked" swapped="no"/>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">1</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
  <object class="GtkAboutDialog" id="aboutdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="attached_to">window</property>
    <property name="program_name">Glade-Tutorial</property>
    <property name="version">1.0</property>
    <property name="logo_icon_name">help-about</property>
    <property name="license_type">mit-x11</property>
    <signal name="delete-event" handler="on_dialog_delete_event" swapped="no"/>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="layout_style">end</property>
            <child>
              <object class="GtkButton" id="button1">
                <property name="label">gtk-ok</property>
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="use_stock">True</property>
              </object>
              <packing>
                <property name="expand">True</property>
                <property name="fill">True</property>
                <property name="position">1</property>
              </packing>
            </child>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
    <action-widgets>
      <action-widget response="-8">button1</action-widget>
    </action-widgets>
  </object>
  <object class="GtkMessageDialog" id="messdialog">
    <property name="can_focus">False</property>
    <property name="type_hint">dialog</property>
    <property name="transient_for">window</property>
    <property name="buttons">yes-no</property>
    <property name="text" translatable="yes">&lt;b&gt;MessageDialog schließen?&lt;/b&gt;</property>
    <property name="use_markup">True</property>
    <signal name="response" handler="on_dialog_response" swapped="no"/>
    <child type="titlebar">
      <placeholder/>
    </child>
    <child internal-child="vbox">
      <object class="GtkBox">
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">2</property>
        <child internal-child="action_area">
          <object class="GtkButtonBox">
            <property name="can_focus">False</property>
            <property name="homogeneous">True</property>
            <property name="layout_style">end</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">False</property>
            <property name="position">0</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

GMenu

14_giomenu.ui (Source)

<?xml version="1.0"?>
<interface>
  <menu id="appmenu">
    <section>
      <item>
        <attribute name="label" translatable="yes">About</attribute>
        <attribute name="action">app.about</attribute>
      </item>
      <item>
        <attribute name="label" translatable="yes">Message</attribute>
        <attribute name="action">app.message</attribute>
      </item>
    </section>
    <section>
      <item>
        <attribute name="label" translatable="yes">Quit</attribute>
        <attribute name="action">app.quit</attribute>
        <attribute name="accel">&lt;Primary&gt;q</attribute>
      </item>
    </section>
  </menu>
</interface>

Kommentare

Comments powered by Disqus