Springe zum Hauptinhalt

Desktopintegrationsbemühungen

Desktopintegration: Icon, Headerbar, Kommandozeilenoptionen

(Fortsetzung zum Artikel Gtk.Application)

/images/15_application.thumbnail.png

Glade

Icon

Einem Fenster lässt sich direkt in Glade unter "Allgemein > Darstellung > Symboldatei" ein Icon auswählen. Das Problem dabei ist, dass Glade Bilddateien nur anzeigt, wenn sie sich im selben Verzeichnis wie die Glade-Datei selbst befinden, auch wenn man ein anderes Verzeichnis auswählt.

Am einfachsten behebt man dies, indem man die Gladedatei in einem Texteditor bearbeitet und den (relativen) Pfad zum Icon angibt. Diese Einstellung bleibt auch erhalten, wenn die Datei später wieder mit Glade bearbeitet und gespeichert wird:

<object class="GtkApplicationWindow" id="window">
  ...
  <!-- <property name="icon">duckyou.svg</property> -->
  <property name="icon">../files/duckyou.svg</property>
  ...

Headerbar

Die Headerbar wurde mit GNOME 3.10 eingeführt und vereint Titelleiste und Toolbar in einem Widget, d.h neben Titel und Untertitel können rechts und/oder links verschiedene Widgets (Menüs, Buttons) angelegt sowie clientseitige Fensterkontrollknöpfe angezeigt werden.

Die Headerbar ist optional. Möchte man sie nutzen, muss in den Fenstereinstellungen "Allgemein > Darstellung > Klienseitige Fensterdekoration" ausgewählt werden. Daraufhin erscheint im oberen Bereich des Fensters ein reservierter Bereich, in dem die Headerbar platziert wird. Wird die Headerbar außerhalb davon platziert, wird weiterhin zusätzlich die normale Titelleiste angezeigt.

/images/15_headerbarglade.png

Kommandozeilenoptionen

Gtk.Application stellt die erforderlichen Mittel für anwendungseigene Kommandozeilenoptionen zur Verfügung (Handling command line options in GApplication).

Optionen anlegen

Verfügbare Optionen werden mit der Funktion add_main_option_entries(entrylist) hinzugefügt. Diese Einträge haben das Format GLib.OptionEntry, welches allerlei Parameter besitzt.

def __init__(self):
    self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))
    self.app.add_main_option_entries([
        self.create_option_entry("--version", description="Show version numbers and exit"),
        self.create_option_entry("--setlabel", description="Set label widget", arg=GLib.OptionArg.STRING,),
        self.create_option_entry("--bollocks", description="Additional test option - exit"),
    ])

def create_option_entry(self,
                        long_name,
                        short_name=None,
                        flags=0,
                        arg=GLib.OptionArg.NONE,
                        arg_data=None,
                        description=None,
                        arg_description=None):
    option = GLib.OptionEntry()
    option.long_name = long_name.lstrip("-")
    option.short_name = 0 if not short_name else ord(short_name.lstrip("-"))
    option.flags = flags
    option.arg = arg
    option.arg_data = arg_data
    option.description = description
    option.arg_description = arg_description
    return option

Shortnames

Eine Option kann ein aus einem Buchstaben (oder besser gesagt "printable ASCII character different from ‘-‘") bestehenden Synonmym besitzen, den Shortname. Bei der Option --help ist dies gemeinhin -h.

Die short_name-Variable von OptionEntry ist allerdings integer. Die in der Dokumentation nicht ersichtliche Lösung besteht darin, in der Variable die Dezimalkodierung des entsprechenden Zeichens zu übergeben, also etwa 97 für "a". Bei ungültigen Werten wird eine Fehlermeldung ausgegeben. Optionen ohne Shortname erhalten den Wert 0.

Signal verbinden

Der Gtk.Application-eigene "handle-local-options"-Handler verarbeitet die Optionen. Sobald Optionen angelegt sind, wird dieses Signal noch vor dem "startup"-Signal ausgelöst

self.app.connect("handle-local-options", self.on_local_option)

Optionen verarbeiten

Die an die Handler-Funktion übergebene option ist ein Element der Klasse GLib.VariantDict. Mit contains("option") lässt sich nach der übergebenen Option suchen.

def on_local_option(self, app, option):
    if option.contains("option1"):
        #do something and exit normally
        return 0
    elif option.contains("option2"):
        #do something different and exit
        return 0
    elif option.contains("option3"):
        #do more and continue
    return -1

Ein übergebener String kann extrahiert werden, indem GLib.VariantDict mit end() in GLib.Variant konvertiert wird, das sich wiederum mit keys() auslesen lässt:

