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

"""
Zweck:  Verschiedene, gerade oder gebogene Pfeile zeichnen.
Aufruf: <Image>/Filters/Formen
Author: Herbert Nemeczek
Datum:  Siehe "pluginDatum"

Änderungen:
 22.11.2018 Vorgaben für die Pfeil-Parameter werden abhängig von der Bildgröße festgelegt.
 23.11.2018 Genauigkeit für die Pfad-Interpolation mittels "gimp_vectors_stroke_interpolate()"
            vom festen Wert 10.0 auf die Berechnung je nach Bildgröße umgestellt.
            Die Genauigkeit wird in "zeichneGebogenenPfeil(self)" verwendet.
 16.12.2018 Kontrolle, ob die aktuell eingestellte Vordergrundfarbe im GIMP-Programm
            mit dem Hintergrund des geladenen Bildes übereinstimmt oder ähnlich ist.
            Der gesamte Pfeil oder Teile davon sind dann unter Umständen nicht sichtbar!
 20.12.2018 Geschachtelte IFs mittels dem Sprachkonstrukt "Dictionary" vereinfachen.
 26.12.2018 Definition der Pfeildaten in Form einer "Dictionary" und "named tubles".
 02.01.2019 Aufruf des Benutzerinferfaces zum Zeichnen mittels Klassenaufruf realisieren.
 08.01.2019 Kontrolle, ob der Pfeil und Pfeilscheibe/-kreis für den Benutzer sichtbar sind.
 12.01.2019 Internationalisierung des Plugins mittels XML-Übersetzungsdatei durchgeführt.
 21.01.2019 Umschreibung meines Python-fu-Plugins in eine echtes Python-Plugin.
 10.02.2019 Den Aufruf-Pfad im GIMP-Menü auf "Filters/Formen" & "Filters/Shapes" geändert.
            Danke für diese & nächste Anregung von Benutzer "ursa major" vom "gimpforum.de"!
 12.02.2019 Bei allen Füllungen "Vordergrund", "Verlauf" und "Muster" eingeführt
            und wenn Pfeilschaft und Pfeilspitze gleich sind, als Gesamtheit füllen.
 05.04.2019 Verwaltung (Speichern, Löschen, Laden) von Werkzeugvorgaben eingeführt.
            ACHTUNG: Die Funktionen für Abfrage/Setzen von "fade_length" fehlen und bei
              "fade_repeat"/"fade_reverse" liefern die entsprechenden Funktionen falsche Werte.
              Nach Korrektur den Hinweis in der Variablen textDaten["wkzHinwStart"] entfernen!
 13.04.2019 Kleinere Korrekturen beim Text und Layout vom Plugin- und Werkzeugvorgaben-Fenster.
 18.06.2019 Benutzerfenster (User Interface) mit Hilfe von "gimpui" gestaltet.
 26.06.2019 Benutzerfenster in 2 Ausführungen: Hilfe im Fenster oder Aufruf mittels Taste.
 27.06.2019 Der gebogene Pfeil konnte bisher noch nicht mit einem Muster befüllt werden.
 05.07.2019 Fehler beim Sichtbarkeitstest der Routine "testeSichbarkeit()" korrigiert.
 13.07.2019 Gebogener Pfeil wird mit Systemroutinen und bei "ungültig" mit Iteration berechnet.
 29.07.2019 Die deutsche Plugin-Oberfläche enthält jetzt Umlaute und das scharfe ß.
 03.08.2019 Die Einschnürung in der Mitte des Bemaßungspfeils neu definiert.
 17.08.2019 Einige kleine Verbesserungen, Vereinheitlichungen und Korrekturen.
"""

import os, re, sys
import math
import locale
import collections
from types import *
#
import gimp, gimpplugin
from gimpenums import *
pdb = gimp.pdb
from gimpshelf import shelf
#
from gimpfu import *
import gimpui
import pygtk
pygtk.require('2.0')
import gtk
#
from xml.dom import minidom

##########################################################################################
# Globale Variablen und Definitionen
pluginName       = "HN_Pfeil_zeichnen"            # Name des Plugins
pluginExtName    = pluginName.replace("_", "-")   #   Interner Name des Plugins
pluginDatum      = "2019-08-17"                   # Versiondatum des Plugins
pluginAuthor     = "Herbert Nemeczek"             # Author des Plugins
emailVomAuthor   = "Herbert.Nemeczek@t-online.de" # Authors Email-Adresse
mitHilfeImFenster= True                           # Anleitungen/Hilfe im Fenster
#
testAusgabe      = False                          # Meldungen zu Testzwecken

gimpVersion      = gimp.version                   # Aktuelle PDB-Version von GIMP
pythonVersion    = sys.version_info               # Aktuelle Python-Version
assert pythonVersion >= (2, 7)                    #   Minimal erwartete Version
pygtkVersion     = gtk.pygtk_version              # Aktuelle GTK-Version

pluginPfad       = os.path.abspath(sys.argv[0])           # Pfad des gestarteten Plugins
pluginVerz       = pluginPfad[0:pluginPfad.rfind(os.sep)] # Ordner des gestarteten Plugins
pluginDatei      = pluginPfad.split(os.sep)[-1][:-3]      # Dateiname des gestart. Plugins

##########################################################################################
# Definitionen aller Texte, deren Inhalt sich je nach Landessprache ändert
textDaten         = {
  "pluginNameImMenue"   :   "HN: Pfeil zeichnen ..."                                    ,
  "pluginPfadImMenue"   :   "Filters/Formen"                                            ,
  "pluginKurzBeschr"    :   "Verschiedene Arten von Pfeilen zeichnen."                  ,
  "pluginLangBeschr"    :
    "Mit diesem Plugin lassen sich insgesamt sieben verschiedene Arten von Pfeilen zeichnen."
    +" Dabei können viele Parameter des Pfeiles über die Plugin-Oberfläche angepasst werden."
    +" Darüber hinaus ist die Füllmethode beim Pfeilschaft, der Pfeilspitze und bei der"
    +" Schaftscheibe/dem Schaftkreis mittels der GIMP-Oberfläche zu beeinflussen."      ,
  "layGruppenName"      :   "HN: Pfeile"                                                ,
  "layPfeilName"        :   "Pfeil"                                                     ,
  "pfadName"            :   "HN: Pfeilpfad"                                             ,
  "fensterTitel"        :   "%s von %s"                                                 ,
  "anleitungKopf"       :   "Anleitung für die Erstellung eines Pfeils"                 ,
  "anleitungText"       :
    'Als Vorbereitung wurde die Pfeilebene "HN: Pfeil #" in der Gruppe "HN: Pfeile" erstellt.\n'
    +'Zusätzlich ist ein Pfad "HN: Pfeilpfad" mit zwei Ankerpunkten in der Bildmitte angelegt\n'
    +'worden, wobei der linke Ankerpunkt den Anfangspunkt des Pfades darstellt.\n'
    +'Um einen Pfeil zu erstellen, führen Sie nun bitte folgende Handgriffe aus:\n'
    +'  1. Das "Pfade"-Werkzeug auswählen und Bearbeitungsmodus "Design" sicherstellen.\n'
    +'  2. Den Anfangs- und Endpunkt des Pfades an die gewünschte Pfeilposition führen.\n'
    +'  3. Alle Einstellungen in dieser Oberfläche je nach gewünschten Pfeil vornehmen.\n'
    +'  4. Vordergrundfarbe o. Farbverlauf des Pfeiles, Hintergrundfarbe d. Schaftscheibe/Kreis\n'
    +'      und wenn gewünscht, Muster der Pfeilspitze(n) mittels GIMP-Oberfläche einstellen. \n'
    +'  5. Sind Pfadpositionen und alle Einstellungen erledigt, die "Zeichnen"-Taste anklicken.\n'
    +'  6. Ist man mit dem Ergebnis zufrieden, mit der Taste "Beenden" das Plugin verlassen.' ,
  "hinweiseKopf"        :   "Aktuelle Hinweise"                                         ,
  "bezPfeilstil"        :   "Stil und Aussehen des Pfeils"                              ,
  "tipPfeilstil"        :
    "Diese Auswahl bestimmt den Stil und damit das Aussehen des Pfeils."                ,
  "Lanzenpfeil"         :   "Lanzenpfeil (Assegai-Speer aus Afrika)"                    ,
  "Hinweispfeil"        :   "Hinweispfeil (Rechtwinkliger Schaft, Dreiecksspitze)"      ,
  "Richtungspfeil"      :   "Richtungspfeil (Schwalbenschwanz, Flügelspitze)"           ,
  "Bemassungspfeil"     :   "Bemaßungspfeil (Dreiecksspitzen an beiden Enden)"          ,
  "BeschriftPfeil"      :   "Beschriftungspfeil (Scheibe, Schaft, Dreiecksspitze)"      ,
  "Scheibengelenk"      :   "Scheibengelenkpfeil (Kreis, Schaft, Dreiecksspitze)"       ,
  "GebogenerPfeil"      :   "Gebogener Pfeil (Am Pfad, Scheibe, Dreiecksspitze)"        ,
  "bezDicke"            :   "Dicke des Pfeilschaftes [Pixel]"                           ,
  "tipDicke"            :
    "Dieser Wert definiert die Dicke/Stärke des Pfeilschaftes.\n"
    +"Der Schaft wird zwischen dem Anfangspunkt des Pfades und dem Beginn der Pfeilspitze"
    +" gezeichnet."                                                                     ,
  "bezLaenge"           :   "Länge der Pfeilspitze [Pixel]"                             ,
  "tipLaenge"           :
    "Dieser Wert definiert die Länge der Pfeilspitze(n).\n"
    +"Die Pfeilspitze(n) werden vom End- und/oder vom Anfangspunkt des Pfades"
    +" in Richtung des Pfeilschaftes gezeichnet."                                       ,
  "bezBreite"           :   "Breite der Pfeilspitze [Pixel]"                            ,
  "tipBreite"           :
    "Dieser Wert definiert die Breite der Pfeilspitze(n) am Schaft.\n"
    +"Die Pfeilspitze wird vom End- und/oder vom Anfangspunkt des Pfades"
    +" in Richtung des Pfeilschaftes gezeichnet."                                       ,
  "bezPfeilpos"         :   "Position der\nPfeilspitze"                                 ,
  "tipPfeilpos"         :
    "Diese Auswahl bestimmt die Position des Pfeilspitze am Pfad.\n"
    +"Bei einem Pfeilstil mit nur einer Pfeilspitze definiert dieser Wert"
    +" die Position der Pfeilspitze am Pfadende oder Pfadanfang."                       ,
  "txtPfeilpos"         :   ["Pfadende", "Pfadanfang"]                                  ,
  "bezSchaftfuell"      :   "Füllung des\nPfeilschaftes"                                ,
  "tipSchaftfuell"      :
    "Diese Auswahl bestimmt die Art der Füllung des Pfeilschaftes.\n"
    +"Der Pfeilschaft kann mit der Vordergrundfarbe, einem Verlauf oder einem Muster"
    +" vom Schaftanfang bis zur Pfeilspitze hin befüllt werden."                        ,
  "txtSchaftfuell"      :   ["Vordergrund", "Verlauf", "Muster"]                        ,
  "bezPfeilfuell"       :   "Füllung der\nPfeilspitze"                                  ,
  "tipPfeilfuell"       :
    "Diese Auswahl bestimmt die Art der Füllung der Pfeilspitze.\n"
    +"Die Pfeilspitze kann mit der Vordergrundfarbe, einem Verlauf oder einem Muster"
    +" befüllt werden."                                                                 ,
  "txtPfeilfuell"       :   ["Vordergrund", "Verlauf", "Muster"]                        ,
  "bezSchaftkreis"      :   "Durchmesser von\nSchaftscheibe/Kreis [Pixel]"              ,
  "tipSchaftkreis"      :
    "Dieser Wert definiert den Durchmesser der Schaftscheibe/des Schaftkreises.\n"
    +"Die Scheibe/der Kreis wird am Anfang des Pfeilschaftes gezeichnet."
    +" Es kann beispielsweise zum Zwecke der Beschriftung verwendet werden."            ,
  "bezButZeichnen"      :   "Pfeil zeichnen"                                            ,
  "tipButZeichnen"      :
    "Diese Taste ist nötig, wenn der Pfad oder Einstellungen in GIMP,"
    +" wie beispielsweise die Vordergrundfarbe, oder Einstellungen der Plugin-Oberfläche"
    +" geändert wurden.\n"
    +"Das Plugin wird nach dem Zeichnen NICHT beendet!"                                 ,
  "bezButVorgaben"      :   "Werkzeugvorgaben"                                          ,
  "tipButVorgaben"      :
    "Werkzeugvorgaben bearbeiten.\n"
    +"Bei Betätigung dieser Taste wird ein separates Fenster geöffnet mittels dem"
    +" Werkzeugvorgaben gespeichert, gelöscht oder geladen werden können."              ,
  "bezButHilfe"         :   "Hilfe"                                                     ,
  "tipButHilfe"         :
    "Ausgabe einer Anleitung für die Erstellung eines Pfeiles und zusätzliche Hilfen!"  ,
  "bezButAbbrechen"     :   "Plugin abbrechen"                                          ,
  "tipButAbbrechen"     :
    "Änderungen der Plugin-Sitzung werden rückgängig gemacht\n"
    +"und das Plugin danach beendet."                                                   ,
  "bezButBeenden"       :   "Plugin beenden"                                            ,
  "tipButBeenden"       :
    "Der bis dahin konfigurierte Pfeil wird gezeichnet\n"
    +"und das Plugin danach beendet."                                                   ,
  "hinwAllePfeile"      :
    'Alle Pfeile werden mit dem "Pinsel"-Werkzeug bemalt. Mit "Füllung des Pfeilschaftes"\n'
    +'kann man definieren, ob der Pfeilschaft entweder mit der Vordergrundfarbe oder einem\n'
    +'Farbverlauf gefüllt werden soll. Wurde "Vordergrund" ausgewählt, dann in GIMP die\n'
    +'gewünschte Farbe einstellen. Bei "Verlauf" das "Pinsel"-Werkzeug auswählen. In den\n'
    +'Werkzeugeinstellungen unter "Optionen der Zeichendynamik" "Länge des Verblassens"\n'
    +'und die "Wiederholung" einstellen sowie den gewünschten "Farbverlauf" wählen.\n'
    +'Die Pfeilspitze wird entweder mit der Vordergrundfarbe oder einem "Muster" befüllt.' ,
  "hinwPluginStart"     :
    'Jetzt das "Pfade"-Werkzeug auswählen, den Bearbeitungsmodus "Design" sicherstellen'
    +' und den Anfangs- sowie Endpunkt des Pfades an die gewünschten Pfeilpositionen führen.'
    +'\n\nIm Anschluss die gewünschten Einstellungen im Benutzerinterface vornehmen.'   ,
  "hinwPfeilGewaehlt"   :
    'Der Pfeilstil wurde geändert.'
    +' Jetzt die weiteren Einstellungen im Benutzerinterface vornehmen.'                ,
  "hinwPfeileMitKreis"  :
    "Dieser Pfeilstil enthält eine, mit der Hintergrundfarbe gefüllte, Schaftscheibe/-kreis."
    +" Durchmesser setzen!"                                                             ,
  "hinwAusgabePfeil"    :
    "Beim Zeichnen wird der Pfadanfang, das Pfadende und die Pfeillänge ausgegeben."    ,
  "hinwBemassPfeil"     :
    "Beim Zeichnen wird der Pfadanfang, das Pfadende, die Pfeillänge und"
    +" die Pfeilrichtung ausgegeben.\nZusätzlich werden die Pfeillänge und"
    +" die Pfeilrichtung in der aktuellen Pfeilebene abgelegt."                         ,
  "hinwGebogPfeil"      :
    "Beim Zeichnen wird der Pfadanfang und das Pfadende ausgegeben."                    ,
  "hinwAusfuehrung"     :
    "Pfadanfang=%d/%d\nPfadende=%d/%d\n\nPfeillänge=%d Pixel"                           ,
  "hinwAusfBemassung"   :
    "Pfadanfang=%d/%d\nPfadende=%d/%d\n\nPfeillänge=%d Pixel\n"
    +"Pfeilrichtung=%d Grad (Positive Gradzahl= Uhrzeigersinn)"                         ,
  "hinwAusfGebogener"   :
    "Pfadanfang=%d/%d\nPfadende=%d/%d"                                                  ,
  "hinwFuellVorderg"    :
    'Die Füllart wurde in "Vordergrund" geändert.\n\n'
    'Jetzt zur GIMP-Oberfläche wechseln und die gewünschte Vordergrundfarbe auswählen.' ,
  "hinwFuellVerlauf"    :
    'Die Füllart wurde in "Verlauf" geändert.\n\n'
    +'Jetzt zur GIMP-Oberfläche wechseln, den gewünschten Farbverlauf auswählen'
    +'\nund die "Pinsel"-Parameter einstellen.'                                         ,
  "hinwFuellMuster"     :
    'Die Füllart wurde in "Muster" geändert.\n\n'
    'Jetzt zur GIMP-Oberfläche wechseln und das gewünschte Muster auswählen.'           ,
  "meldBildZugriff"     :
    "============================================================\n"
    +"FEHLER: Der Zugriff auf das Bild beim Start des Plugins ist nicht mehr möglich!\n"
    +"Vermutlich wurde das Bild aus Versehen geschlossen.\n"
    +"\nDie Ausführung des Plugins wird abgebrochen!"                                   ,
  "meldEbenenZugriff"   :
    "============================================================\n"
    +"FEHLER: Der Zugriff auf die Pfeilebene ist nicht mehr möglich!\n"
    +"Vermutlich wurde die Ebene aus Versehen gelöscht.\n"
    +"\nDie Ausführung des Plugins wird abgebrochen!"                                   ,
  "meldPfadZugriff"     :
    "============================================================\n"
    +"FEHLER: Der Zugriff auf den Pfeilpfad ist nicht mehr möglich!\n"
    +"Vermutlich wurde der Pfad aus Versehen gelöscht.\n"
    +"\nDie Ausführung des Plugins wird abgebrochen!"                                   ,
  "meldAnkerKeinen"     :
    "============================================================\n"
    +"FEHLER: Der Pfeilpfad enthält keine Ankerpunkte mehr!\n"
    +"Vermutlich wurden aus Versehen beide Ankerpunkte des Pfads gelöscht.\n"
    +"\nBitte einen Pfad mit zwei Ankerpunkten definieren!"                             ,
  "meldAnkerEinen"      :
    "============================================================\n"
    +"FEHLER: Der Pfeilpfad enthält nur einen Ankerpunkt!\n"
    +"Vermutlich wurde ein Ankerpunkt des Pfads aus Versehen gelöscht.\n"
    +"\nBitte einen Pfad mit zwei Ankerpunkten definieren!"                             ,
  "meldAnkerZuviel"     :
    "============================================================\n"
    +"WARNUNG: Der Pfeilpfad enthält mehr als 2 Ankerpunkte!\n"
    +"Vermutlich wurden dem Pfad aus Versehen Ankerpunkte hinzugefügt.\n"
    +"\nZum Zeichnen werden die ersten beiden Ankerpunkte verwendet!"                   ,
  "meldKompZuviel"      :
    "============================================================\n"
    +"WARNUNG: Im Pfeilpfad befinden sich %s Komponenten!\n"
    +"Vermutlich wurden dem Pfad aus Versehen weitere Komponenten hinzugefügt.\n"
    +"\nZum Zeichnen wird die erste Komponente verwendet!"                              ,
  "meldPktAusserhalb"   :
    "============================================================\n"
    +"FEHLER: Mindestens ein Ankerpunkt des Pfades liegt außerhalb der Zeichenfläche!\n"
    +"Vermutlich wurden Ankerpunkte aus Versehen außerhalb des Bildes platziert.\n"
    +"\nBitte die Ankerpunkte des Pfeilpfads in die Zeichenfläche verschieben!"         ,
  "meldPfeilSichtbar"   :
    "============================================================\n"
    "INFO: Die Hintergrundfarbe der Zeichenfläche und die aktuell eingestellte\n"
    +"Vordergrundfarbe für den Pfeil ist gleich oder ähnlich.\n"
    +"\nDies kann dazu führen, dass der gesamte Pfeil unsichtbar ist\n"
    +"oder sich nur leicht vom Hintergrund des Bildes abhebt!\n"
    +"\nTipp: Die Sichtbarkeit der Hintergrund-Ebene in GIMP wechseln.\n\n"
    +'Gemessene Koordinate innerhalb vom Pfeilschaft = "%d/%d"\n'
    +'Hintergrundfarbe an der gemessener Stelle im Bild = "RGB(%s,%s,%s,%s)"\n'
    +'Vordergrundfarbe für den Pfeilschaft/Pfeilspitze = "RGB(%s,%s,%s,%s)"\n'
    +'Farbunterschied (Methode "euclidean distance") = "%s"'                            ,
  "meldScheibeSichtbar" :
    "============================================================\n"
    "INFO: Die Hintergrundfarbe der Zeichenfläche und die aktuell eingestellte\n"
    +"Hintergrundfarbe für die Pfeilscheibe/den Pfeilkreis ist gleich oder ähnlich.\n"
    +"\nDies kann dazu führen, dass die Pfeilscheibe/der Pfeilkreis unsichtbar ist\n"
    +"oder sich nur leicht vom Hintergrund des Bildes abhebt!\n"
    +"\nTipp: Die Sichtbarkeit der Hintergrund-Ebene in GIMP wechseln.\n\n"
    +'Gemessene Koordinate innerhalb der Scheibe/Kreis = "%d/%d"\n'
    +'Hintergrundfarbe an der gemessenen Stelle im Bild = "RGB(%s,%s,%s,%s)"\n'
    +'Hintergrundfarbe für die Pfeilscheibe/den Pfeilkreis = "RGB(%s,%s,%s,%s)"\n'
    +'Farbunterschied (Methode "euclidean distance") = "%s"'                            ,
  "meldLaeuftSchon"     :
    "============================================================\n"
    +'FEHLER: Eine Instanz des Plugins "%s" läuft bereits!'                             ,
  "hilfeFensterTitel"   :
    "%s - Anleitung und Hilfen"                                                         ,
  "hilfeBezButOK"       :   "OK"                                                        ,
  "hilfeTipButOK"       :
    'Die Funktion "Anleitungen und Hilfen" wird beendet.'                               ,
  "wkzFensterTitel"     :
    "%s - Werkzeugvorgaben verwalten"                                                   ,
  "wkzAnleitKopf"       :   "Hinweise, Informationen und Fehlermeldungen"               ,
  "wkzAnleitText"       :
    '"Werkzeugvorgaben verwalten" selektiert zu Beginn das Anlegen einer neuen Vorlage.\n'
    +'Soll eine Vorlage verändert, gelöscht oder geladen werden, die Vorlage auswählen!' ,
  "wkzAnleitPDB"        :
    'BEACHTE: Verwendet der Benutzer bei einem Pfeil die dynamischen Ausblendoptionen \n'
    +'  "Länge des Verblassens" und "Wiederholung" des Pinsel-Werkzeugs, werden diese\n'
    +'  Optionen in den Werkzeugvorgaben des Plugins nicht wertgleich abgelegt!\n'
    +'  Dies liegt daran, weil GIMP in der PDB (GIMP procedural database) zurzeit (April 2019)\n'
    +'  keine Methoden zur Verfügung stellt, mit denen die dynamischen Optionen des\n'
    +'  Pinsel-Werkzeugs programmgesteuert abgerufen oder eingestellt werden können.\n'
    +'  Möchte der Benutzer eine Werkzeugvorgabe ablegen, bei denen dynamische\n'
    +'  Optionen des Pinsel-Werkzeugs verwendet werden, so wird empfohlen, die\n'
    +'  Werkzeug-Voreinstellungen des Pinsels zusätzlich zu den Plugin-Vorgaben zu\n'
    +'  speichern und wiederherzustellen.'                                              ,
  "wkzHinweiseKopf"     :   "Aktuelle Hinweise"                                         ,
  "wkzBezVorgAuswahl"   :   "Werkzeugvorgabe"                                           ,
  "wkzTipVorgAuswahl"   :
    "Mit dieser Auswahl kann die Erstellung einer neuen Werkzeugvorgabe oder wenn eine"
    +" Vorgabe bereits existiert, zum Speichern, Löschen und Laden selektiert werden."  ,
  "wkzBezVorgBezeich"   :   "Kurzbezeichnung"                                           ,
  "wkzInhVorgBezeich"   :   "Kurzbezeichnung"                                           ,
  "wkzTipVorgBezeich"   :
    "Dieses Eingabefeld enthält die Kurzbezeichnung der Werkzeugvorgabe."
    +" Diese Bezeichnung wird für die Namensbildung der Vorgabendatei verwendet!"       ,
  "wkzBezVorgBeschr"    :   "Beschreibung"                                              ,
  "wkzInhVorgBeschr"    :   "Ausführliche Beschreibung"                                 ,
  "wkzTipVorgBeschr"    :
    "Dieses Eingabefeld enthält die ausführliche Beschreibung der Werkzeugvorgabe."
    +" Diese Beschreibung sollte den Zweck der Vorgabe möglichst genau definieren!"     ,
  "wkzBezButSpeichern"  :   "Vorgabe speichern"                                         ,
  "wkzTipButSpeichern"  :
    "Hiermit wird eine neue Werkzeugvorgabe erstellt oder eine Vorhandene überschrieben."
    +' Zu beachten ist, dass die Felder "Kurzbezeichnung" und "Beschreibung" nicht leer sind!' ,
  "wkzBezButLoeschen"   :   "Vorgabe löschen"                                           ,
  "wkzTipButLoeschen"   :
    "Hiermit wird die ausgewählte Werkzeugvorgabe gelöscht."                            ,
  "wkzBezButLaden"      :   "Vorgabe laden"                                             ,
  "wkzTipButLaden"      :
    "Hiermit wird die ausgewählte Werkzeugvorgabe geladen und alle Einstellungen gesetzt." ,
  "wkzBezButBeenden"    :   "Beenden"                                                   ,
  "wkzTipButBeenden"    :
    'Die Funktion "Werkzeugvorgaben verwalten" wird beendet.'                           ,
  "wkzVorgabeErstellen" :   "Neue Werkzeugvorgabe erstellen"                            ,
  "wkzHinwVorgKeine"    :   "Es konnte keine Werkzeugvorgabe gefunden werden!"          ,
  "wkzHinwVorgAnzahl"   :   "Vorhandene Werkzeugvorgaben: %s"                           ,
  "wkzHinwVorgGespeich" :
    'Die Werkzeugvorgabe\n"%s"\nwurde gespeichert!'                                     ,
  "wkzHinwVorgGelosch"  :
    'Die Werkzeugvorgabe\n"%s"\nwurde gelöscht!'                                        ,
  "wkzHinwVorgGeladen"  :
    'Die Werkzeugvorgabe\n"%s"\nwurde geladen!'                                         ,
  "wkzHinwBezLeer"      :
    'FEHLER: Das Feld mit der Kurzbezeichnung der Werkzeugvorgabe ist leer!'            ,
  "wkzHinwBeschrLeer"   :
    'FEHLER: Das Feld mit der ausführlichen Beschreibung der Werkzeugvorgabe ist leer!' ,
  "wkzMeldVorgPfad"     :
    '============================================================\n'
    'FEHLER: Das Verzeichnis\n   %s\n'
    +'für die Ablage der Werkzeugeinstellungen existiert nicht!'                        ,
  "wkzMeldDateiVorh"    :
    '============================================================\n'
    +'WARNUNG: Die Werkzeugvorgabe\n   %s\nexistiert bereits im Verzeichnis\n   %s!\n'
    +'\nSoll die Vorgabe überschrieben werden?'                                         ,
  "wkzMeldDateiLoesch"  :
    '============================================================\n'
    +'WARNUNG: Soll die Werkzeugvorgabe\n   %s\nwirklich gelöscht werden?'
}

##########################################################################################
# Falls andere Sprache vorliegt, deutsche Texte durch ev. vorhandene Übersetzung ersetzen.
# XML-Datei "<Pluginname>_<Sprachkennung>" mit Sprachkennung DE, EN, .. wird verwendet.
#
# Spracheinstellung aus der Konfigurationsdatei von GIMP besorgen, wenn vorhanden.
aktSprache = ""                         # Keine Sprache definiert
konfDateipfad = gimp.personal_rc_file("gimprc")
if os.path.exists(konfDateipfad) :      # Kontrolle, ob Konfig-Datei existiert
  try :
    konfDatei = open(konfDateipfad, "r")
  except :                              # Fehler beim Offnen der Datei
    None
  else :
    for konfZeile in konfDatei :
      if konfZeile.startswith("(language") :
        sprachCode = konfZeile.split('"')[1]
        if sprachCode != "" :
          aktSprache = sprachCode.split('_')[0].upper()
        break
    konfDatei.close()
#
# Wurde in GIMP "System Language" gewählt, steht in der Konfigurationsdatei '(language "")'.
# In diesem Fall muss die aktuelle Sprache des Betriebssystem ermittelt werden!
if aktSprache == "" :
  gebietsSchema = locale.getlocale()        # Aktuelles Gebietsschema besorgen
  sprachCode = gebietsSchema[0]
  aktSprache = sprachCode.split('_')[0].upper()
#
# Konnte keine Sprache ermittelt werden, Englisch als Default verwenden.
if aktSprache == "" : aktSprache = "EN"
#
# Übersetzung der ermittelten Spache aus einer XML-Datei einlesen
sprachOrdner = os.path.abspath(gimp.directory + os.sep + "plug-ins" + os.sep + "locale")
xmlDateiPfad = sprachOrdner + os.sep + pluginExtName + "_" + aktSprache + ".xml"
if not os.path.exists(xmlDateiPfad) :       # Wenn Sprachdatei nicht existiert
  aktSprache = "EN"                         #   mit Englisch versuchen
  xmlDateiPfad = sprachOrdner + os.sep + pluginExtName + "_" + aktSprache + ".xml"
if os.path.exists(xmlDateiPfad) :           # Kontrolle, ob Sprachdatei jetzt existiert
  try :
    # XML-Sprachdatei auf gültigen Aufbau und Namen analysieren
    xmlDatei = minidom.parse(xmlDateiPfad)
  except :
    # Fehler im Aufbau der XML-Sprachdatei melden
    meldung = '=======================================================================\n' \
      + 'FEHLER: Lesen der folgenden XML-Datei mit Übersetzungen ist fehlgeschlagen!\n'   \
      + 'Datei=%s\n' %( xmlDateiPfad )                                                    \
      + '\nVermutlich ist die innere Struktur fehlerhaft! Die Datei wird nicht gelesen!\n'\
      + '\nZum Einlesen wird das Minimal-DOM-Modul "xml.dom.minidom" verwendet.\n'        \
      + 'Warnung: Dieses Modul ist nicht gegen böswillig erstellte Daten geschützt.\n'    \
      + '\nBeispiele für illegale oder fehlerhafte Elemente:\n'                           \
      + ' - Kommentare <!-- Kommentar --> sind nicht erlaubt.\n'                          \
      + ' - Elemente <String>Text</String> nicht paarig, z.B. <String>Text</Strng>.\n'    \
      + ' - IDs mit dopelten Hochkommas, z.B. <Element ID="id"">.\n\n'                    \
      + '=======================================================================\n'       \
      + 'ERROR: Reading the following XML file with translations was unsuccessful!\n'     \
      + 'File=%s\n' %( xmlDateiPfad )                                                     \
      + '\nPresumably, the internal structure is flawed! The file is not readed in!\n'    \
      + '\nFor reading in the Minimal DOM implementation "xml.dom.minidom" is used.\n'    \
      + 'Warning: This module is not secure against maliciously constructed data.\n'      \
      + '\nExamples of illegal or faulty elements:\n'                                     \
      + ' - Comments <!-- Comment --> are not allowed.\n'                                 \
      + ' - Elements <String>Text</String> not paired, f.e. <String>Text</Strng>.\n'      \
      + ' - IDs with double quotes, f.e. <Element ID="id"">.'
    msgBox = gtk.MessageDialog(None, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
      gtk.MESSAGE_INFO, gtk.BUTTONS_OK, meldung)
    titel = textDaten["fensterTitel"] %(textDaten["pluginNameImMenue"], pluginAuthor)
    msgBox.set_title(titel)
    msgBox.run()    ; msgBox.destroy()
  else :
    # Einträge der XML-Sprachdatei einlesen und zuordnen
    for eintrag in xmlDatei.firstChild.childNodes :
      if eintrag.nodeName == "Element" :
        kennung = eintrag.getAttribute("id")
        for knoten in eintrag.childNodes :
          if knoten.nodeName == "String" :  # Typ: <String>Element</String>
            element = knoten.firstChild.data.strip()
            textDaten[kennung] = element.replace("\\n", chr(13))
          elif knoten.nodeName == "Liste" : # Typ: <Liste>Element1 | Element2 | ..</Liste>
            element = knoten.firstChild.data.strip()
            textDaten[kennung] = element.split(' | ')

##########################################################################################
# Festlegung der Pfeiltypen
pfeilTyp            = collections.namedtuple(	# Pfeiltypen und zugehörige Pfeildaten
 'pfeilTyp', 'Auswahltext PfeilPosWechsel MitSchaftkreis ZeichnenFunktion')
pfeilDaten        = collections.OrderedDict(
 [( "Lanzenpfeil",
    pfeilTyp(textDaten["Lanzenpfeil"],     True,  False, "self.zeichneLanzenpfeil()")    ),
  ( "Hinweispfeil",
    pfeilTyp(textDaten["Hinweispfeil"],    True,  False, "self.zeichneHinweispfeil()")   ),
  ( "Richtungspfeil",
    pfeilTyp(textDaten["Richtungspfeil"],  True,  False, "self.zeichneRichtungspfeil()") ),
  ( "Bemassungspfeil",
    pfeilTyp(textDaten["Bemassungspfeil"], False, False, "self.zeichneBemassungspfeil()")),
  ( "BeschriftPfeil",
    pfeilTyp(textDaten["BeschriftPfeil"],  True,  True,  "self.zeichneBeschriftPfeil()") ),
  ( "Scheibengelenk",
    pfeilTyp(textDaten["Scheibengelenk"],  True,  True,  "self.zeichneScheibengelenk()") ),
  ( "GebogenerPfeil",
    pfeilTyp(textDaten["GebogenerPfeil"],  False, True,  "self.zeichneGebogenenPfeil()") )
 ])

##########################################################################################
# Vorgaben und Variablen für das Zeichnen von Pfeilen
# Die folgenden Vorgaben gelten bei einer Bildbreite mit 4.096 Pixel
schaftDicke       = 100               # Dicke/Stärke des Pfeilschaftes
schaftDickeMin    = 1                 #  Vorgabe MIN-Wert
schaftDickeMax    = 400               #  Vorgabe MAX-Wert
pfeilLaenge       = 300               # Länge der Pfeilspitze
pfeilLaengeMin    = 0
pfeilLaengeMax    = 600
pfeilBreite       = 200               # Breite der Pfeilspitze
pfeilBreiteMin    = 1
pfeilBreiteMax    = 600
#
pfeilNamen        = pfeilDaten.keys() # Namen der möglichen Pfeiltypen
pfeilIndex        = 1                 # Default/Aktueller "pfeilNamen"-Index
pfeilPosition     = 0                 # Pfeilspitze am Pfadende zeichnen
schaftFuellInd    = 0                 # Füllung (Index&Text) des Pfeilschaftes
schaftFuellung    = textDaten["txtSchaftfuell"][schaftFuellInd]
pfeilFuellInd     = 0                 # Füllung (Index&Text) der Pfeilspitze
pfeilFuellung     = textDaten["txtPfeilfuell"][pfeilFuellInd]
schaftKreis       = 300               # Durchmesser des Schaftkreises
schaftKreisMin    = 0
schaftKreisMax    = 600

# Definitionen für das Zeichnen des Pfeils
layAktivBeiStart  = None              # Aktive Ebene bei Plugin-Start
layGruppeAngelegt = None              # Gruppen-Ebene in akt. Sitzung angelegt
layGruppe         = None              # Gruppen-Ebene (group layer)
layPfeilNummer    = None              # Nummer des akuellen Pfeils
layPfeil          = None              # Pfeil-Ebene der aktuellen Sitzung
pfadPfeil         = None              # Aktueller Pfad für den Pfeil
pfadX1, pfadY1    = None, None        # Anfangskoordinaten des Pfads
pfadX2, pfadY2    = None, None        # Endkoordinaten des Pfads

# Genauigkeit für die Pfad-Interpolation mittels "gimp_vectors_stroke_interpolate()"
# Die folgende Vorgabe gilt bei einer Bildbreite von 4096 Pixel
genauigkeit       = 4096 / 1000.0

# Sonstige Definitionen
aktuellerHinweis  = textDaten["hinwAllePfeile"]	# Aktueller Hinweis je nach Pfeilstil
bemassungLaenge   = None              # Länge des Bemaßungspfeils
bemassungWinkel   = None              # Winkel des Bemaßungspfeils

##########################################################################################
# Werkzeugvorgaben: Festlegung des Typs und aller Werkzeugvorgaben
vorgabeTyp       = collections.namedtuple(	# Vorgaben und benötigte Funktionen
 'vorgabeTyp', 'Holewert Setzewert')
vorgabeDaten     = collections.OrderedDict(
 [( "name",
    vorgabeTyp("self.wkzEntBezeichnung.get_text()", "None")                             ),
  ( "descr",
    vorgabeTyp("self.wkzEntBeschreibung.get_text()","None")                             ),
  ( "arrow_style",
    vorgabeTyp("self.comPfeilstil.get_active()",    "self.comPfeilstil.set_active")     ),
  ( "shaft_thickness",
    vorgabeTyp("self.sclDicke.get_value()",         "self.sclDicke.set_value")          ),
  ( "head_length",
    vorgabeTyp("self.sclLaenge.get_value()",        "self.sclLaenge.set_value")         ),
  ( "head_width",
    vorgabeTyp("self.sclBreite.get_value()",        "self.sclBreite.set_value")         ),
  ( "head_position",
    vorgabeTyp("self.comPfeilpos.get_active()",     "self.comPfeilpos.set_active")      ),
  ( "shaft_filling",
    vorgabeTyp("self.comSchaftfuell.get_active()",  "self.comSchaftfuell.set_active")   ),
  ( "head_filling",
    vorgabeTyp("self.comPfeilfuell.get_active()",   "self.comPfeilfuell.set_active")    ),
  ( "shaft_circle",
    vorgabeTyp("self.sclSchaftkreis.get_value()",   "self.sclSchaftkreis.set_value")    ),
  ( "foreground",
    vorgabeTyp("pdb.gimp_context_get_foreground()", "pdb.gimp_context_set_foreground")  ),
  ( "background",
    vorgabeTyp("pdb.gimp_context_get_background()", "pdb.gimp_context_set_background")  ),
  ( "gradient",
    vorgabeTyp("pdb.gimp_context_get_gradient()",   "pdb.gimp_context_set_gradient")    ),
  ( "pattern",
    vorgabeTyp("pdb.gimp_context_get_pattern()",    "pdb.gimp_context_set_pattern")     ),
  ( "brush",
    vorgabeTyp("pdb.gimp_context_get_brush()",      "pdb.gimp_context_set_brush")       ),
  ( "dynamics",
    vorgabeTyp("pdb.gimp_context_get_dynamics()",   "pdb.gimp_context_set_dynamics")    ),
  ( "palette",
    vorgabeTyp("pdb.gimp_context_get_palette()",    "pdb.gimp_context_set_palette")     ),
  ( "brush_size",
    vorgabeTyp("pdb.gimp_context_get_brush_size()", "pdb.gimp_context_set_brush_size")  ),
  ( "fade_length",
    vorgabeTyp("0",                                 "None")                             ),
  ( "fade_repeat",	vorgabeTyp(                                                         \
    "pdb.gimp_context_get_gradient_repeat_mode()",  "pdb.gimp_context_set_gradient_repeat_mode") ),
  ( "fade_reverse",	vorgabeTyp(                                                         \
    "pdb.gimp_context_get_gradient_reverse()",      "pdb.gimp_context_set_gradient_reverse") ),
  ( "blend_color",	vorgabeTyp(                                                         \
    "pdb.gimp_context_get_gradient_blend_color_space()", "pdb.gimp_context_set_gradient_blend_color_space") )
 ])