var = GLib.VariantDict.end(option)
option_string = var[var.keys()[0]]
Ein Return-Wert ist zwingend erforderlich, er entspricht dabei dem Exit-Status:
  • -1: Anwendung wird weiter ausgeführt

  • 0: erfolgreiche Ausführung, Anwendung wird beendet, "startup/activate" werden nicht ausgeführt

  • 1 bzw. positiver Wert: nicht erfolgreiche Ausführung, Anwendung wird beendet

Optionen übergeben

Die Option, die immer verfügbar ist, ist --help. Hier werden unter "Anwendungsoptionen" die angelegten Optionen samt Beschreibung aufgeführt. Die Optionen können wie definiert angegeben werden:

$ python script.py --version
Python: 3.6.0
GTK+:   3.22.6

oder mit --setlabel einen String an Gtk.Label übergeben:

$ python script.py --setlabel "I can haz options!"

Listings

Python

15_application.py (Source)

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

import sys
import setproctitle

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


class ExampleApp:

    def __init__(self):

        setproctitle.setproctitle("Application test")
        GLib.set_prgname("testapp")

        self.app = Gtk.Application.new("org.application.test", Gio.ApplicationFlags(0))

        self.app.add_main_option_entries([
            self.create_option_entry("--version",
                                     description="Show version numbers and exit",
                                    ),
            self.create_option_entry("--setlabel",
                                     description="Set label widget",
                                     arg=GLib.OptionArg.STRING,
                                    ),
            self.create_option_entry("--bollocks",
                                     description="Additional test option - exit",
                                    ),
        ])

        self.app.connect("handle-local-options", self.on_local_option)
        self.app.connect("activate", self.on_app_activate)

    def on_local_option(self, app, option):
        self.option_string = ""
        if option.contains("version"):
            var = GLib.VariantDict.end(option)
            print("Python: {}".format(sys.version[:5]))
            print("GTK+:   {}.{}.{}".format(Gtk.MAJOR_VERSION,
                                             Gtk.MINOR_VERSION,
                                             Gtk.MICRO_VERSION,
                                             ))
            return 0
        elif option.contains("bollocks"):
            return 1
        elif option.contains("setlabel"):
            var = GLib.VariantDict.end(option)
            self.option_string = var[var.keys()[0]]
        return -1

    def create_option_entry(self,
                            long_name,
                            short_name=None,
                            flags=0,
                            arg=GLib.OptionArg.NONE,
                            arg_data=None,
                            description=None,
                            arg_description=None,
                            ):
        option = GLib.OptionEntry()
        option.long_name = long_name.lstrip("-")
        option.short_name = 0 if not short_name else ord(short_name.lstrip("-"))
        option.flags = flags
        option.arg = arg
        option.arg_data = arg_data
        option.description = description
        option.arg_description = arg_description
        return option

    def on_app_activate(self, app):
        builder = Gtk.Builder()
        builder.add_from_file("15_application.glade")

        self.obj = builder.get_object
        self.obj("window").set_application(app)
        self.obj("label").set_text(self.option_string)
        self.obj("window").show_all()

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


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

Glade

15_application.glade (Source)

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
  <requires lib="gtk+" version="3.20"/>
  <object class="GtkMenu" id="menu">
    <property name="visible">True</property>
    <property name="can_focus">False</property>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 1</property>
        <property name="use_underline">True</property>
      </object>
    </child>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 2</property>
        <property name="use_underline">True</property>
      </object>
    </child>
    <child>
      <object class="GtkMenuItem">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="label" translatable="yes">MenuItem 3</property>
        <property name="use_underline">True</property>
      </object>
    </child>
  </object>
  <object class="GtkApplicationWindow" id="window">
    <property name="width_request">400</property>
    <property name="height_request">300</property>
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Titel</property>
    <property name="icon">../files/duckyou.svg</property>
    <property name="show_menubar">False</property>
    <child>
      <object class="GtkBox">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="orientation">vertical</property>
        <child>
          <object class="GtkImage">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="pixbuf">../files/duckyou.svg</property>
          </object>
          <packing>
            <property name="expand">True</property>
            <property name="fill">True</property>
            <property name="position">0</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="pack_type">end</property>
            <property name="position">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkSeparator">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
          </object>
          <packing>
            <property name="expand">False</property>
            <property name="fill">True</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
    <child type="titlebar">
      <object class="GtkHeaderBar">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <property name="title">Titel</property>
        <property name="subtitle">Untertitel</property>
        <property name="show_close_button">True</property>
        <child>
          <object class="GtkMenuButton">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="popup">menu</property>
            <child>
              <placeholder/>
            </child>
          </object>
        </child>
        <child>
          <object class="GtkButton">
            <property name="label">gtk-no</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">2</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton">
            <property name="label">gtk-yes</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <property name="use_stock">True</property>
            <property name="always_show_image">True</property>
          </object>
          <packing>
            <property name="pack_type">end</property>
            <property name="position">2</property>
          </packing>
        </child>
      </object>
    </child>
  </object>
</interface>

Kommentare

Comments powered by Disqus