vorgabeNamen      = vorgabeDaten.keys() # Namen der Werkzeugvorgaben

##########################################################################################
# Werkzeugvorgaben: Vorgaben und Variablen
wkzVorgPfad       = os.path.join(gimp.directory, 'tool-presets')
wkzDatAnfang      = pluginExtName + "_" # Anfangsbezeichnung der Werkzeugdatei
wkzDatNamen       = None                # Namen der vorhandene Werkzeugvorgaben
wkzAnzDatNam      = 0                   # Anzahl der gelesenen Werkzeugvorgaben
wkzVorgWerte      = { "name": " " }     # Werte (Dictionary) einer Werkzeugvorgabe
wkzAktHinweis     = ""                  # Aktueller Hinweis je nach Stand

##########################################################################################
### Allgemeine Hilfroutinen.
##########################################################################################
#
# Nachricht in einem Meldungsfenster, verschiedenen Typs und Tasten, anzeigen.
# Parameter:
#   fMeldung  = Meldungstext.
#   fMeldetyp = Typ der Meldung
#               (gtk.MESSAGE_INFO, gtk.MESSAGE_ERROR, gtk.MESSAGE_WARNING, ..)
#   fKnoepfe  = Welche Meldungsknöpfe/Tasten
#               (gtk.BUTTONS_OK, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL, ..)
# Rückgabe:
#   Vom Benutzer gedrückter Knopf/Taste
#   (gtk.RESPONSE_OK, gtk.RESPONSE_YES, gtk.RESPONSE_NO, gtk.RESPONSE_CANCEL, ..)
def ausgabeMeldung(fMeldung, fMeldetyp = gtk.MESSAGE_INFO, fKnoepfe = gtk.BUTTONS_OK) :
  verhalten = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT
  msgBox = gtk.MessageDialog(None, verhalten, fMeldetyp, fKnoepfe, fMeldung)
  titel = textDaten["fensterTitel"] %(textDaten["pluginNameImMenue"], pluginAuthor)
  msgBox.set_title(titel)
  gedrueckteTaste = msgBox.run()
  msgBox.destroy()
  return gedrueckteTaste

# Methoden einer Klasse besorgen.
# Parameter:
#   fKlasse = Klassenname, z.B. Name bei beispielsweise class Name().
# Rückgabe:
#   Liste mit den Namen der Klasse, z.B. [ 'start', 'quit', 'run' ].
def holeKlassenMethoden(fKlasse) :
  return [ x for x, y in fKlasse.__dict__.items() if type(y) == FunctionType ]

# Element in einer Liste suchen, wobei das Element mit bestimmter Zeichenfolge startet.
# Parameter:
#   fListe   = Liste in der gesucht werden soll, z.B. [ 'start', 'quit', 'run' ].
#   fElement = Element, nach dem gesucht werden soll, z.B. "HN_".
# Rückgabe:
#   Gefundenes Element oder "".
def elementStartetMit(fListe, fElement) :
  for element in fListe :
    indGefunden = element.find(fElement)
    if indGefunden == 0 :
      return element;
  return ""

##########################################################################################
### Hilfroutinen, um den doppelten Start des Plugins zu verhindern.
##########################################################################################
#
# Plugin-Start vermerken
def pluginStart() :
  #### Start des Plugins vermerken
  shelf[pluginName] = True      # Erneuter Start nicht möglich.

# Plugin-Ende vermerken
def pluginEnde() :
  ### Ende des Plugins vermerken
  shelf[pluginName] = False     # Neuer Start wieder möglich.

# Kontrolle, ob Plugin ein zweites Mal gestartet wurde
# Rückgabe:
#   True    Plugin wurde bereits einmal gestartet. Zuvor Fehlermeldung!
#   False   Plugin wurde noch nicht gestartet.
def pluginGestartet() :
  if shelf.has_key(pluginName) and shelf[pluginName] :
    meldung = textDaten["meldLaeuftSchon"] %(textDaten["pluginNameImMenue"])
    ausgabeMeldung(meldung, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
    return True
  return False

##########################################################################################
### Hilfsroutinen für die Erzeugung des Benutzerinterfaces (UI: User Interface).
##########################################################################################
#
# Tabelle (Table) anlegen.
# Übergabe:
#   fZeilen       = Anzahl der Zeilen.
#   fSpalten      = Anzahl der Spalten.
#   fRahmenbreite = Breite der Rahmenlinien.
# Rückgabe:
#   Erzeugte Tabelle der Form "gtk.Table()".
def anlTabelle(fZeilen, fSpalten, fRahmenbreite) :
  tabelle = gtk.Table(rows=fZeilen, columns=fSpalten, homogeneous=False)
  tabelle.set_border_width(fRahmenbreite)
  tabelle.set_col_spacings(10)
  tabelle.set_row_spacings(10)
  return tabelle

# Textmarke (Label) in die Tabelle einbauen.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Textmarke.
#   fUnten  = Untere Position der einzubauenden Textmarke.
#   fLinks  = Linke Position der einzubauenden Textmarke.
#   fRechts = Rechte Position der einzubauenden Textmarke.
#   fText   = Elemente der Auswahl-Box, z.B. "Hurra es kalppt!".
#   fTipp   = Tipp (tooltip) der einzubauenden Textmarke.
# Rückgabe:
#   Erzeugte Textmarke der Form "gtk.Label()".
def addTextZurTab(fTab, fOben, fUnten, fLinks, fRechts, fText, fTipp=None) :
  marke = gtk.Label(fText)
  marke.set_alignment(xalign=0.0, yalign=1.0)
  if fTipp :
    marke.set_tooltip_text(fTipp)
  fTab.attach(marke, fLinks, fRechts, fOben, fUnten, xoptions=gtk.FILL, yoptions=0)
  return marke

# Eingabe-Feld (Entry) in die Tabelle einbauen.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Eingabe-Feld.
#   fUnten  = Untere Position der einzubauenden Eingabe-Feld.
#   fLinks  = Linke Position der einzubauenden Eingabe-Feld.
#   fRechts = Rechte Position der einzubauenden Eingabe-Feld.
#   fVorg   = Textvorgabe im einzubauenden Eingabe-Feld.
#   fEvent  = Reaktion auf Ereignis, wenn der Feld-Inhalt sich ändert.
#   fTipp   = Tipp (tooltip) der einzubauenden Eingabe-Feld.
# Rückgabe:
#   Erzeugtes Eingabe-Feld der Form "gtk.Entry()".
def addEingZurTab(fTab, fOben, fUnten, fLinks, fRechts, fVorg, fEvent, fTipp=None) :
  eingFeld = gtk.Entry()	# (max=80)
  eingFeld.set_text(fVorg)
  if fTipp :
    eingFeld.set_tooltip_text(fTipp)
  fTab.attach(eingFeld, fLinks, fRechts, fOben, fUnten, xoptions=gtk.FILL, yoptions=0)
  eingFeld.connect("key-release-event", fEvent)
  return eingFeld

# Auswahl-Box (ComboBox) in die Tabelle einbauen.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Auswahl-Box.
#   fUnten  = Untere Position der einzubauenden Auswahl-Box.
#   fLinks  = Linke Position der einzubauenden Auswahl-Box.
#   fRechts = Rechte Position der einzubauenden Auswahl-Box.
#   fElem   = Einzubauenden Elemente, z.B. ["Elem1", "Elem2", "Elem3"].
#   fEvent  = Reaktion auf Ereignis, wenn die Auswahl ändert.
#   fTipp   = Tipp (tooltip) der einzubauenden Auswahl-Box.
# Rückgabe:
#   Erzeugte Auswahl-Box der Form "gtk.combo_box_new_text()".
def addAuswahlZurTab(fTab, fOben, fUnten, fLinks, fRechts, fElem, fEvent, fTipp=None) :
  auswBox = gtk.combo_box_new_text()
  for auswElement in fElem :
    auswBox.append_text(auswElement)
  if fTipp :
    auswBox.set_tooltip_text(fTipp)
  fTab.attach(auswBox, fLinks, fRechts, fOben, fUnten, xoptions=gtk.EXPAND|gtk.FILL, yoptions=0)
  auswBox.connect("changed", fEvent)
  return auswBox

# Schiebe-Regler (HScale) in die Tabelle einbauen.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Regler.
#   fUnten  = Untere Position der einzubauenden Regler.
#   fLinks  = Linke Position der einzubauenden Regler.
#   fRechts = Rechte Position der einzubauenden Regler.
#   fAusr   = Ausrichtungs-Element der Form "gtk.Adjustment()".
#   fEvent  = Reaktion auf Ereignis, wenn die Auswahl ändert.
#   fTipp   = Tipp (tooltip) der einzubauenden Regler.
# Rückgabe:
#   Erzeugten Schiebe-Regler der Form "gtk.HScale()".
def addSchieberZurTab(fTab, fOben, fUnten, fLinks, fRechts, fAusr, fEvent, fTipp=None, fNach=0) :
  scale = gtk.HScale(fAusr)
  #scale.set_size_request(150, -1)
  scale.set_digits(fNach)
  scale.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
  if fTipp :
    scale.set_tooltip_text(fTipp)
  fTab.attach(scale, fLinks, fRechts, fOben, fUnten, xoptions=gtk.EXPAND|gtk.FILL, yoptions=0)
  fAusr.connect("value_changed", fEvent)
  return scale

# Schiebe- (HScale) sowie Werte-Regler (SpinButton) in die Tabelle einbauen.
# Die erzeugten Regler sind über dieselbe Ausrichtung miteinander verknüpft.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Regler.
#   fUnten  = Untere Position der einzubauenden Regler.
#   fLinks  = Linke Position der einzubauenden Regler.
#   fRechts = Rechte Position der einzubauenden Regler.
#   fAusr   = Ausrichtungs-Element der Form "gtk.Adjustment()".
#   fEvent  = Reaktion auf Ereignis, wenn die Auswahl ändert.
#   fTipp   = Tipp (tooltip) der einzubauenden Regler.
# Rückgabe:
#   Erzeugte Regler der Form "( gtk.HScale(), gtk.SpinButton() )".
def addReglerZurTab(fTab, fOben, fUnten, fLinks, fRechts, fAusr, fEvent, fTipp=None, fNach=0) :
  scale = gtk.HScale(fAusr)
  #scale.set_size_request(150, -1)
  scale.set_digits(fNach)
  scale.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
  if fTipp :
    scale.set_tooltip_text(fTipp)
  fTab.attach(scale, fLinks, fLinks+1, fOben, fUnten, xoptions=gtk.EXPAND|gtk.FILL, yoptions=0)
  #
  spinner = gtk.SpinButton(fAusr, climb_rate=0.5, digits=fNach)
  spinner.set_numeric(True)
  spinner.set_snap_to_ticks(True)
  spinner.set_max_length(5)
  spinner.set_width_chars(5)
  if fTipp :
    spinner.set_tooltip_text(fTipp)
  fTab.attach(spinner, fLinks+1, fRechts, fOben, fUnten, xoptions=0, yoptions=0)
  fAusr.connect("value_changed", fEvent)
  return (scale, spinner)

# Markier-Feld (CheckButton) in die Tabelle einbauen.
# Übergabe:
#   fTab    = Tabelle.
#   fOben   = Obere Position der einzubauenden Markier-Feldes.
#   fUnten  = Untere Position der einzubauenden Markier-Feldes.
#   fLinks  = Linke Position der einzubauenden Markier-Feldes.
#   fRechts = Rechte Position der einzubauenden Markier-Feldes.
#   fBezeich= Bezeichnung des Markier-Feldes.
#   fEvent  = Reaktion auf Ereignis, wenn die Markierung ändert.
#   fTipp   = Tipp (tooltip) der einzubauenden Markier-Feldes.
# Rückgabe:
#   Erzeugte Markier-Feldes der Form "gtk.CheckButton()".
def addMarkierZurTab(fTab, fOben, fUnten, fLinks, fRechts, fBezeich, fEvent, fTipp=None) :
  checkbox = gtk.CheckButton(fBezeich)
  #checkbox = gtk.CheckButton()
  checkbox.set_alignment(xalign=0.0, yalign=1.0)
  checkbox.set_border_width(0)
  if fTipp :
    checkbox.set_tooltip_text(fTipp)
  fTab.attach(checkbox, fLinks, fRechts, fOben, fUnten, xoptions=gtk.FILL, yoptions=0)
  checkbox.connect("toggled", fEvent)
  return checkbox

# Text mit Rahmen (Label with Frame) zu einer Box hinzufügen.
# Übergabe:
#   fBox    = Box, in welche der Textrahmen eingebaut werden soll.
#   fUeber  = Überschrift des Textrahmens.
#   fText   = Textinhalt des Textrahmens.
#   fTipp   = Tipp (tooltip) des Textrahmens.
# Rückgabe:
#   Erzeugten Textrahmen der Form "gtk.Label()".
def addTextMitRahmenZurBox(fBox, fUeber, fText, fTipp) :
  frmTextrahmen = gtk.Frame(" " + fUeber + " ")
  labTextrahmen = gtk.Label(fText)
  labTextrahmen.set_justify(gtk.JUSTIFY_LEFT) 	# .._LEFT | .._RIGHT | .._CENTER | .._FILL
  labTextrahmen.set_line_wrap(False)
  labTextrahmen.set_use_markup(True)
  if fTipp :
    labTextrahmen.set_tooltip_text(fTipp)
  frmTextrahmen.add(labTextrahmen)
  fBox.add(frmTextrahmen)
  return labTextrahmen

# Taste/Schaltfläche (Button) zu einer Box hinzufügen.
# Übergabe:
#   fBox    = Box, in welche die Taste eingebaut werden soll.
#   fBez    = Bezeichnung der Taste.
#   fEvent  = Reaktion auf Ereignis, wenn die Taste gedrückt wird.
#   fTipp   = Tipp (tooltip) der Taste.
# Rückgabe:
#   Erzeugte Taste der Form "gtk.Button()".
def addTasteZurBox(fBox, fBez, fEvent, fTipp=None) :
  taste = gtk.Button(fBez)
  if fTipp :
    taste.set_tooltip_text(fTipp)
  fBox.add(taste)
  taste.connect("clicked", fEvent)
  return taste

##########################################################################################
# Benutzerinterface (UI: User Interface) erstellen.
##########################################################################################
class benutzerInterface(gtk.Window) :

  ########################################################################################
  # Der Konstruktor, der das Fenster (window) erstellt
  def __init__(self, fImage, fDrawable) :
    self.Img = fImage	; self.Drw = fDrawable

    #
    ######################################################################################
    ### Titel, Größe, Position des Fensters
    gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
    self.winBreite = 500   ; self.winHoehe = -1 # Festlegung der Mindestgrößen
    titel = textDaten["fensterTitel"] %(textDaten["pluginNameImMenue"], pluginAuthor)
    self.set_title(titel)
    self.set_default_size(self.winBreite, self.winHoehe)
    self.set_resizable(True)
    self.set_position(gtk.WIN_POS_NONE)         # Auch möglich: gtk.WIN_POS_CENTER)
    #self.set_keep_above(True)                  # Immer oberstes Fenster (Probleme!)
    self.set_border_width(10)
    #
    ######################################################################################
    # Gesamt-Box, in welcher alle Elemente enthalten sind.
    gesamtBox = gtk.VBox(spacing=10, homogeneous=False)
    self.add(gesamtBox)
    ######################################################################################
    ### Hinweis-Box für Anleitungen und wichtige Hinweise an den Benutzer.
    if mitHilfeImFenster :
      hinweisBox = gtk.HBox(spacing=10, homogeneous=False)
      # ^-- ACHTUNG: Bei "homogeneous=True" sind die Hinweis-Boxen gleich breit!
      labAnleitungen = addTextMitRahmenZurBox(hinweisBox, textDaten["anleitungKopf"],
        textDaten["anleitungText"] + "\n\n" + textDaten["hinwAllePfeile"],
        textDaten["anleitungKopf"] + ".")
      self.labMeldung = addTextMitRahmenZurBox(hinweisBox, textDaten["hinweiseKopf"],
        " ", textDaten["hinweiseKopf"] + ".")
      #self.labMeldung.set_size_request(135, -1)
      self.labMeldung.set_line_wrap(True)       # Zeilenumbruch
      self.labMeldung.set_width_chars(20)       # Maximal 20 Zeichen
      gesamtBox.add(hinweisBox)
    ######################################################################################
    # Einstellungs-Box für grundlegende Einstellungen des Benutzers.
    einstellungsBox = gtk.VBox(spacing=10, homogeneous=False)
    dreiSpaltenTab = anlTabelle(4, 3, 10)       # Vier Zeilen, drei Spalten, Rahmendicke
    ###
    ### Kombinationsbox für die Festlegung des Pfeilstils
    addTextZurTab(dreiSpaltenTab, 0, 1, 0, 1, textDaten["bezPfeilstil"])
    auswahlElemente = []
    for aktuellerStil in pfeilDaten :
      auswahlElemente.append(pfeilDaten[aktuellerStil].Auswahltext)
    self.comPfeilstil = addAuswahlZurTab(dreiSpaltenTab, 0, 1, 1, 3,
      auswahlElemente, self.evtPfeilstil, textDaten["tipPfeilstil"])
    self.comPfeilstil.set_active(pfeilIndex)
    ###
    ### Schieberegler (slider) für die Dicke des Pfeilschaftes definieren
    addTextZurTab(dreiSpaltenTab, 1, 2, 0, 1, textDaten["bezDicke"])
    self.adjDicke = gtk.Adjustment(schaftDicke, schaftDickeMin, schaftDickeMax, 1)
    (self.sclDicke, self.spiDicke) = addReglerZurTab(dreiSpaltenTab, 1, 2, 1, 3,
      self.adjDicke, self.evtDicke, textDaten["tipDicke"], 0)    # Keine Nachkommastellen
    ###
    ### Schieberegler (slider) für die Länge der Pfeilspitze definieren
    addTextZurTab(dreiSpaltenTab, 2, 3, 0, 1, textDaten["bezLaenge"])
    self.adjLaenge = gtk.Adjustment(pfeilLaenge, pfeilLaengeMin, pfeilLaengeMax, 1)
    (self.sclLaenge, self.spiLaenge) = addReglerZurTab(dreiSpaltenTab, 2, 3, 1, 3,
      self.adjLaenge, self.evtLaenge, textDaten["tipLaenge"], 0) # Keine Nachkommastellen
    ###
    ### Schieberegler (slider) für die Breite der Pfeilspitze definieren
    addTextZurTab(dreiSpaltenTab, 3, 4, 0, 1, textDaten["bezBreite"])
    self.adjBreite = gtk.Adjustment(pfeilBreite, pfeilBreiteMin, pfeilBreiteMax, 1)
    (self.sclBreite, self.spiBreite) = addReglerZurTab(dreiSpaltenTab, 3, 4, 1, 3,
      self.adjBreite, self.evtBreite, textDaten["tipBreite"], 0) # Keine Nachkommastellen
    #
    einstellungsBox.add(dreiSpaltenTab)
    trennlinie = gtk.HSeparator()	; einstellungsBox.add(trennlinie)
    gesamtBox.add(einstellungsBox)
    ######################################################################################
    # Steuerungs-Box für weitere steuernden Eingaben des Benutzers.
    steuerungsBox = gtk.VBox(spacing=10, homogeneous=False)
    fuenfSpaltenTab = anlTabelle(2, 5, 10)      # Zwei Zeilen, fünf Spalten, Rahmendicke
    ###
    ### Vertikale Kombinationsbox für die Festlegung der Pfeilposition (Label & Wert)
    addTextZurTab(fuenfSpaltenTab, 0, 1, 0, 1, textDaten["bezPfeilpos"])
    self.comPfeilpos = addAuswahlZurTab(fuenfSpaltenTab, 1, 2, 0, 1,
      textDaten["txtPfeilpos"], self.evtPfeilpos, textDaten["tipPfeilpos"])
    self.comPfeilpos.set_active(pfeilPosition)
    ###
    ### Vertikale Kombinationsbox für Festlegung der Schaftfüllung (Label & Wert)
    addTextZurTab(fuenfSpaltenTab, 0, 1, 1, 2, textDaten["bezSchaftfuell"])
    self.comSchaftfuell = addAuswahlZurTab(fuenfSpaltenTab, 1, 2, 1, 2,
      textDaten["txtSchaftfuell"], self.evtSchaftfuell, textDaten["tipSchaftfuell"])
    self.comSchaftfuell.set_active(schaftFuellInd)
    ###
    ### Vertikale Kombinationsbox für Festlegung der Pfeilspitzfüllung (Label & Wert)
    addTextZurTab(fuenfSpaltenTab, 0, 1, 2, 3, textDaten["bezPfeilfuell"])
    self.comPfeilfuell = addAuswahlZurTab(fuenfSpaltenTab, 1, 2, 2, 3,
      textDaten["txtPfeilfuell"], self.evtPfeilfuell, textDaten["tipPfeilfuell"])
    self.comPfeilfuell.set_active(pfeilFuellInd)
    ###
    ### Vertikale Kombinationsbox für Festlegung vom Durchmesser des Schaftkreises
    labSchaftkreis = addTextZurTab(fuenfSpaltenTab, 0, 1, 3, 5, textDaten["bezSchaftkreis"])
    breiteRegler = self.winBreite / 4 * 2
    labSchaftkreis.set_size_request(breiteRegler, -1)
    self.adjSchaftkreis = gtk.Adjustment(schaftKreis, schaftKreisMin, schaftKreisMax, 1)
    (self.sclSchaftkreis, self.spiSchaftkreis) = addReglerZurTab(fuenfSpaltenTab, 1, 2, 3, 5,
      self.adjSchaftkreis, self.evtSchaftkreis, textDaten["tipSchaftkreis"], 0)
    self.sclSchaftkreis.set_sensitive(False)        # Nicht aktiviert beim Fenster-Start
    self.spiSchaftkreis.set_sensitive(False)        # Nicht aktiviert beim Fenster-Start
    #
    steuerungsBox.add(fuenfSpaltenTab)
    if mitHilfeImFenster :
      trennlinie = gtk.HSeparator()	; steuerungsBox.add(trennlinie)
    gesamtBox.add(steuerungsBox)
    ######################################################################################
    ### Hinweis-Box für wichtige Hinweise an den Benutzer.
    if not mitHilfeImFenster :
      hinweisBox = gtk.HBox(spacing=10, homogeneous=False)
      # ^-- ACHTUNG: Bei "homogeneous=True" sind die Hinweis-Boxen gleich breit!
      hinwText = "1\n2\n3\n4\n5\n"                  # Hinweis mit 5 Zeilen erzeugen
      self.labMeldung = addTextMitRahmenZurBox(hinweisBox, textDaten["hinweiseKopf"],
        hinwText, textDaten["hinweiseKopf"] + ".")
      hinwHoehe = self.labMeldung.size_request()[1] # Höhe vom Beispiels-Hinweis
      self.labMeldung.set_size_request(-1, hinwHoehe)
      self.labMeldung.set_line_wrap(True)           # Zeilenumbruch
      self.labMeldung.set_width_chars(100)          # Maximale Zeichenanzahl
      gesamtBox.add(hinweisBox)
    ######################################################################################
    # Tasten/Knöpfe-Box, in welcher alle Tasten enthalten sind.
    tastenBox = gtk.HBox(spacing=10, homogeneous=True)
    # ^-- ACHTUNG: Durch "homogeneous=True" sind die Tasten gleich breit!
    ######################################################################################
    ### Taste "Zeichnen"
    self.butZeichnen = addTasteZurBox(tastenBox, textDaten["bezButZeichnen"],
      self.evtZeichnen, textDaten["tipButZeichnen"])
    ###
    ### Taste "Vorgaben"
    self.butVorgaben = addTasteZurBox(tastenBox, textDaten["bezButVorgaben"],
      self.evtVorgaben, textDaten["tipButVorgaben"])
    ###
    ### Taste "Hilfe"
    if not mitHilfeImFenster :
      self.butHilfe = addTasteZurBox(tastenBox, textDaten["bezButHilfe"],
        self.evtHilfe, textDaten["tipButHilfe"])
    ###
    ### Taste "Abbrechen"
    self.butAbbrechen = addTasteZurBox(tastenBox, textDaten["bezButAbbrechen"],
      self.evtAbbrechen, textDaten["tipButAbbrechen"])
    ###
    ###  Taste "Beenden"
    self.butBeenden = addTasteZurBox(tastenBox, textDaten["bezButBeenden"],
      self.evtBeenden, textDaten["tipButBeenden"])
    #
    gesamtBox.add(tastenBox)
    #
    ######################################################################################
    # Beim Klick auf den X-Button (x) wird wie bei Taste "Abbrechen" reagiert
    self.connect("destroy", self.evtAbbrechen)
    #
    ######################################################################################
    # Voraussetzung für die Bearbeitung sicherstellen
    ### UNDO deaktivieren
    pdb.gimp_image_undo_freeze(self.Img)
    ### Pfeil-Gruppenebene sicherstellen
    self.pfeilEbenengruppeAnlegen()
    ### Pfeil-Ebene für die aktuelle Sitzung sicherstellen
    self.pfeilEbeneAnlegen()
    ### Pfeil-Pfad für die aktuelle Sitzung sicherstellen
    self.pfeilPfadAnlegen()

  ########################################################################################
  # Methode zum Anzeigen des Fensters und Verweilen in der Event-Warteschleife.
  def anzeigen(self) :
    self.ausgabeHinweise(textDaten["hinwPluginStart"])
    self.show_all()	# Im Konstruktor aufgebautes Fenster jetzt anzeigen
    gtk.main()		# Event-Warteschleife, nach dem Anzeigen des Hauptfensters

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Änderung des Pfeilstils
  def evtPfeilstil(self, widget):
    global pfeilIndex, aktuellerHinweis, pfeilPosition
    pfeilIndex = self.comPfeilstil.get_active()
    #
    # Je nach Pfeilstil Schaltflächen aktivieren / deaktivieren
    pfeilGewaehlt = pfeilNamen[pfeilIndex]
    self.comPfeilpos.set_sensitive(pfeilDaten[pfeilGewaehlt].PfeilPosWechsel)
    self.sclSchaftkreis.set_sensitive(pfeilDaten[pfeilGewaehlt].MitSchaftkreis)
    self.spiSchaftkreis.set_sensitive(pfeilDaten[pfeilGewaehlt].MitSchaftkreis)
    if not pfeilDaten[pfeilGewaehlt].PfeilPosWechsel :
      pfeilPosition = 0		; self.comPfeilpos.set_active(pfeilPosition)
    #
    # Spezifische Hinweise ausgeben
    hinweise = textDaten["hinwPfeilGewaehlt"]
    if pfeilDaten[pfeilGewaehlt].MitSchaftkreis :
      hinweise += "\n\n" + textDaten["hinwPfeileMitKreis"]
    if pfeilDaten[pfeilGewaehlt].Auswahltext == textDaten["Bemassungspfeil"] :
      hinweise += "\n\n" + textDaten["hinwBemassPfeil"]
    elif pfeilDaten[pfeilGewaehlt].Auswahltext == textDaten["GebogenerPfeil"] :
      hinweise += "\n\n" + textDaten["hinwGebogPfeil"]
    else :
      hinweise += "\n\n" + textDaten["hinwAusgabePfeil"]
    self.ausgabeHinweise(hinweise)

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Wertänderung der Pfeilschaftdicke
  def evtDicke(self, widget) :
    global schaftDicke
    schaftDicke = self.adjDicke.value

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Wertänderung der Pfeillänge
  def evtLaenge(self, widget) :
    global pfeilLaenge
    pfeilLaenge = self.adjLaenge.value

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Wertänderung der Pfeilbreite
  def evtBreite(self, widget) :
    global pfeilBreite
    pfeilBreite = self.adjBreite.value

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Änderung der Pfeilposition
  def evtPfeilpos(self, widget):
    global pfeilPosition
    pfeilPosition = self.comPfeilpos.get_active()

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Änderung der Schaftfüllung
  def evtSchaftfuell(self, widget):
    global schaftFuellInd, schaftFuellung
    schaftFuellInd = self.comSchaftfuell.get_active()
    schaftFuellung = textDaten["txtSchaftfuell"][schaftFuellInd]
    #
    # Spezifische Hinweise ausgeben
    if schaftFuellung == textDaten["txtSchaftfuell"][0] :       # Vordergrund
      self.ausgabeHinweise(textDaten["hinwFuellVorderg"])
    elif schaftFuellung == textDaten["txtSchaftfuell"][1] :     # Verlauf
      self.ausgabeHinweise(textDaten["hinwFuellVerlauf"])
    else :							# Muster
      self.ausgabeHinweise(textDaten["hinwFuellMuster"])

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Änderung der Pfeilfüllung
  def evtPfeilfuell(self, widget):
    global pfeilFuellInd, pfeilFuellung
    pfeilFuellInd = self.comPfeilfuell.get_active()
    pfeilFuellung = textDaten["txtPfeilfuell"][pfeilFuellInd]
    #
    # Spezifische Hinweise ausgeben
    if pfeilFuellung == textDaten["txtPfeilfuell"][0] :         # Vordergrund
      self.ausgabeHinweise(textDaten["hinwFuellVorderg"])
    elif pfeilFuellung == textDaten["txtPfeilfuell"][1] :       # Verlauf
      self.ausgabeHinweise(textDaten["hinwFuellVerlauf"])
    else :							# Muster
      self.ausgabeHinweise(textDaten["hinwFuellMuster"])

  ########################################################################################
  # Ereignis (event) als Reaktion auf die Wertänderung des Schaftkreis-Durchmessers
  def evtSchaftkreis(self, widget) :
    global schaftKreis
    schaftKreis = self.adjSchaftkreis.value

  ########################################################################################
  # Ereignis (event) als Reaktion des Tastendrucks auf "Vorgaben"
  def evtVorgaben(self, widget, data=None) :
    self.butVorgaben.set_sensitive(False)
    if self.wkzVorausKontrolle() :
      self.werkzeugVorgaben()
    self.butVorgaben.set_sensitive(True)

  ########################################################################################
  # Ereignis (event) als Reaktion des Tastendrucks auf "Zeichnen"
  def evtZeichnen(self, widget, data=None) :
    #self.butZeichnen.set_sensitive(False)
    self.pfeilZeichnen()
    #self.butZeichnen.set_sensitive(True)

  ########################################################################################
  # Ereignis (event) als Reaktion des Tastendrucks auf "Beenden"
  def evtBeenden(self, widget, data=None) :
    self.butBeenden.set_sensitive(False)
    self.pluginBeenden()

  ########################################################################################
  # Ereignis (event) als Reaktion des Tastendrucks auf "Abbrechen"
  def evtAbbrechen(self, widget, data=None) :
    self.butAbbrechen.set_sensitive(False)
    self.pluginAbbrechen()

  ########################################################################################
  # Ereignis (event) als Reaktion des Tastendrucks auf "Hilfe"
  def evtHilfe(self, widget, data=None) :
    #ausgabeMeldung(textDaten["anleitungText"] + "\n\n" + textDaten["hinwAllePfeile"],
    #  gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
    global hilfeFenster

    ######################################################################################
    ### Dialog-Fenster erzeugen
    hilfeBreite = 400   ; hilfeHoehe = -1		# Festlegung der Mindestgrößen
    hilfeTitel = textDaten["hilfeFensterTitel"] %(textDaten["pluginNameImMenue"])
    hilfeFenster = gtk.Dialog(hilfeTitel, None,
      gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
    #hilfeFenster.set_default_size(hilfeBreite, hilfeHoehe)
    hilfeFenster.set_position(gtk.WIN_POS_CENTER)
    hilfeFenster.set_border_width(10)
    hilfeFensterBox = hilfeFenster.get_content_area()
    #
    ######################################################################################
    # Gesamt-Box, in welcher alle Elemente enthalten sind.
    hilfeGesamtBox = gtk.VBox(spacing=10, homogeneous=False)
    hilfeFensterBox.add(hilfeGesamtBox)
    ######################################################################################
    ### Hinweis-Box für Anleitungen und wichtige Hinweise an den Benutzer.
    hilfeHinweiseBox = gtk.HBox(spacing=10, homogeneous=False)
    hilfeLabAnleitungen = addTextMitRahmenZurBox(hilfeHinweiseBox,
      textDaten["anleitungKopf"],
      textDaten["anleitungText"] + "\n\n" + textDaten["hinwAllePfeile"],
      textDaten["anleitungKopf"] + ".")
    hilfeGesamtBox.add(hilfeHinweiseBox)
    ######################################################################################
    # Tasten/Knöpfe-Box, in welcher alle Tasten enthalten sind.
    hilfeTastenBox = gtk.HBox(spacing=10, homogeneous=False)
    ###
    ### Taste "Beenden"
    hilfeButOK = addTasteZurBox(hilfeTastenBox, textDaten["hilfeBezButOK"],
      self.hilfeEvtOK, textDaten["hilfeTipButOK"])
    ###
    hilfeGesamtBox.add(hilfeTastenBox)
    #
    ######################################################################################
    # Fenster-Box anzeigen
    hilfeFenster.show_all()
    #
    ######################################################################################
    # Beim Klick auf den X-Button (x) wird wie bei der Taste "Beenden" reagiert.
    hilfeFenster.connect("destroy", self.hilfeEvtOK)
    rueckgabe = hilfeFenster.run()
    hilfeFenster.destroy()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion des Tastendrucks auf "OK".
  def hilfeEvtOK(self, widget, data=None) :
    hilfeFenster.destroy()

  ########################################################################################
  # Pfeil zeichnen
  def pfeilZeichnen(self) :
    global pfadX1, pfadY1, pfadX2, pfadY2   # Anfangs- und Endkoordinaten des Pfades
    global bemassungLaenge                  # Länge des Bemaßungspfeils
    ######################################################################################
    ### Voraussetzungen kontrollieren
    if not self.bildExistiert() :
      ausgabeMeldung(textDaten["meldBildZugriff"], gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
      self.pluginAbbrechen()
      return
    ####### Kontrolle, ob die Pfeilebene noch existiert
    if not self.pfeilEbeneExistiert() :
      ausgabeMeldung(textDaten["meldEbenenZugriff"], gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
      self.pluginAbbrechen()
      return
    ####### Kontrolle, ob der Pfeilpfad noch existiert
    if not self.pfeilPfadExistiert() :
      ausgabeMeldung(textDaten["meldPfadZugriff"], gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
      self.pluginAbbrechen()
      return
    ###### Kontrolle, ob die Anzahl der im Pfad befindlichen Komponenten stimmt
    pfadKomponenten = pdb.gimp_vectors_get_strokes(pfadPfeil)
    if pfadKomponenten[0] == 0 :
      ausgabeMeldung(textDaten["meldAnkerKeinen"], gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
      return
    if pfadKomponenten[0] > 1:
      meldung = textDaten["meldKompZuviel"] %(pfadKomponenten[0])
      ausgabeMeldung(meldung, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
    ###### Kontrolle, ob der Benutzer einen Pfad mit zwei Ankerpunkten definiert hat
    #pfadPunkte = pdb.gimp_vectors_stroke_get_points(pfadPfeil, 1)[2]
    pfadPunkte = pfadPfeil.strokes[0].points[0]
    anzahlPunkte = len(pfadPunkte)
    if anzahlPunkte != 12 :
      if anzahlPunkte < 12:
        ausgabeMeldung(textDaten["meldAnkerEinen"], gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
        return
      else :
        ausgabeMeldung(textDaten["meldAnkerZuviel"], gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
    ######################################################################################
    ### Koordinaten der Ankerpunkte ermitteln und verschiedene Tests durchführen
    pfadX1 = pfadPunkte[2]	; pfadY1 = pfadPunkte[3]
    pfadX2 = pfadPunkte[8]	; pfadY2 = pfadPunkte[9]
    ###### Testen, ob die Punkte außerhalb der Zeichenfläche liegen
    if min(pfadX1, pfadY1, pfadX2, pfadY2) < 0				\
    or max(pfadX1, pfadX2) > imgBreite or max(pfadY1, pfadY2) > imgHoehe :
      ausgabeMeldung(textDaten["meldPktAusserhalb"], gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
      return
    # Ermittelte Koordinaten je nach gewählter Position der Pfeilspitze definieren
    if pfeilPosition != 0 :		# Pfeilspitze soll am Pfadanfang gezeichnet werden
      x1temp, y1temp, x2temp, y2temp = pfadX2, pfadY2, pfadX1, pfadY1
      pfadX1, pfadY1, pfadX2, pfadY2 = x1temp, y1temp, x2temp, y2temp
    ######################################################################################
    ### Zeichnen des Pfeils
    bemassungLaenge = None                              # Ausgabewert löschen
    pfeilGewaehlt = pfeilNamen[pfeilIndex]
    eval(pfeilDaten[pfeilGewaehlt].ZeichnenFunktion)    # Zeichenfunktion aufrufen
    return

  ########################################################################################
  # Plugin-Ausführung beenden. Zuvor den gewählten Pfeil zeichnen.
  def pluginBeenden(self) :
    ### Pfeil zeichnen
    self.pfeilZeichnen()
    #
    # Ebenennamen ergänzen, wenn als letztes der Bemaßungspfeil gezeichnet wurde
    if bemassungLaenge != None :
      neuerLayName = textDaten["layPfeilName"] + " %d (%d, %.1f)" \
        %(layPfeilNummer, bemassungLaenge, bemassungWinkel)
      pdb.gimp_item_set_name(layPfeil, neuerLayName)
    #
    ### Pfad des aktuellen Pfeils auf unsichtbar setzen
    if self.pfeilPfadExistiert() :
      pdb.gimp_item_set_visible(pfadPfeil, False)
    #
    ### Falls vorhanden, soll die Ebene beim Start wieder die aktuelle Ebene sein
    if pdb.gimp_item_is_valid(layAktivBeiStart) :
      pdb.gimp_image_set_active_layer(self.Img, layAktivBeiStart)
    #
    ### Display aktualisieren
    pdb.gimp_displays_flush()
    ### UNDO wieder auffrieren
    pdb.gimp_image_undo_thaw(self.Img)
    #
    # Benutzerinteface schließen und Plugin beenden
    gtk.main_quit()         # Ende der Event-Warteschleife, die mit "gtk.main()" anfing
    pluginEnde()

  ########################################################################################
  # Plugin-Ausführung abbrechen. Zuvor alle Änderungen der Sitzung rückgängig machen!
  def pluginAbbrechen(self) :
    ### Kontrolle, ob das Bild noch ansprechbar ist
    if not self.bildExistiert() :
      # Benutzerinteface schließen und Plugin beenden
      gtk.main_quit()       # Ende der Event-Warteschleife, die mit "gtk.main()" anfing
      pluginEnde()
    #
    ### In der Sitzung angelegte Pfeil-Ebene löschen
    self.pfeilEbeneLoeschen()
    ### In der Sitzung angelegte Ebenengruppe löschen
    self.pfeilEbenengruppeLoeschen()
    ### In der Sitzung angelegter Pfad löschen
    self.pfeilPfadLoeschen()
    #
    ### Falls vorhanden, soll die Ebene beim Start wieder die aktuelle Ebene sein
    if pdb.gimp_item_is_valid(layAktivBeiStart) :
      pdb.gimp_image_set_active_layer(self.Img, layAktivBeiStart)
    #
    ### Display aktualisieren
    pdb.gimp_displays_flush()
    ### UNDO wieder auffrieren
    pdb.gimp_image_undo_thaw(self.Img)
    #
    # Benutzerinteface schließen und Plugin beenden
    gtk.main_quit()         # Ende der Event-Warteschleife, die mit "gtk.main()" anfing
    pluginEnde()

  ########################################################################################
  # Ausgabe wichtiger Informationen im Hinweis-Label
  def ausgabeHinweise(self, fText) :
    self.labMeldung.set_text(fText)

  ########################################################################################
  # Existiert die Gruppenebene für die Pfeile noch nicht, dann anlegen.
  def pfeilEbenengruppeAnlegen(self) :
    global layGruppeAngelegt, layGruppe
    #
    layGruppeAngelegt = False
    layGruppe = pdb.gimp_image_get_layer_by_name(self.Img, textDaten["layGruppenName"])
    if layGruppe == None :
      layGruppe = pdb.gimp_layer_group_new(self.Img)
      pdb.gimp_layer_set_name(layGruppe, textDaten["layGruppenName"])
      pdb.gimp_image_insert_layer(self.Img, layGruppe, None, 0)
      layGruppeAngelegt = True
    return None

  ########################################################################################
  # Wurde die Pfeil-Gruppenebene in der Sitzung angelegt und existiert, dann löschen.
  def pfeilEbenengruppeLoeschen(self) :
    if layGruppeAngelegt and pdb.gimp_item_is_valid(layGruppe) :
      ### In der Sitzung angelegte Gruppenebene löschen
      pdb.gimp_image_remove_layer(self.Img, layGruppe)
    return None

  ########################################################################################
  # Pfeil-Ebene für die aktuelle Sitzung anlegen.
  def pfeilEbeneAnlegen(self) :
    global layPfeilNummer, layPfeil
    # Pfeil-Gruppenebene muss existieren
    if pdb.gimp_item_is_valid(layGruppe) :
      # Existierende Pfeil-Ebenen besorgen und Nummer der neuen Pfeil-Ebene ermitteln
      layPfeileInGruppe = pdb.gimp_item_get_children(layGruppe)
      if layPfeileInGruppe[0] == 0 :
        layPfeilNummer = 1
      else :
        layPfeilNummer = layPfeileInGruppe[0] + 1
      # Pfeil-Ebene für den aktuellen Pfeil sicherstellen
      layPfeil = pdb.gimp_layer_new(self.Img, imgBreite, imgHoehe, RGB_IMAGE, \
        textDaten["layPfeilName"] + " " + str(layPfeilNummer), 100, LAYER_MODE_NORMAL)
      pdb.gimp_layer_add_alpha(layPfeil)
      pdb.gimp_drawable_fill(layPfeil, FILL_TRANSPARENT)
      pdb.gimp_image_insert_layer(self.Img, layPfeil, layGruppe, -1)
    return None

  ########################################################################################
  # Existiert die Pfeil-Ebene der aktuellen Sitzung, dann löschen.
  def pfeilEbeneLoeschen(self) :
    # Pfeil-Ebene muss existieren
    if self.pfeilEbeneExistiert() :
      ### In der Sitzung angelegte Pfeil-Ebene löschen
      pdb.gimp_drawable_edit_clear(layPfeil)
      pdb.gimp_image_remove_layer(self.Img, layPfeil)
    return None

  ########################################################################################
  # Pfeil-Pfad für die aktuelle Sitzung sicherstellen.
  def pfeilPfadAnlegen(self) :
    global pfadPfeil
    pfadPfeil = pdb.gimp_image_get_vectors_by_name(self.Img, textDaten["pfadName"])
    if pfadPfeil == None :
      pfadPfeil = pdb.gimp_vectors_new(self.Img, textDaten["pfadName"])
      pdb.gimp_image_insert_vectors(self.Img, pfadPfeil, None, -1)
      ### Segment zum Pfad in der Mitte des Bildes hinzufügen
      anfPunkte = [imgBreite / 3,     imgHoehe / 2] * 3
      endPunkte = [imgBreite / 3 * 2, imgHoehe / 2] * 3
      allePunkte = anfPunkte + endPunkte
      strokeID = pdb.gimp_vectors_stroke_new_from_points(pfadPfeil,
                   VECTORS_STROKE_TYPE_BEZIER, len(allePunkte), allePunkte, False)
    pdb.gimp_item_set_visible(pfadPfeil, True)
    return None

  ########################################################################################
  # Existiert der Pfeil-Pfad der aktuellen Sitzung, dann löschen.
  def pfeilPfadLoeschen(self) :
    # Pfeil-Pfad muss existieren und in der aktuellen Sitzung angelegt worden sein
    if self.pfeilPfadExistiert() :
      if layGruppeAngelegt :
        pdb.gimp_image_remove_vectors(self.Img, pfadPfeil)
      else :
        pdb.gimp_item_set_visible(pfadPfeil, False)
    return None

  ########################################################################################
  # Existiert das Bild der aktuellen Sitzung.
  def bildExistiert(self) :
    return pdb.gimp_image_is_valid(self.Img)

  ########################################################################################
  # Existiert die Pfeil-Ebene der aktuellen Sitzung.
  def pfeilEbeneExistiert(self) :
    return pdb.gimp_item_is_valid(layPfeil)

  ########################################################################################
  # Existiert der Pfeil-Pfad der aktuellen Sitzung.
  def pfeilPfadExistiert(self) :
    return pdb.gimp_item_is_valid(pfadPfeil)

  ########################################################################################
  # Euclidean distance for multicomponent colors
  def farbDifferenz(self, fFarbe1, fFarbe2) :
    ret = 0
    for i in range(0, len(fFarbe1)) :
      ret = ret + (fFarbe2[i] - fFarbe1[i]) * (fFarbe2[i] - fFarbe1[i])
    return math.sqrt(ret)

  ########################################################################################
  # Kontrolle, ob die Hintergrundfarbe des geladenen Bildes an der Koordinate fX/fY
  # gleich oder ähnlich der eingestellten Vordergrundfarbe/Hintergrundfarbe ist.
  # Der Pfeil oder Teile davon sind dann unter Umständen nicht/schlecht sichtbar!
  def testeSichbarkeit(self, fX, fY, fFarbe="Vordergrund", fMeldName="meldPfeilSichtbar") :
    if fFarbe == "Vordergrund" :
      aktVerglFarbe = pdb.gimp_context_get_foreground()
    else :
      aktVerglFarbe = pdb.gimp_context_get_background()
    imgHGFarbe = \
      pdb.gimp_image_pick_color(self.Img, self.Img.layers[-1], fX, fY, False, True, 10)
    unterschied = self.farbDifferenz(aktVerglFarbe, imgHGFarbe)
    if unterschied <= 80 :
      aR, aG, aB, aA = aktVerglFarbe	; iR, iG, iB, iA = imgHGFarbe
      meldung = textDaten[fMeldName] \
        %(round(fX), round(fY), iR, iG, iB, iA, aR, aG, aB, aA, unterschied)
      ausgabeMeldung(meldung, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Lanzenpfeil (Assegai-Speer aus Afrika)".
  def zeichneLanzenpfeil(self) :
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sL = gL - pfeilLaenge                   # Schaftlänge des Pfeils
    sDH = schaftDicke / 2                   # Hälftige Schaftdicke
    sX1 = round(pfadX1 + sDH * cosAlpha)    # Koordinaten Schaftanfang wegen Rundung
    sY1 = round(pfadY1 + sDH * sinAlpha)
    sX2 = round(pfadX1 + sL * cosAlpha)     # Koordinaten Schaftende wegen Spitze
    sY2 = round(pfadY1 + sL * sinAlpha)
    #
    # Wichtiges im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfuehrung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :    # Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft mit Rundung zeichnen
      eX = sX1 - sDH	; eY = sY1 - sDH
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftDicke, schaftDicke)
      schaftKoord = self.berechneKonusSchaft(sX1, sY1, schaftDicke, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_ADD, len(schaftKoord), schaftKoord)
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2])
    elif sL - sDH <= 0 :	# Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(sX1, sY1, schaftDicke, sX2, sY2, schaftDicke)
      pfeilKoord = self.berechneLanzenSpitze(sX2, sY2, pfadX2, pfadY2)
      if sL - sDH < 0 :
        auswahl = schaftKoord[4:6] + pfeilKoord + schaftKoord[2:4]
      else :
        auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(0, [], pfeilBreite, [sX2, sY2, pfadX2, pfadY2], auswahl)
    else :
      # Pfeilschaft mit Rundung und Pfeilspitze zeichnen
      eX = sX1 - sDH	; eY = sY1 - sDH
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftDicke, schaftDicke)
      schaftKoord = self.berechneKonusSchaft(sX1, sY1, schaftDicke, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_ADD, len(schaftKoord), schaftKoord)
      #
      pfeilKoord = self.berechneLanzenSpitze(sX2, sY2, pfadX2, pfadY2)
      auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2],
        pfeilBreite, [sX2, sY2, pfadX2, pfadY2], auswahl)
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = pfadX1 + (sX2 - pfadX1) / 2	; testY = pfadY1 + (sY2 - pfadY1) / 2
    self.testeSichbarkeit(testX, testY)
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Hinweispfeil".
  def zeichneHinweispfeil(self) :
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sL = gL - pfeilLaenge               # Schaftlänge des Pfeils
    sX2 = round(pfadX1 + sL * cosAlpha) # Koordinaten des zweiten Schaftpunktes
    sY2 = round(pfadY1 + sL * sinAlpha)
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfuehrung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :    # Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft zeichnen
      schaftKoord = self.berechneKonusSchaft(pfadX1, pfadY1, schaftDicke, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2])
    elif sL <= 0 :          # Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die Pfeilspitze zeichnen
      pfeilKoord = self.berechneDreiecksSpitze(sX2, sY2, pfadX2, pfadY2)
      self.zeichnePfeil(0, [], pfeilBreite, [sX2, sY2, pfadX2, pfadY2], pfeilKoord)
    else :
      # Pfeilschaft und Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(pfadX1, pfadY1, schaftDicke, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      #
      pfeilKoord = self.berechneDreiecksSpitze(sX2, sY2, pfadX2, pfadY2)
      auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2],
        pfeilBreite, [sX2, sY2, pfadX2, pfadY2], auswahl)
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = pfadX1 + (sX2 - pfadX1) / 2	; testY = pfadY1 + (sY2 - pfadY1) / 2
    self.testeSichbarkeit(testX, testY)
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Bemaßungspfeil".
  def zeichneBemassungspfeil(self) :
    global bemassungLaenge, bemassungWinkel       # Länge und Winkel des Bemaßungspfeils
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sL = gL - 2 * pfeilLaenge                     # Schaftlänge des Pfeils
    sDH = schaftDicke / 2                         # Hälftige Schaftdicke
    sX1 = round(pfadX1 + pfeilLaenge * cosAlpha)  # Koordinaten Schaftanfang wegen Spitze
    sY1 = round(pfadY1 + pfeilLaenge * sinAlpha)
    sX2 = round(sX1 + sL * cosAlpha)              # Koordinaten Schaftende wegen Spitze
    sY2 = round(sY1 + sL * sinAlpha)
    #
    # Werte vom Bemaßungspfeil berechnen
    bemassungLaenge = round(gL)
    radianten = math.asin(sinAlpha)                   # Winkel im Bogenmaß (Radiant)
    bemassungWinkel = round(math.degrees(radianten))  # Winkel in Grad
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfBemassung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL), bemassungWinkel)
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :	# Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft zeichnen
      schaftKoord = self.berechneTailliertSchaft(sX1, sY1, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      self.zeichnePfeil(schaftDicke, [sX1, sY1, sX2, sY2])
    elif sL <= 0 :	# Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die beiden Pfeilspitzen zeichnen
      pfeilKoord1 = self.berechneDreiecksSpitze(sX1, sY1, pfadX1, pfadY1)
      pfeilKoord2 = self.berechneDreiecksSpitze(sX2, sY2, pfadX2, pfadY2)
      self.zeichnePfeil(0, [],
        pfeilBreite, [sX1, sY1, pfadX1, pfadY1], pfeilKoord1, [sX2, sY2, pfadX2, pfadY2], pfeilKoord2)
    else :
      # Pfeilschaft und Pfeilspitze zeichnen
      schaftKoord = self.berechneTailliertSchaft(sX1, sY1, sX2, sY2, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      #
      pfeilKoord1 = self.berechneDreiecksSpitze(sX1, sY1, pfadX1, pfadY1)
      pfeilKoord2 = self.berechneDreiecksSpitze(sX2, sY2, pfadX2, pfadY2)
      self.zeichnePfeil(schaftDicke, [sX1, sY1, sX2, sY2],
        pfeilBreite, [sX1, sY1, pfadX1, pfadY1], pfeilKoord1, [sX2, sY2, pfadX2, pfadY2], pfeilKoord2)
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = sX1 + (sX2 - sX1) / 2	; testY = sY1 + (sY2 - sY1) / 2
    self.testeSichbarkeit(testX, testY)
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Richtungspfeil".
  def zeichneRichtungspfeil(self) :
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sVS = schaftDicke / 3                   # Schaft-Verkürzung am Schwalbenschwanz
    sVP = pfeilLaenge / 3                   # Schaft-Verlängerung an Pfeilspitze
    sDP = schaftDicke / 3                   # Schaftdicke an der Pfeilspitze
    if sDP <= 2 : sDP = 6
    sL  = gL - pfeilLaenge                  # Schaftlänge des Pfeils
    sLV = sL + sVP                          # Schaftlänge des Pfeils mit Verlängerung
    sX1 = round(pfadX1 + sVS * cosAlpha)    # Koordinaten Schaftanfang wegen Schwanz
    sY1 = round(pfadY1 + sVS * sinAlpha)
    sX2 = round(pfadX1 + sLV * cosAlpha)    # Koordinaten Schaftende wegen Pfeil
    sY2 = round(pfadY1 + sLV * sinAlpha)
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfuehrung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :    # Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft zeichnen
      schaftKoord = self.berechneKonusSchaft(pfadX1, pfadY1, schaftDicke, sX2, sY2, sDP)
      auswahl = [ sX1, sY1 ] + schaftKoord
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(auswahl), auswahl)
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2])
    elif sL <= 0 :	# Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(pfadX1, pfadY1, schaftDicke, sX2, sY2, sDP)
      pX1 = round(pfadX1 + sL * cosAlpha)   # Anfangskoordinaten der Pfeilspitze
      pY1 = round(pfadY1 + sL * sinAlpha)
      pfeilKoord = self.berechneDreiecksSpitze(pX1, pY1, pfadX2, pfadY2)
      if sLV >= 0 :
        auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      else :
        auswahl = schaftKoord[4:6] + pfeilKoord + schaftKoord[2:4]
      self.zeichnePfeil(0, [], pfeilBreite, [pX1, pY1, pfadX2, pfadY2], auswahl)
    else :
      # Pfeilschaft und Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(pfadX1, pfadY1, schaftDicke, sX2, sY2, sDP)
      auswahl =  [ sX1, sY1 ] + schaftKoord
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(auswahl), auswahl)
      #
      pX1 = round(pfadX1 + sL * cosAlpha)   # Anfangskoordinaten der Pfeilspitze
      pY1 = round(pfadY1 + sL * sinAlpha)
      pfeilKoord = self.berechneDreiecksSpitze(pX1, pY1, pfadX2, pfadY2)
      auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(schaftDicke, [pfadX1, pfadY1, sX2, sY2],
        pfeilBreite, [pX1, pY1, pfadX2, pfadY2], auswahl)
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = sX1 + (sX2 - sX1) / 2	; testY = sY1 + (sY2 - sY1) / 2
    self.testeSichbarkeit(testX, testY)
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Beschriftungspfeil".
  def zeichneBeschriftPfeil(self) :
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sL  = gL - pfeilLaenge                      # Schaftlänge des Pfeils
    sXB = round(pfadX1 + sL * cosAlpha)         # End-Koordinaten des Pfeilschaftes
    sYB = round(pfadY1 + sL * sinAlpha)
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfuehrung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Kontrolle, ob die Schaftscheibe gezeichnet wird oder nicht
    if schaftKreis > 0  and  schaftKreis >= schaftDicke:
      # Schaftscheibe wird gezeichnet
      zeichneSchaftscheibe = True
      sDH = schaftDicke / 2                     # Hälftige Schaftdicke
      sKR = schaftKreis / 2                     # Schaftscheibe-Radius
      kXM = round(pfadX1 + sKR * cosAlpha)	    # Koordinaten des Kreismittelpunkts
      kYM = round(pfadY1 + sKR * sinAlpha)
      sA  = math.sqrt(abs(sKR*sKR - sDH*sDH))   # Abstand zum Schaft-Anfangspunkt
      sXA = round(kXM + sA * cosAlpha)          # Anfangs-Koordinaten des Pfeilschaftes
      sYA = round(kYM + sA * sinAlpha)
    else :
      # Schaftscheibe wird NICHT gezeichnet
      zeichneSchaftscheibe = False
      sXA = pfadX1                              # Anfangs-Koordinaten des Pfeilschaftes
      sYA = pfadY1
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :	# Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft zeichnen
      schaftKoord = self.berechneKonusSchaft(sXA, sYA, schaftDicke, sXB, sYB, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      self.zeichnePfeil(schaftDicke, [sXA, sYA, sXB, sYB])
    elif sL <= 0 :	# Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die Pfeilspitze zeichnen
      pfeilKoord = self.berechneDreiecksSpitze(sXB, sYB, pfadX2, pfadY2)
      self.zeichnePfeil(0, [], pfeilBreite, [sXB, sYB, pfadX2, pfadY2], pfeilKoord)
    else :
      # Pfeilschaft und Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(sXA, sYA, schaftDicke, sXB, sYB, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      #
      pfeilKoord = self.berechneDreiecksSpitze(sXB, sYB, pfadX2, pfadY2)
      auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(schaftDicke, [sXA, sYA, sXB, sYB],
        pfeilBreite, [sXB, sYB, pfadX2, pfadY2], auswahl)
    #
    # Schaftscheibe befüllen
    if zeichneSchaftscheibe :
      eX = kXM - sKR	; eY = kYM - sKR
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftKreis, schaftKreis)
      self.befuelleAuswahl()        # FILL_BACKGROUND
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = sXA + (sXB - sXA) / 2	; testY = sYA + (sYB - sYA) / 2
    self.testeSichbarkeit(testX, testY)
    #
    # Teste, ob die Schaftscheibe für den Benutzer sichtbar ist
    if zeichneSchaftscheibe :
      self.testeSichbarkeit(kXM, kYM, "Hintergrund", "meldScheibeSichtbar")
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Scheibengelenkpfeil".
  def zeichneScheibengelenk(self) :
    ### Vorbereitende Berechnungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sL  = gL - pfeilLaenge                      # Schaftlänge des Pfeils
    sXB = round(pfadX1 + sL * cosAlpha)         # Anfangs-Koordinaten des Pfeilschaftes
    sYB = round(pfadY1 + sL * sinAlpha)
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfuehrung"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2), round(gL))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Kontrolle, ob der Schaftkreis gezeichnet wird oder nicht
    if schaftKreis > 0  and  schaftKreis >= schaftDicke:
      # Schaftkreis wird gezeichnet
      zeichneSchaftkreis = True
      sDH = schaftDicke / 2                     # Hälftige Schaftdicke
      sKR = schaftKreis / 2                     # Schaftkreis-Radius
      kXM = round(pfadX1 + sKR * cosAlpha)      # Koordinaten des Kreismittelpunkts
      kYM = round(pfadY1 + sKR * sinAlpha)
      sA  = math.sqrt(abs(sKR*sKR - sDH*sDH))   # Abstand zum Schaft-Anfangspunkt
      sXA = round(kXM + sA * cosAlpha)		    # Anfangs-Koordinaten des Pfeilschaftes
      sYA = round(kYM + sA * sinAlpha)
    else :
      # Schaftkreis wird NICHT gezeichnet
      zeichneSchaftkreis = False
      sXA = pfadX1                              # Anfangs-Koordinaten des Pfeilschaftes
      sYA = pfadY1
    #
    # Pfeil zeichnen
    if pfeilLaenge < 1 :    # Kontrolle, ob Pfeilspitzlänge zu gering ist
      # Nur den Pfeilschaft zeichnen
      schaftKoord = self.berechneKonusSchaft(sXA, sYA, schaftDicke, sXB, sYB, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      self.zeichnePfeil(schaftDicke, [sXA, sYA, sXB, sYB])
    elif sL <= 0 :	# Kontrolle, ob überhaupt ein Schaft zu zeichnen ist
      # Nur die Pfeilspitze zeichnen
      pfeilKoord = self.berechneDreiecksSpitze(sXB, sYB, pfadX2, pfadY2)
      self.zeichnePfeil(0, [], pfeilBreite, [sXB, sYB, pfadX2, pfadY2], pfeilKoord)
    else :
      # Pfeilschaft und Pfeilspitze zeichnen
      schaftKoord = self.berechneKonusSchaft(sXA, sYA, schaftDicke, sXB, sYB, schaftDicke)
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(schaftKoord), schaftKoord)
      #
      pfeilKoord = self.berechneDreiecksSpitze(sXB, sYB, pfadX2, pfadY2)
      auswahl = schaftKoord[2:4] + pfeilKoord + schaftKoord[4:6]
      self.zeichnePfeil(schaftDicke, [sXA, sYA, sXB, sYB],
        pfeilBreite, [sXB, sYB, pfadX2, pfadY2], auswahl)
    #
    # Schaftkreis befüllen
    if zeichneSchaftkreis :
      # Gesamtscheibe befüllen
      eX = kXM - sKR	; eY = kYM - sKR
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftKreis, schaftKreis)
      self.befuelleAuswahl()	# FILL_BACKGROUND
      #pdb.gimp_selection_none(self.Img)
      # Innenscheibe löschen, sodass ein Kreis entsteht
      eX = eX + 10	; eY = eY + 10
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftKreis-20, schaftKreis-20)
      pdb.gimp_edit_clear(layPfeil)
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    testX = sXA + (sXB - sXA) / 2	; testY = sYA + (sYB - sYA) / 2
    self.testeSichbarkeit(testX, testY)
    #
    # Teste, ob der Schaftkreis für den Benutzer sichtbar ist
    if zeichneSchaftkreis :
      self.testeSichbarkeit(kXM, kYM, "Hintergrund", "meldScheibeSichtbar")
    return

  ########################################################################################
  # Zeichnen des Pfeils vom Stil "Gebogener Pfeil (am Pfad entlang)".
  def zeichneGebogenenPfeil(self) :
    ### Vorbereitende annäherungsweise Berechnungen und Festlegungen
    gL, sinAlpha, cosAlpha = self.berechneHypSinCos(pfadX1, pfadY1, pfadX2, pfadY2)
    sDH = schaftDicke / 2               # Hälftige Schaftdicke
    sKR = schaftKreis / 2               # Schaftkreis-Radius
    #
    # Wichtige Informationen im Hinweis-Label ausgeben
    hinweise = textDaten["hinwAusfGebogener"] \
      %(round(pfadX1), round(pfadY1), round(pfadX2), round(pfadY2))
    self.ausgabeHinweise(hinweise)
    #
    # Inhalt der Pfeilebene löschen
    pdb.gimp_drawable_edit_clear(layPfeil)
    #
    # Erste Pfadkomponente besorgen und interpolieren.
    anzKomp, kompIDs = pdb.gimp_vectors_get_strokes(pfadPfeil)
    anzPkte, tubPkte, pfadGeschl = \
      pdb.gimp_vectors_stroke_interpolate(pfadPfeil, kompIDs[0], genauigkeit)
    pkte = list(tubPkte)   ; del tubPkte
    #
    # Anfangs- und Endpunkt für die Bemalung des Pfeilschaftes ermitteln.
    maxSoK = max(sDH, sKR)              # Maximum Schaft- oder Kreishälfte
    maxSoP = max(sDH, pfeilLaenge)      # Maximum Schafthälfte oder Pfeillänge
    anfInd, endInd = self.berAnfEndGebPfeilPDB(kompIDs[0], pkte, anzPkte, maxSoK, maxSoP)
    if anfInd == -1 :
      anfInd, endInd = self.berAnfEndGebPfeilITER(pkte, anzPkte, maxSoK, maxSoP)
    #
    # Pfeilschaft zeichnen
    pkteZeichnen = pkte[anfInd:endInd+2]
    if schaftFuellung == textDaten["txtSchaftfuell"][1] :   # Verlauf
      pdb.gimp_context_set_dynamics("Color From Gradient")
    else :
      pdb.gimp_context_set_dynamics("Dynamics Off")         # Vordergrund/Muster
    pdb.gimp_context_set_brush_size(schaftDicke)
    pdb.gimp_paintbrush_default(layPfeil, len(pkteZeichnen), pkteZeichnen)
    if schaftFuellung == textDaten["txtSchaftfuell"][2] :   # Muster
      # Zuvor gezeichneten Pfeilschaft auswählen und mit Muster befüllen.
      aktVorderg = pdb.gimp_context_get_foreground()
      pdb.gimp_image_select_color(self.Img, CHANNEL_OP_REPLACE, layPfeil, aktVorderg)
      pdb.gimp_drawable_edit_fill(layPfeil, FILL_PATTERN)
    #
    # Pfeilspitze zeichnen
    if pfeilLaenge > 1 :	# Kontrolle, ob Pfeilspitze befüllt werden muss
      pfeilKoord = self.berechneDreiecksSpitze(pkte[endInd], pkte[endInd+1], pfadX2, pfadY2)
      self.zeichnePfeil(0, [], pfeilBreite, [pkte[endInd], pkte[endInd+1], pfadX2, pfadY2], pfeilKoord)
    #
    # Schaftkreis befüllen
    kXM = pkte[anfInd]   ; kYM = pkte[anfInd+1]
    if schaftKreis >= schaftDicke :     # Kontrolle, on Schaftkreis befüllt werden muss
      eX = kXM - sKR	; eY = kYM - sKR
      pdb.gimp_image_select_ellipse(self.Img, CHANNEL_OP_REPLACE, eX, eY, schaftKreis, schaftKreis)
      self.befuelleAuswahl()            # FILL_BACKGROUND
    #
    # Auswahl entfernen und Bildschirm refreshen
    pdb.gimp_selection_none(self.Img)
    pdb.gimp_displays_flush()
    #
    # Teste, ob der Pfeilschaft und die Pfeilspitze für den Benutzer sichtbar ist
    indSchaftmitte = anfInd + (endInd - anfInd) / 2 + 1
    self.testeSichbarkeit(pkte[indSchaftmitte], pkte[indSchaftmitte+1])
    #
    # Teste, ob die Schaftscheibe für den Benutzer sichtbar ist
    self.testeSichbarkeit(kXM, kYM, "Hintergrund", "meldScheibeSichtbar")
    return

  ########################################################################################
  # Gebogener Pfeil: Berechne Anfangs-/Endpunkt des Pfeilschaftes mittels PDB-Routinen.
  # Übergabe:
  #   fKompID  = Kennung der Pfadkomponente.
  #   fPkte    = Ermittelte Koordinaten der Pfadkomponente (x0, y0, x1, y1, .. xN, yN).
  #   fAnzPkte = Anzahl der Punkte in fPkte (d.h. Anzahl Koordinaten = N * 2).
  #   fMaxSoK  = Maximum von Schaft- oder Kreishälfte.
  #   fMaxSoP  = Maximum von Schafthälfte oder Pfeillänge.
  # Rückgabe:
  #   anfInd   = Index in fPkte, der den Anfangspunkt des Pfeilschaftes kennzeichnet.
  #   endInd   = Index in fPkte, welches den Endpunkt des Pfeilschaftes kennzeichnet.
  def berAnfEndGebPfeilPDB(self, fKompID, fPkte, fAnzPkte, fMaxSoK, fMaxSoP) :
    #
    # Anfangspunkt für die Schaftbemalung (Schaft oder Kreis) ermitteln
    kXM, kYM, steigung, gueltig = \
      pdb.gimp_vectors_stroke_get_point_at_dist(pfadPfeil, fKompID, fMaxSoK, genauigkeit)
    if not gueltig :        # PDB-Routine liefert kein gültiges Ergebnis
      return -1, -1
    # Errechneten Anfangspunkt in den interpolierten Koordinaten des Pfades einordnen.
    anfInd = 0
    while anfInd < fAnzPkte - 4 :
      if self.pktImRechteck(kXM,kYM, fPkte[anfInd],fPkte[anfInd+1], fPkte[anfInd+2],fPkte[anfInd+3]) :
        break
      anfInd += 2
    fPkte[anfInd] = kXM   ; fPkte[anfInd+1] = kYM
    #
    # Endpunkt für die Schaftbemalung (Schaft oder Pfeilspitze) ermitteln
    pfadLaenge = pdb.gimp_vectors_stroke_get_length(pfadPfeil, fKompID, genauigkeit)
    entf = pfadLaenge - fMaxSoP
    kXP, kYP, steigung, gueltig = \
      pdb.gimp_vectors_stroke_get_point_at_dist(pfadPfeil, fKompID, entf, genauigkeit)
    if not gueltig :        # PDB-Routine liefert kein gültiges Ergebnis.
      return -1, -1
    # Errechneten Endpunkt in den interpolierten Koordinaten des Pfades einordnen.
    endInd = fAnzPkte - 4
    while endInd > anfInd :
      if self.pktImRechteck(kXP,kYP, fPkte[endInd],fPkte[endInd+1], fPkte[endInd+2],fPkte[endInd+3]) :
        break
      endInd -= 2
    fPkte[endInd+2] = kXP   ; fPkte[endInd+3] = kYP  ; endInd += 2
    #
    return anfInd, endInd

  ########################################################################################
  # Gebogener Pfeil: Berechne Anfangs- und Endpunkt des Pfeilschaftes mittels Iteration.
  # Übergabe:
  #   fPkte    = Ermittelte Koordinaten der Pfadkomponente (x0, y0, x1, y1, .. xN, yN).
  #   fAnzPkte = Anzahl der Punkte in fPkte (d.h. Anzahl Koordinaten = N * 2).
  #   fMaxSoK  = Maximum von Schaft- oder Kreishälfte.
  #   fMaxSoP  = Maximum von Schafthälfte oder Pfeillänge.
  # Rückgabe:
  #   Index in fPkte, der den Anfangspunkt des Pfeilschaftes kennzeichnet.
  #   Index in fPkte, welches den Endpunkt des Pfeilschaftes kennzeichnet.
  def berAnfEndGebPfeilITER(self, fPkte, fAnzPkte, fMaxSoK, fMaxSoP) :
    #
    # Anfangspunkt für die Schaftbemalung (Schaft oder Kreis) ermitteln
    abstand = 0   ; abstMax = fMaxSoK - 2 * genauigkeit   ; anfInd = 0
    while (abstand < abstMax) and (anfInd < fAnzPkte - 4) :
      a = fPkte[anfInd+2] - fPkte[anfInd]	# Y-Koordinaten
      b = fPkte[anfInd+3] - fPkte[anfInd+1]	# X-Koordinaten
      abstand = abstand + math.hypot(a, b)
      anfInd += 2
    # Ermittelten Anfangspunkt in den interpolierten Koordinaten des Pfades einordnen
    if anfInd > 0 :		# Abfrage, ob gefundener Punkt nicht erster Pfadpunkt ist
      c, sinAlpha, cosAlpha = \
        self.berechneHypSinCos(pfadX1, pfadY1, fPkte[anfInd], fPkte[anfInd+1])
      kXM = pfadX1 + fMaxSoK * cosAlpha   ; kYM = pfadY1 + fMaxSoK * sinAlpha
      fPkte[anfInd] = kXM   ; fPkte[anfInd+1] = kYM
    #
    # Endpunkt für die Schaftbemalung (Schaft oder Pfeilspitze) ermitteln
    abstand = 0   ; abstMax = fMaxSoP - 2 * genauigkeit   ; endInd = fAnzPkte-2
    while (abstand < abstMax) and (endInd > anfInd) :
      a = fPkte[endInd+1] - fPkte[endInd-1]		# Y-Koordinaten
      b = fPkte[endInd]   - fPkte[endInd-2]		# X-Koordinaten
      abstand = abstand + math.hypot(a, b)
      endInd -= 2
    # Ermittelten Endpunkt in den interpolierten Koordinaten des Pfades einordnen
    if endInd < fAnzPkte-2 :	# Abfrage, ob gefundener Punkt nicht letzter Pfadpunkt ist
      c, sinAlpha, cosAlpha = \
        self.berechneHypSinCos(fPkte[endInd], fPkte[endInd+1], pfadX2, pfadY2)
      kXP = pfadX2 - fMaxSoP * cosAlpha   ; kYP = pfadY2 - fMaxSoP * sinAlpha
      fPkte[endInd] = kXP   ; fPkte[endInd+1] = kYP
    #
    return anfInd, endInd

  ########################################################################################
  # Kontrolle, ob der Punkt X/Y im Rechteck von X1/Y1 & X2/Y2 liegt?
  def pktImRechteck(self, fX, fY, fX1, fY1, fX2, fY2) :
    x  = int(round(fX,0))   ; y  = int(round(fY,0))
    x1 = int(round(fX1,0))  ; y1 = int(round(fY1,0))
    x2 = int(round(fX2,0))  ; y2 = int(round(fY2,0))
    if x1 > x2 :
      wert = x1     ; x1 = x2   ; x2 = wert
    if y1 > y2 :
      wert = y1     ; y1 = y2   ; y2 = wert
    return (x >= x1) and (x <= x2) and (y >= y1) and (y <= y2)

  ########################################################################################
  # Pfeilschaft und Pfeilspitze zeichnen (malen oder füllen).
  # Voraussetzung:
  #   Die Umrisse des Pfeilschafts müssen von der aufrufenden Methode mit
  #     pdb.gimp_image_select_XYZ(..)
  #   bereits ausgewählt worden sein.
  # Übergabe:
  #   fBSchaft  = Breite/Dicke des Pfeilschaftes.
  #   fKSchaft  = Anfangs- und Endkoordinaten der Pfeilschaft-Achse.
  #               Liste [ X1, Y1, X2, Y2 ] mit 4 Werten.
  #               Bei leerer Liste [] wird kein Pfeilschaft gezeichnet.
  #   fBSpitzen = Breite/Dicke der Pfeilspitze(n).
  #   fKSpitze1 = Anfangs- und Endkoordinaten der Pfeilspitz-Achse der Spitze 1.
  #               Liste [ X1, Y1, X2, Y2 ] mit 4 Werten.
  #               Bei leerer Liste [] wird keine Pfeilspitze gezeichnet.
  #   fASpitze1 = Eckpunkte für die Erstellung einer Pfeilspitz-Auswahl der Spitze 1.
  #               Liste [ X1, Y1, X2, Y2, .. ] mit X Werten.
  #   fKSpitze2 = Anfangs- und Endkoordinaten der Pfeilspitz-Achse der Spitze 2.
  #               Liste [ X1, Y1, X2, Y2 ] mit 4 Werten.
  #               Bei leerer Liste [] wird keine Pfeilspitze gezeichnet.
  #   fASpitze2 = Eckpunkte für die Erstellung einer Pfeilspitz-Auswahl der Spitze 2.
  #               Liste [ X1, Y1, X2, Y2, .. ] mit X Werten.
  def zeichnePfeil(self, fBSchaft, fKSchaft,
  fBSpitzen=0, fKSpitze1=[], fASpitze1=[], fKSpitze2=[], fASpitze2=[]) :
    lenfKSchaft = len(fKSchaft)
    lenfKSpitze1 = len(fKSpitze1)	; lenfKSpitze2 = len(fKSpitze2)
    #
    # Kontrolle, ob Pfeilsschaft und Pfeilspitze mit gleiche Füllung zu zeichnen sind
    if lenfKSchaft == lenfKSpitze1 and schaftFuellung == pfeilFuellung :
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_ADD, len(fASpitze1), fASpitze1)
      anfEndKoord = [ fKSchaft[0], fKSchaft[1], fKSpitze1[2], fKSpitze1[3] ]
      if lenfKSpitze2 == 4 :
        pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_ADD, len(fASpitze2), fASpitze2)
        anfEndKoord = [ fKSpitze1[2], fKSpitze1[3], fKSpitze2[2], fKSpitze2[3] ]
      if schaftFuellung == textDaten["txtSchaftfuell"][1] :  # Verlauf
        # Bei Füllung "Verlauf" muss der gesamte Pfeil mit dem Pinsel gemalt werden
        self.bemaleAuswahl(max([fBSchaft, fBSpitzen]), anfEndKoord)
      else :
        # Bei Füllung "Vordergrund" und "Muster" wird der gesamte Pfeil ausgefüllt
        self.befuelleAuswahl(schaftFuellung)
      return
    #
    # Pfeilschaft und Pfeilspitze haben unterschiedliche Füllungen
    # Pfeilschaft füllen oder malen
    if lenfKSchaft == 4 :
      if schaftFuellung == textDaten["txtSchaftfuell"][1] :  # Verlauf
        # Bei Füllung "Verlauf" muss der Pfeilschaft mit dem Pinsel gemalt werden
        self.bemaleAuswahl(fBSchaft, fKSchaft)
      else :
        # Bei Füllung "Vordergrund" und "Muster" wird der Pfeilschaft ausgefüllt
        self.befuelleAuswahl(schaftFuellung)
    #
    # Pfeilspitze(n) füllen oder malen
    if lenfKSpitze1 == 4 :
      pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_REPLACE, len(fASpitze1), fASpitze1)
      anfEndKoord = fKSpitze1
      if lenfKSpitze2 == 4 :
        pdb.gimp_image_select_polygon(self.Img, CHANNEL_OP_ADD, len(fASpitze2), fASpitze2)
        anfEndKoord = [ fKSpitze1[2], fKSpitze1[3], fKSpitze2[2], fKSpitze2[3] ]
      if pfeilFuellung == textDaten["txtPfeilfuell"][1] :  # Verlauf
        # Bei Füllung "Verlauf" muss die Pfeilspitze mit dem Pinsel gemalt werden
        self.bemaleAuswahl(fBSpitzen, anfEndKoord)
      else :
        # Bei Füllung "Vordergrund" und "Muster" wird die Pfeilspitze ausgefüllt
        self.befuelleAuswahl(pfeilFuellung)
    return

  ########################################################################################
  # Bemale eine definierte Auswahl mit dem aktuell eingestellten Pinsel.
  # Übergabe:
  #   fGroesse     = Größe/Dicke/Breite des Pinsels
  #   fAnfEndKoord = Anfangs- und Endkoordinaten des Malbereichs (ein Rechteck).
  #                  Liste [ X1, Y1, X2, Y2 ] mit 4 Werten.
  def bemaleAuswahl(self, fGroesse, fAnfEndKoord) :
    pdb.gimp_context_set_dynamics("Color From Gradient")
    pdb.gimp_context_set_brush_size(fGroesse)
    pdb.gimp_paintbrush_default(layPfeil, len(fAnfEndKoord), fAnfEndKoord)
    return

  ########################################################################################
  # Befülle eine definierte Auswahl mit dem Vordergrund, einem Muster oder Hintergrund.
  # Übergabe:
  #   fFuelltyp = Fülltyp (Vordergrund, Muster), mit dem befüllt werden soll
  # Anmerkung:
  #   Wird kein Fülltyp übergeben, so wird der Hintergrund-Farbe befüllt!
  def befuelleAuswahl(self, fFuelltyp=None) :
    pdb.gimp_context_set_dynamics("Dynamics Off")
    if fFuelltyp == None :
      pdb.gimp_drawable_edit_fill(layPfeil, FILL_BACKGROUND)
    else :
      if fFuelltyp == textDaten["txtPfeilfuell"][0] :	# Vordergrund
        pdb.gimp_drawable_edit_fill(layPfeil, FILL_FOREGROUND)
      else :
        pdb.gimp_drawable_edit_fill(layPfeil, FILL_PATTERN)
    return

  ########################################################################################
  # Berechne die Hypothenuse, sinAlpha und cosAlpha eines Koordinatenpaars.
  # Übergabe:
  #   fX1/fY1 = Anfangskoordinaten der Hypothenuse
  #   fX2/fY2 = Endkoordinaten der Hypothenuse
  # Rückgabe:
  #   Hypothenuse, sinAlpha, cosAlpha
  def berechneHypSinCos(self, fX1, fY1, fX2, fY2) :
    a = fY2 - fY1	; b = fX2 - fX1
    c = math.hypot(a, b)		# Schaftlänge des Pfeils
    if c == 0 :
      sinAlpha = 0	; cosAlpha = 1	# Sinus und Cosinus Alpha
    else :
      sinAlpha = a / c  ; cosAlpha = b / c
    return c, sinAlpha, cosAlpha

  ########################################################################################
  # Koordinaten eines Pfeilschaftes in Zylinder- oder Konus-Form berechnen.
  # Übergabe:
  #   fX1/fY1 = Anfangskoordinaten des Schaftes
  #   fDicke1 = Schaftdicke an den Anfangskoordinaten
  #   fX2/fY2 = Endkoordinaten des Schaftes
  #   fDicke2 = Schaftdicke an den Endkoordinaten
  # Rückgabe:
  #   Liste mit den ermittelten Pfeilschaft-Koordinaten. Form:
  #     [ x1, y1, x2, y2, x3, y3, x4, y4 ]
  #         x1/y1 .-------------------------------------. x2/y2
  #               | fDicke1                     fDicke2 |
  #       fX1/fY1 +                                     + fX2/fY2
  #               |                                     |
  #         x4/y4 '-------------------------------------' x3/y3
  def berechneKonusSchaft(self, fX1, fY1, fDicke1, fX2, fY2, fDicke2) :
    ### Vorbereitende Berechnungen
    sL, sinAlpha, cosAlpha = self.berechneHypSinCos(fX1, fY1, fX2, fY2)
    sDH1 = fDicke1 / 2          # Hälftige Schaftdicke am AnfPkt
    sDH2 = fDicke2 / 2          # Hälftige Schaftdicke am AnfPkt
    ### Eckpunkte ermitteln
    x1 = round(fX1 + sDH1 * sinAlpha)   ; y1 = round(fY1 - sDH1 * cosAlpha)
    x2 = round(fX2 + sDH2 * sinAlpha)   ; y2 = round(fY2 - sDH2 * cosAlpha)
    x3 = round(fX2 - sDH2 * sinAlpha)   ; y3 = round(fY2 + sDH2 * cosAlpha)
    x4 = round(fX1 - sDH1 * sinAlpha)   ; y4 = round(fY1 + sDH1 * cosAlpha)
    return [ x1, y1, x2, y2, x3, y3, x4, y4 ]

  ########################################################################################
  # Koordinaten eines Pfeilschaftes in taillierter Form berechnen.
  # Übergabe:
  #   fX1/fY1 = Anfangskoordinaten des Schaftes
  #   fX2/fY2 = Endkoordinaten des Schaftes
  #   fDicke  = Schaftdicke des Pfeilschaftes
  # Rückgabe:
  #   Liste mit den ermittelten Pfeilschaft-Koordinaten. Form:
  #     [ x1, y1, x2, y2, x3, y3, x4, y4, ... x7, y7, x8, y8, x9, y9, x10, y10 ]
  #         x1/y1 .-----.                .-----. x2/y2
  #               |       ' .        . '       |
  #               |           ' .. '           |
  #       fX1/fY1 +                            + fX2/fY2
  #               |           , .. ,           |
  #               |       , '        ' ,       |
  #         x4/y4 '----'                 '-----' x3/y3
  def berechneTailliertSchaft(self, fX1, fY1, fX2, fY2, fDicke) :
    ### Vorbereitende Berechnungen
    sL, sinAlpha, cosAlpha = self.berechneHypSinCos(fX1, fY1, fX2, fY2)
    sLH = sL / 2				    # Hälftige Schaftlänge
    sLZ = sL / 6				    # Schaftlänge des Zylinderteils
    sDZH = fDicke / 2				# Hälftige Schaftdicke am Zylinder
    sDTH = (fDicke / 2) / 2			# Hälftige Schaftdicke an der Taillierung
    if sDTH <= 2 : sDTH = fDicke / 2
    xM  = round(fX1 + sLH * cosAlpha)   ; yM  = round(fY1 + sLH * sinAlpha)
    ### Eckpunkte ermitteln
    x1  = round(fX1 + sDZH * sinAlpha)  ; y1  = round(fY1 - sDZH * cosAlpha)
    x10 = round(fX1 - sDZH * sinAlpha)  ; y10 = round(fY1 + sDZH * cosAlpha)
    x2  = round(x1  + sLZ  * cosAlpha)  ; y2  = round(y1  + sLZ  * sinAlpha)
    x9  = round(x10 + sLZ  * cosAlpha)  ; y9  = round(y10 + sLZ  * sinAlpha)
    x3  = round(xM  + sDTH * sinAlpha)  ; y3  = round(yM  - sDTH * cosAlpha)
    x8  = round(xM  - sDTH * sinAlpha)  ; y8  = round(yM  + sDTH * cosAlpha)
    x5  = round(fX2 + sDZH * sinAlpha)  ; y5  = round(fY2 - sDZH * cosAlpha)
    x6  = round(fX2 - sDZH * sinAlpha)  ; y6  = round(fY2 + sDZH * cosAlpha)
    x4  = round(x5  - sLZ  * cosAlpha)  ; y4  = round(y5  - sLZ  * sinAlpha)
    x7  = round(x6  - sLZ  * cosAlpha)  ; y7  = round(y6  - sLZ  * sinAlpha)
    return [ x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, x6, y6, x7, y7, x8, y8, x9, y9, x10, y10 ]

  ########################################################################################
  # Koordinaten der Pfeilspitze mit der Lanzen-Form berechnen
  # Übergabe:
  #   fX1/fY1 = Anfangskoordinaten der Pfeilspitze
  #   fX2/fY2 = Endkoordinaten der Pfeilspitze
  # Rückgabe:
  #   Liste mit den ermittelten Pfeilspitz-Koordinaten. Form:
  #     [ x1, y1, x2, y2, x3, y3 ]
  #           x1/y1  ,
  #               _,' ',
  #                     ',
  #       fX1/fY1 +       . fX2/fY2 = x2/y2
  #               _     ,'
  #                ', ,'
  #           x3/y3  '
  def berechneLanzenSpitze(self, fX1, fY1, fX2, fY2) :
    ### Vorbereitende Berechnungen
    c, sinAlpha, cosAlpha = self.berechneHypSinCos(fX1, fY1, fX2, fY2)
    pBH = pfeilBreite / 2			    # Hälftige Pfeilbreite
    pL3V = (pfeilLaenge / 4) * 3		# Dreiviertel der Pfeillänge
    ### Eckpunkte ermitteln
    x0 = fX2 - pL3V * cosAlpha      ; y0 = fY2 - pL3V * sinAlpha
    x1 = round(x0 + pBH * sinAlpha) ; y1 = round(y0 - pBH * cosAlpha)
    x2 = fX2                        ; y2 = fY2
    x3 = round(x0 - pBH * sinAlpha) ; y3 = round(y0 + pBH * cosAlpha)
    return [ x1, y1, x2, y2, x3, y3 ]

  ########################################################################################
  # Koordinaten der Pfeilspitze mit der Dreiecks-Form berechnen.
  # Übergabe:
  #   fX1/fY1 = Anfangskoordinaten der Pfeilspitze
  #   fX2/fY2 = Endkoordinaten der Pfeilspitze
  # Rückgabe:
  #   Liste mit den ermittelten Pfeilspitz-Koordinaten. Form:
  #     [ x1, y1, x2, y2, x3, y3 ]
  #         x1/y1 .                        x1/y1 ' .
  #               |',                             '  ',
  #              _|  ',                            '   ',
  #                    ',                        ---'    ',
  #       fX1/fY1 +      '. fX2/fY2      fX1/fY1 +         '. fX2/fY2 = x2/y2
  #              _      ,'  = x2/y2              ---,     .'
  #               |   ,'                           ,    .'
  #               | ,'                            ,   .'
  #         x3/y3 .'                       x3/y3 , .'
  def berechneDreiecksSpitze(self, fX1, fY1, fX2, fY2) :
    ### Vorbereitende Berechnungen
    c, sinAlpha, cosAlpha = self.berechneHypSinCos(fX1, fY1, fX2, fY2)
    pBH = pfeilBreite / 2			# Hälftige Pfeilbreite
    ### Eckpunkte ermitteln
    x1 = round(fX1 + pBH * sinAlpha)    ; y1 = round(fY1 - pBH * cosAlpha)
    x2 = fX2                            ; y2 = fY2
    x3 = round(fX1 - pBH * sinAlpha)    ; y3 = round(fY1 + pBH * cosAlpha)
    return [ x1, y1, x2, y2, x3, y3 ]

  ########################################################################################
  # Werkzeugvorgaben: Definieren, verändern, löschen und abrufen.
  def werkzeugVorgaben(self) :
    global wkzFenster

    ######################################################################################
    ### Dialog-Fenster erzeugen
    wkzBreite = 400     ; wkzHoehe = -1     # Festlegung der Mindestgrößen
    wkzTitel = textDaten["wkzFensterTitel"] %(textDaten["pluginNameImMenue"])
    wkzFenster = gtk.Dialog(wkzTitel, None,
      gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
    #wkzFenster.set_default_size(wkzBreite, wkzHoehe)
    wkzFenster.set_resizable(True)
    wkzFenster.set_position(gtk.WIN_POS_CENTER)
    #wkzFenster.set_keep_above(True)        # Immer oberstes Fenster (Probleme!)
    wkzFenster.set_border_width(10)
    wkzFensterBox = wkzFenster.get_content_area()
    #
    ######################################################################################
    # Gesamt-Box, in welcher alle Elemente enthalten sind.
    wkzGesamtBox = gtk.VBox(spacing=10, homogeneous=False)
    wkzFensterBox.add(wkzGesamtBox)
    ######################################################################################
    ### Hinweis-Box für Anleitungen und wichtige Hinweise an den Benutzer.
    wkzHinweiseBox = gtk.HBox(spacing=10, homogeneous=False)
    # ^-- ACHTUNG: Bei "homogeneous=True" sind die Hinweis-Boxen gleich breit!
    wkzLabAnleitungen = addTextMitRahmenZurBox(wkzHinweiseBox, textDaten["wkzAnleitKopf"],
      textDaten["wkzAnleitText"] + "\n\n" + textDaten["wkzAnleitPDB"],
      textDaten["wkzAnleitKopf"] + ".")
    self.wkzLabMeldung = addTextMitRahmenZurBox(wkzHinweiseBox,
      textDaten["wkzHinweiseKopf"], " ", textDaten["wkzHinweiseKopf"] + ".")
    #self.wkzLabMeldung.set_size_request(135, -1)
    self.wkzLabMeldung.set_line_wrap(True)      # Zeilenumbruch
    self.wkzLabMeldung.set_width_chars(20)      # Maximal 20 Zeichen
    wkzGesamtBox.add(wkzHinweiseBox)
    ######################################################################################
    # Einstellungs-Box für grundlegende Einstellungen des Benutzers.
    wkzEinstellBox = gtk.VBox(spacing=10, homogeneous=False)
    zweiSpaltenTab = anlTabelle(3, 2, 10)	# Drei Zeilen, zwei Spalten, Rahmendicke
    ###
    ### Auswahl-Box der Werkzeugvorgaben
    self.wkzLeseDatNamen()			# Vorhandene Werkzeug-Vorgaben lesen
    addTextZurTab(zweiSpaltenTab, 0, 1, 0, 1, textDaten["wkzBezVorgAuswahl"])
    self.wkzComVorgabe = addAuswahlZurTab(zweiSpaltenTab, 0, 1, 1, 2,
      wkzDatNamen, self.wkzEvtVorgabenAuswahl, textDaten["wkzTipVorgAuswahl"])
    self.wkzComVorgabe.set_active(0)
    ###
    ### Eingabe-/Ausgabefeld für Bezeichnung der Werkzeug-Vorgabe
    addTextZurTab(zweiSpaltenTab, 1, 2, 0, 1, textDaten["wkzBezVorgBezeich"])
    self.wkzEntBezeichnung = addEingZurTab(zweiSpaltenTab, 1, 2, 1, 2,
      textDaten["wkzInhVorgBezeich"], self.wkzEvtBezeichnung, textDaten["wkzTipVorgBezeich"])
    ###
    ### Eingabe-/Ausgabefeld für Beschreibung der Werkzeug-Vorgabe
    addTextZurTab(zweiSpaltenTab, 2, 3, 0, 1, textDaten["wkzBezVorgBeschr"])
    self.wkzEntBeschreibung = addEingZurTab(zweiSpaltenTab, 2, 3, 1, 2,
      textDaten["wkzInhVorgBeschr"], self.wkzEvtBeschreibung, textDaten["wkzTipVorgBeschr"])
    ###
    wkzEinstellBox.add(zweiSpaltenTab)
    trennlinie = gtk.HSeparator()	; wkzEinstellBox.add(trennlinie)
    wkzGesamtBox.add(wkzEinstellBox)
    ######################################################################################
    # Tasten/Knöpfe-Box, in welcher alle Tasten enthalten sind.
    wkzTastenBox = gtk.HBox(spacing=10, homogeneous=True)
    ### ^-- ACHTUNG: Durch "homogeneous=True" sind die Tasten gleich breit!
    ###
    ### Taste "Speichern"
    self.wkzButSpeichern = addTasteZurBox(wkzTastenBox, textDaten["wkzBezButSpeichern"],
      self.wkzEvtSpeichern, textDaten["wkzTipButSpeichern"])
    ###
    ### Taste "Löschen"
    self.wkzButLoeschen = addTasteZurBox(wkzTastenBox, textDaten["wkzBezButLoeschen"],
      self.wkzEvtLoeschen, textDaten["wkzTipButLoeschen"])
    self.wkzButLoeschen.set_sensitive(False)
    ###
    ### Taste "Laden"
    self.wkzButLaden = addTasteZurBox(wkzTastenBox, textDaten["wkzBezButLaden"],
      self.wkzEvtLaden, textDaten["wkzTipButLaden"])
    self.wkzButLaden.set_sensitive(False)
    ###
    ### Taste "Beenden"
    wkzButBeenden = addTasteZurBox(wkzTastenBox, textDaten["wkzBezButBeenden"],
      self.wkzEvtBeenden, textDaten["wkzTipButBeenden"])
    ###
    wkzGesamtBox.add(wkzTastenBox)
    #
    ######################################################################################
    # Fenster-Box anzeigen
    self.wkzAusgabeHinweise()
    wkzFenster.show_all()
    #
    ######################################################################################
    # Beim Klick auf den X-Button (x) wird wie bei der Taste "Beenden" reagiert.
    wkzFenster.connect("destroy", self.wkzEvtBeenden)
    rueckgabe = wkzFenster.run()
    wkzFenster.destroy()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion auf Auswahl einer Werkzeugvorgabe.
  def wkzEvtVorgabenAuswahl(self, widget, data=None) :
    aktVorgabe = self.wkzComVorgabe.get_active()
    if aktVorgabe > 0 :     # Vorhandene Werkzeugvorgabe liegt vor
      vorgBezeichnung = wkzDatNamen[aktVorgabe]
      if self.wkzLeseVorgWerte(vorgBezeichnung) :
        self.wkzEntBezeichnung.set_text(wkzVorgWerte["name"])
        self.wkzEntBezeichnung.set_sensitive(False)
        self.wkzEntBeschreibung.set_text(wkzVorgWerte["descr"])
        self.wkzEntBeschreibung.set_sensitive(False)
        self.wkzButLoeschen.set_sensitive(True)
        self.wkzButLaden.set_sensitive(True)
        self.wkzAusgabeHinweise()
        return
    #
    # "Neue Werkzeugvorgabe" wurde gewahlt oder wir gesetzt
    aktVorgabe = 0	; self.wkzComVorgabe.set_active(aktVorgabe)
    self.wkzEntBezeichnung.set_text(textDaten["wkzInhVorgBezeich"])
    self.wkzEntBezeichnung.set_sensitive(True)
    self.wkzEntBeschreibung.set_text(textDaten["wkzInhVorgBeschr"])
    self.wkzEntBeschreibung.set_sensitive(True)
    self.wkzButLoeschen.set_sensitive(False)
    self.wkzButLaden.set_sensitive(False)
    self.wkzAusgabeHinweise()
    wkzFenster.redraw()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion auf Eingabe der Werkzeugbezeichnung.
  def wkzEvtBezeichnung(self, widget, data=None) :
    None

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion auf Eingabe der Werkzeugbeschreibung.
  def wkzEvtBeschreibung(self, widget, data=None) :
    None

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion des Tastendrucks auf "Speichern".
  def wkzEvtSpeichern(self, widget, data=None) :
    # Voraussetzungen kontrollieren
    if not self.wkzVorausKontrolle() :
      return
    vorgBezeichnung = self.wkzEntBezeichnung.get_text().strip(" ")
    if len(vorgBezeichnung) == 0 :
      self.wkzAusgabeHinweise(textDaten["wkzHinwBezLeer"])      # Bezeichnung ist leer
      return
    vorgBeschreibung = self.wkzEntBeschreibung.get_text().strip(" ")
    if len(vorgBeschreibung) == 0 :
      self.wkzAusgabeHinweise(textDaten["wkzHinwBeschrLeer"])   # Beschreibung ist leer
      return
    #
    # Name/Pfad erzeugen und kontrollieren, ob die Werkzeugvorgaben-Datei bereits existiert
    nameDatei = wkzDatAnfang + re.sub('[^\w\-_\. ]', '_', vorgBezeichnung) + ".txt"
    pathDatei = os.path.normpath(wkzVorgPfad + os.sep + nameDatei)
    if os.path.exists(pathDatei) :
      meldung = textDaten["wkzMeldDateiVorh"] %(vorgBezeichnung, wkzVorgPfad)
      antwort = ausgabeMeldung(meldung, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO)
      if antwort == gtk.RESPONSE_NO :
        return
    #
    # Werkzeugvorgaben-Datei schreiben
    vorgDatei = open(pathDatei, "w")
    vorgDatei.write("# " + pluginDatei + " preset file - begin" + "\n")
    vorgDatei.write("# Plugin version: " + pluginDatum + "\n\n")
    for vorgabe in vorgabeNamen :
      wert = eval(vorgabeDaten[vorgabe].Holewert)
      if type(wert) is StringType :
        wert = '"' + wert + '"'
      if vorgabe == "fade_length" and wert == 0 :   # Funktion fehlt
        wert = 50.0
      vorgDatei.write(vorgabe + "=" + str(wert) + "\n")
    vorgDatei.write("\n")
    vorgDatei.write("# " + pluginDatei + " preset file - end" + "\n")
    vorgDatei.flush()
    vorgDatei.close()
    #
    # Anzahl der Werkzeugvorgaben hat sich geändert -> Fenster aktualisieren
    self.wkzNeueVorgabenAuswahl()
    self.wkzComVorgabe.set_active(0)
    meldung = textDaten["wkzHinwVorgGespeich"] %(vorgBezeichnung)
    self.wkzAusgabeHinweise(meldung)
    wkzFenster.redraw()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion des Tastendrucks auf "Löschen".
  def wkzEvtLoeschen(self, widget, data=None) :
    # Voraussetzungen kontrollieren
    if not self.wkzVorausKontrolle() :
      return
    vorgBezeichnung = self.wkzEntBezeichnung.get_text().strip(" ")
    if len(vorgBezeichnung) == 0 :
      self.wkzAusgabeHinweise(textDaten["wkzHinwBezLeer"])  # Bezeichnung ist leer
      return
    #
    # Sicherstellen, dass die Löschung wirklich gewollt ist
    meldung = textDaten["wkzMeldDateiLoesch"] %(vorgBezeichnung)
    antwort = ausgabeMeldung(meldung, gtk.MESSAGE_WARNING, gtk.BUTTONS_YES_NO)
    if antwort == gtk.RESPONSE_NO :
      return
    #
    # Löschung der Werkzeugvorgabe durchführen
    nameDatei = re.sub('[^\w\-_\. ]', '_', vorgBezeichnung)
    pathDatei = os.path.normpath(wkzVorgPfad + os.sep + wkzDatAnfang + nameDatei + ".txt")
    os.remove(pathDatei)
    #
    # Anzahl der Werkzeugvorgaben hat sich geändert -> Fenster aktualisieren
    self.wkzNeueVorgabenAuswahl()
    self.wkzComVorgabe.set_active(0)
    meldung = textDaten["wkzHinwVorgGeloesch"] %(vorgBezeichnung)
    self.wkzAusgabeHinweise(meldung)
    wkzFenster.redraw()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion des Tastendrucks auf "Laden".
  def wkzEvtLaden(self, widget, data=None) :
    # Voraussetzungen kontrollieren
    if not self.wkzVorausKontrolle() :
      return
    vorgBezeichnung = self.wkzEntBezeichnung.get_text().strip(" ")
    if len(vorgBezeichnung) == 0 :
      self.wkzAusgabeHinweise(textDaten["wkzHinwBezLeer"])  # Bezeichnung ist leer
      return
    #
    # Einzelne Einstellungen der Werkzeugvorgabe laden
    for vorgabe in vorgabeNamen :
      wert = wkzVorgWerte[vorgabe]
      fkt = vorgabeDaten[vorgabe].Setzewert
      if fkt == "None" : continue
      #
      if vorgabe == "foreground"  or  vorgabe == "background" :
        indKlammerauf = wert.find("(")          # "RGB " an Stringanfang entfernen
        if indKlammerauf != -1 :
          wert = wert[indKlammerauf:]
      fktAufruf = fkt + "(" + wert + ")"
      rueckgabe = eval(fktAufruf)
    #
    # Meldung ausgeben
    meldung = textDaten["wkzHinwVorgGeladen"] %(vorgBezeichnung)
    self.wkzAusgabeHinweise(meldung)
    wkzFenster.redraw()

  ########################################################################################
  # Werkzeugvorgaben: Ereignis (event) als Reaktion des Tastendrucks auf "Beenden".
  def wkzEvtBeenden(self, widget, data=None) :
    wkzFenster.destroy()

  ########################################################################################
  # Werkzeugvorgaben: Ausgabe wichtiger Informationen im Hinweis-Label
  def wkzAusgabeHinweise(self, fText="") :
    hinweise = fText + "\n\n" + wkzAktHinweis
    self.wkzLabMeldung.set_text(hinweise)

  ########################################################################################
  # Werkzeugvorgaben: Kontrolle aller Voraussetzungen für die Ausführung.
  # Rückgabe:
  #   True bei Erfolg, False bei Fehler.
  def wkzVorausKontrolle(self) :
    # Kontrolle, ob das Vorgaben-Verzeichnis existiert
    if not os.path.isdir(wkzVorgPfad) :
      meldung = textDaten["wkzMeldVorgPfad"] %(wkzVorgPfad)
      antwort = ausgabeMeldung(meldung, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK)
      return False
    return True

  ########################################################################################
  # Werkzeugvorgaben: Vorgabenauswahl ändern.
  def wkzNeueVorgabenAuswahl(self) :
    self.wkzLeseDatNamen()
    neueVorgaben = gtk.ListStore(str)
    for vorgabe in wkzDatNamen :
      neueVorgaben.append([vorgabe])
    self.wkzComVorgabe.set_model(neueVorgaben)

  ########################################################################################
  # Werkzeugvorgaben: Namen der vorhandenen Vorgaben einlesen.
  def wkzLeseDatNamen(self) :
    global wkzDatNamen, wkzAnzDatNam, wkzAktHinweis

    wkzDatNamen = [textDaten["wkzVorgabeErstellen"]] # Auswahl "Werkzeugvorgabe erstellen"
    wkzAnzDatNam = 0
    if not self.wkzVorausKontrolle() :  # Voraussetzungen kontrollieren
      return
    wkzVorgDateien = os.listdir(wkzVorgPfad)
    for dateiName in wkzVorgDateien :
      istWkzDatei = re.match(wkzDatAnfang + '+', dateiName)
      if istWkzDatei :
        vorgBezeichnung = dateiName.split("_", 1)
        wkzDatNamen.append( vorgBezeichnung[1].replace(".txt", "") )
    wkzAnzDatNam = len(wkzDatNamen) - 1
    if wkzAnzDatNam <= 0 :
      wkzAktHinweis = textDaten["wkzHinwVorgKeine"]                   # Keine Vorgaben
    else :
      wkzAktHinweis = textDaten["wkzHinwVorgAnzahl"] %(wkzAnzDatNam)  # Vorgaben gefunden

  ########################################################################################
  # Werkzeugvorgaben: Werte einer vorhandenen Vorgaben einlesen.
  # Rückgabe:
  #   True bei Erfolg, False bei Fehler.
  def wkzLeseVorgWerte(self, fVorgName) :
    global wkzVorgWerte

    # Voraussetzungen kontrollieren
    if not self.wkzVorausKontrolle() :
      return False
    #
    # Werkzeugvorgaben-Datei öffnen
    nameDatei = wkzDatAnfang + fVorgName + ".txt"
    pathDatei = os.path.normpath(wkzVorgPfad + os.sep + nameDatei)
    vorgDatei = open(pathDatei)
    #
    # Werte der Werkzeugvorgabe lesen und ablegen
    wkzVorgWerte.clear()                # Werte der Dictionary löschen
    for zeile in vorgDatei :
      indNewline = zeile.find("\n")     # Newline entfernen
      if indNewline != -1 :
        vorgabe = zeile[:indNewline]
      else :
        vorgabe = zeile
      if len(vorgabe) <= 0 : continue
      if vorgabe[0] == "#" : continue
      #
      elemente = vorgabe.split("=")     # Name und Wert trennen
      if len(elemente) == 2 :
        vorgName = str(elemente[0])	; vorgWert = str(elemente[1])
        if vorgName == "name"  or  vorgName == "descr" :
          wkzVorgWerte[vorgName] = vorgWert[1:-1]
        else :
          wkzVorgWerte[vorgName] = vorgWert
    vorgDatei.close()
    return True

  ########################################################################################
  # Ausgabe wichtiger, globaler Variablen und weiterer übergebenen Variablen.
  # Übergabe:
  #   fWeitere = Array mit weiteren Werten, die ausgeben werden sollen Form:
  #              [ ["Name Variable1", Variable1], ["Name Variable2", Variable2], .. ]
  #   fOhne    = Wichtige und globale Werte nicht mit ausgeben!
  def kontrollAusgabe(self, fWeitere=None, fOhne=False) :
    ### KONTROLLAUSGABE: Aufrufmuster Anfang
    """
    weitere = [ [ "Funktion/Ort/Wert", "!!!!!!!!!!!!!"            ],
                [ "fX1",               fX1                        ],
                [ "fY1",               fY1                        ],
                [ "fX2",               fX2                        ],
                [ "fY2",               fY2                        ],
                [ "pBH",               pBH                        ],
                [ "sinAlpha",          sinAlpha                   ],
                [ "cosAlpha",          cosAlpha                   ],
                [ "pfeilKoordinaten",  [ x1, y1, x2, y2, x3, y3 ] ]
              ]
    self.kontrollAusgabe(fWeitere=weitere, fOhne=True)
    """
    ### KONTROLLAUSGABE: Aufrufmuster Ende
    #
    # Ausgabe wichtiger, globaler Variablen
    meldung = "============================================================\n"
    if not fOhne :
      meldung = meldung \
        + "schaftDicke="       + str(schaftDicke)                + "\n"  \
        + "pfeilLaenge="       + str(pfeilLaenge)                + "\n"  \
        + "pfeilBreite="       + str(pfeilBreite)                + "\n"  \
        + "pfeilIndex="        + str(pfeilIndex)       + ": "            \
            + pfeilNamen[pfeilIndex]                             + "\n"	 \
        + "pfeilPosition="     + str(pfeilPosition)    + ": "            \
            + textDaten["txtPfeilpos"][pfeilPosition]            + "\n"  \
        + "schaftFuellInd="    + str(schaftFuellInd)   + ": "            \
            + schaftFuellung                                     + "\n"  \
        + "pfeilFuellInd="     + str(pfeilFuellInd)    + ": "            \
            + pfeilFuellung                                      + "\n"  \
        + "schaftKreis="       + str(schaftKreis)                + "\n"  \
        + "\n"                                                           \
        + "layGruppenName="    + textDaten["layGruppenName"]     + "\n"  \
        + "layGruppeAngelegt=" + str(layGruppeAngelegt)          + "\n"  \
        + "layGruppe="         + str(layGruppe)        + "\t\t"          \
            + str(type(layGruppe))                               + "\n"  \
        + "\n"                                                           \
        + "layPfeilName="      + textDaten["layPfeilName"]       + "\n"  \
        + "layPfeilNummer="    + str(layPfeilNummer)             + "\n"  \
        + "layPfeil="          + str(layPfeil)         + "\t\t"          \
            + str(type(layPfeil))                                + "\n"  \
        + "\n"                                                           \
        + "pfadName="          + textDaten["pfadName"]           + "\n"  \
        + "pfadPfeil="         + str(pfadPfeil)        + "\t\t"          \
            + str(type(pfadPfeil))                               + "\n\n"
    # Ausgabe eventuell weiterer übergebener Variablen
    if fWeitere != None :
      for ind in range(len(fWeitere)) :
        meldung = meldung + fWeitere[ind][0] + "=" + str(fWeitere[ind][1]) \
          + "\t\t" + str(type(fWeitere[ind][1])) + "\n"
    ausgabeMeldung(meldung)
    return None

##########################################################################################
# Plugin-Klasse.
class plugin_main(gimpplugin.plugin):

  ########################################################################################
  # Definiert, welche Methoden "init()", "quit()", "query()", "run()" implementiert wurden.
  # Normalerweise definiert man diese Methode in seinem eigenen Plugin nicht neu,
  # da diese bereits in der Oberklasse passend gesetzt sind. Aber, es geht.
  def start(self):
    gimp.main(self.init, self.quit, self.query, self._run)

  ########################################################################################
  # Die Methode "init(self)" wird immer dann aufgerufen, wenn GIMP neu gestartet wird.
  # Normalerweise definiert man diese Methode in seinem eigenen Plugin nicht neu.
  # Beispielsweise ist sie sinnvoll, wenn man temporäre Einträge im Menü registrieren
  # möchte, die vom Systemzustand abhängen.
  # Beispiel: Falls eine Kamera XYZ angeschlossen ist (und nur dann),
  #           soll im Menü ein "Bilder von XYZ importieren" auftauchen.
  #           Es ist dann eine gute Idee, ebenfalls einen Eintrag "Geräte aktualisieren"
  #           anzubieten, mit dem man das ohne Neustart machen kann.
  def init(self):
    methode = "init()"
    if testAusgabe :
      meldung = "============================================================\n"    \
        + 'Aufruf der Methode "%s"\n' %(methode)
      ausgabeMeldung(meldung)

  ########################################################################################
  # Die Methode "quit(self)" wird immer dann aufgerufen, wenn eine der anderen Methoden,
  # also "init()", "query()" (die selbst installierten Methoden), fertig ist.
  # Normalerweise definiert man diese Methode in seinem eigenen Plugin nicht neu.
  # Beispielsweise ist sie sinnvoll, wenn man an einer zentralen Stelle aufräumen muss.
  def quit(self):
    methode = "quit()"
    if testAusgabe :
      meldung = "============================================================\n"    \
        + 'Aufruf der Methode "%s"\n' %(methode)
      ausgabeMeldung(meldung)

  ########################################################################################
  # Die Methode "query(self)" wird immer dann aufgerufen, wenn das Plugin als neu
  # oder geändert erkannt wird.
  # Hier legt man normalerweise die permanent vorhandenen Menüeinträge und die dabei
  # aufgerufenen Methoden an.
  def query(self):
    methode = "query()"
    if testAusgabe :
      meldung = "============================================================\n"    \
        + 'Aufruf der Methode "%s"\n' %(methode)
      meldung += '\n'                                       \
        + 'gimpVersion=%s\n'        %( str(gimpVersion) )   \
        + 'pythonVersion=%s\n'      %( pythonVersion )      \
        + 'pygtkVersion=%s\n'       %( str(pygtkVersion) )  \
        + 'pluginVerz="%s"\n'       %( pluginVerz )         \
        + 'pluginDatei="%s"\n'      %( pluginDatei )
      ausgabeMeldung(meldung)
    #
    ######################################################################################
    # Plugin-Methode der Klasse ermitteln.
    klassenMethoden = holeKlassenMethoden(plugin_main)
    pluginMethode = elementStartetMit(klassenMethoden, "HN_")
    if pluginMethode == "" : pluginMethode = pluginName
    #
    ######################################################################################
    # Einbau des Plugins in die Menüleiste der Benutzeroberfläche von GIMP.
    # Die Menüleiste (pluginPfadImMenue) beginnt mit <Toolbox>, <Image>, <Load>, <Save>.
    gimp.install_procedure(
      pluginMethode,
      textDaten["pluginKurzBeschr"],
      textDaten["pluginLangBeschr"],
      "%s (%s)"	%(pluginAuthor, emailVomAuthor),
      "(c) %s"	%(pluginAuthor),
      pluginDatum,
      '<Image>/%s/%s' %(textDaten["pluginPfadImMenue"], textDaten["pluginNameImMenue"]),
      "RGB*",
      PLUGIN,
      [ (PDB_INT32,     "run_mode", "Run mode"      ),
        (PDB_IMAGE,     "image",    "Input image"   ),
        (PDB_DRAWABLE,  "drawable", "Input drawable"),
      ],
      []
    )

  ########################################################################################
  # Die Methode "run(self)" realisiert das eigentliche Plugin.
  def HN_Pfeil_zeichnen(self, fRunMode, fImage, fDrawable):
    global imgBreite, imgHoehe, layAktivBeiStart
    global schaftDicke, pfeilLaenge, pfeilBreite, schaftKreis, genauigkeit

    methode = "HN_Pfeil_zeichnen"
    if testAusgabe :
      meldung = "============================================================\n"	\
        + 'Aufruf der Methode "%s"\n' %(methode)
      meldung += '\n'                           \
        + 'fRunMode=%s\n'   %( str(fRunMode) )  \
        + 'fImage=%s\n'     %( str(fImage) )    \
        + 'fDrawable=%s\n'  %( str(fDrawable) )
      ausgabeMeldung(meldung)
    #
    ######################################################################################
    # Run-Modus kontrollieren
    if (fRunMode != RUN_INTERACTIVE) and (fRunMode != RUN_WITH_LAST_VALS) :
      return
    #
    ######################################################################################
    # Mit dieser Kontrolle wird der doppelte Start des Plugins vermieden.
    if pluginGestartet() :
      return
    # Start des Plugins vermerken
    pluginStart()
    #
    ######################################################################################
    # Zeichenfläche (layer) des aktuellen Bildes ermitteln.
    imgBreite = pdb.gimp_image_width(fImage)
    imgHoehe = pdb.gimp_image_height(fImage)
    #
    # Aktive Ebene bei Plugin-Start besorgen und Hintergrund-Ebene aktivieren.
    layAktivBeiStart = pdb.gimp_image_get_active_layer(fImage)
    #
    # Pinsel für das Bemalen wählen. Falls anderer gewünscht, muss Benutzer ändern.
    pdb.gimp_context_set_brush("2. Hardness 100")
    #
    # Vorgaben für die Pfeil-Parameter, abhängig von der Bildgröße definieren.
    faktor = imgBreite / 4096.0
    vorgabe = round(schaftDicke * faktor)   ; schaftDicke = min(vorgabe, schaftDickeMax)
    vorgabe = round(pfeilLaenge * faktor)   ; pfeilLaenge = min(vorgabe, pfeilLaengeMax)
    vorgabe = round(pfeilBreite * faktor)   ; pfeilBreite = min(vorgabe, pfeilBreiteMax)
    vorgabe = round(schaftKreis * faktor)   ; schaftKreis = min(vorgabe, schaftKreisMax)
    #
    # Genauigkeit für die Pfad-Interpolation, abhängig von der Bildgröße definieren
    genauigkeit = imgBreite / 1000.0
    #
    ######################################################################################
    # Aufruf des Benutzerintefaces für das Zeichnen eines Pfeils.
    fenster = benutzerInterface(fImage, fDrawable)
    fenster.anzeigen()
    #
    ######################################################################################
    # Plugin wird beendet.
    pluginEnde()

if __name__ == '__main__' :
  plugin_main().start()
