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

"""
  PyObjVG -- Version 2.0
  
  Autor: Matthias Heming
  Kontakt: matthias@familie-heming.de
  
  Dieses Pythonmodul wird unter der Creative Commons by-nc-sa
  veroeffentlich, naehere Informationen dazu unter  
  http://creativecommons.org/licenses/by-nc-sa/3.0/de/.

  Mit diesem Pythonmodul soll mit Hilfe von Vektorgrafiken
  ein Zugang zu objektorientiertem Programmieren
  ermöglicht werden.
  
  In diesem Modul enthalten sind die Grafikprimitivklassen
  - Rechteck
  - Kreis
  - Ellipse
  - Linie
  - Polygonzug
  - Polygon
  - Bezierkurve
  - Text
  
  Durch verschiedene Abstraktionsniveaus innerhalb verschiedener
  Klassen ist es moeglich:
  - Eindeutige Namen fuer Grafikobjekte zu verwenden,
    welche innerhalb von Objektkarten zur Identifizierung
    der Objekte durch Schuelerinnen und Schuelern genutzt werden.
    (Für die einfache interaktive Nutzung wird empfohlen,
    Variablennamen und Objektnamen gleich zu wählen.)
  - Objekte in Gruppen zusammenzufassen, wobei Gruppen als
    ganzes gedreht und verschoben werden koennen.
    Dabei koennen Objekte, die mit einer Gruppe verbunden sind,
    bei Aenderungen in den eigenen Attributen, das Gruppenobjekt
    benachrichtigen, so dass z.B. eine Neuzeichnung durch
    externe Zeichenroutinen stattfinden kann.
  - Objekte, die in EbenenGruppen zusammengefasst sind, in
    ihrer Ueberdeckungsreihenfolge zu veraendern.
  - Stilattribute (Linienfarbe/-staerke, Fuellfarbe) einheitlich
    zu modifzieren.
    
  Die meisten der genannten Moeglichkeiten (z.B. die einheitliche
  Namensvergabe) koennen durch Ableitung auch in eigenen
  Klassen genutzt werden.
  
  Damit die Nutzung der Bibliothek in einen weiteren Rahmen
  eingebettet werden kann, ist eine Methode zum SVG-Export
  in die Klasse 'Vektorgrafik' eingebaut.
  In eingeschränkter Art und Weise können auch SVG-Dateien
  importiert werden, diese müssen vom Format her jedoch exakt den
  exportierten SVG-Dateien entsprechen!
  Daher sei der SVG-Import als noch im ALPHA-Stadium bezeichnet.
  
  TODO: Approximation von Ellipsen als Bezierkurve ermöglichen. Dadurch
        können Ellipsen auch gedreht werden, wenn die Ellipsenzeichnungsfunktion
        keine Drehung vorsieht.
  TODO: Dazu gefuellte Bezierkurven implementieren.
        (Letzter Punkt des Polygons muss dann dem ersten entsprechen, mal sehen...)
  TODO: Bei der Groessenberechnung die Linienstaerke mit einbeziehen
          
  Versionsgeschichte:

  Version 1.0 vom 23. September 2008
  Version 1.0.1 vom 26. November 2008
  - BUGFIX: Die Methode drehePunkt hat entgegen der Beschreibung
    den Winkel im Bogenmaß interpretiert.
  Version 2.0 vom 15. Januar 2008
  - Ueberarbeitung saemtlicher Klassen zu Python-New-Style-Klassen
    und stark verbesserte Klassenstrukturen.
  - SVGImport staerker in einzelne Klassen verlagert.
  
"""
#Aktivieren bzw. Deaktivieren von zusatzlichen Debug-Ausgaben
debug = False

# ===========================
# Importanweisungen
# ===========================

# fuer Kopien von Variablen, so dass nicht mit Zugriffsmethoden
# direkte Veraenderungen durchgefuehrt werden koennen.
from copy import deepcopy,copy

# Zur Berechnung von Drehungen
# Umrechnungsmethode 'radians' ist im math-Modul von
# PyS60 nicht enthalten und wird hier daher bei Bedarf
# selbst gebaut.
import math
if 'radians' not in math.__dict__:
    def myradians(radwinkel):
        return float(radwinkel)/180*math.pi
    
    math.radians = myradians

# Importiere XML-Parser Funktionalität
# Für PyS60 muss cElementTree verfügbar sein,
try:
    from ElementTree import parse
    svgimportmoeglich = True
except ImportError:
    try:
        from cElementTree import parse
        svgimportmoeglich = True
    except ImportError:
        svgimportmoeglich = False

if debug:
    if not svgimportmoeglich:
        print "(c)ElementTree nicht verfuegbar, kein SVG-Import moeglich!"
    else:
        print "SVG-Import moeglich."

# Zur Nutzung von Regulaeren Ausdrucken beim SVG-Import
import re


# =======================================
# Allgemeine Hilfsklassen und -methoden
# =======================================
def drehePunkt(p, z, winkel):
    """
    Gibt den um 'winkel' Grad um das Zentrum 'z' gedrehten Punkt 'p' zurueck.
    Angabe des Winkels im Gradmass.
    Dabei wird das Koordinatensystem mit nach unten gerichteter
    positiver y-Achse betrachtet!!
    """
    if debug: print "drehePunkt " + str(p) + " um " + str(z) + " um " + str(winkel) + " Grad."
    # Winkel soll Bedingung 0 <= winkel < 360 erfuellen
    while (winkel > 360):
        winkel -=360
    while (winkel < 0):
        winkel +=360
    
    if (winkel == 0):
        return copy(p)
        
    # verschiebe Punkt, so dass drehzentrum der ursprung ist
    pneu = p-z
    
    # Matrix fuer Vielfache von 90 manuell erstellen, um Rundungsfehler zu vermeiden
    # winkel == 0 ist durch obige return-Anweisung ausgeschlossen
    # winkel == 360 ist durch die winkel-Modifikation am Anfang ausgeschlossen.
    if (winkel == 90):
        if debug: print "drehePunkt: manuelle Drehmatrix fuer 90 Grad"
        sin = 1
        cos = 0
    elif (winkel == 180):
        if debug: print "drehePunkt: manuelle Drehmatrix fuer 180 Grad"
        sin = 0
        cos = -1
    elif (winkel == 270):
        if debug: print "drehePunkt: manuelle Drehmatrix fuer 270 Grad"
        sin = -1
        cos = 0
    else:
        sin = math.sin(math.radians(winkel))
        cos = math.cos(math.radians(winkel))
    
    # Drehmatrix anwenden:
    # Normale Drehmatrix fuer Koordinatensystem mit positiver y-Achse nach oben:
    # (+cos -sin)
    # (+sin +cos)
    # pneu.x,pneu.y = pneu.x * cos - pneu.y * sin, pneu.x * sin + pneu.y * cos
    # Hier aber berücksichtigen, dass positiver y-Achse nach unten gedreht,
    # daher folgende Drehmatrix benutzen:
    # (+cos +sin)
    # (-sin +cos)
    pneu.x,pneu.y = pneu.x * cos + pneu.y * sin, -pneu.x * sin + pneu.y * cos
    # verschiebung rueckgaengig machen 
    pneu = pneu+z
    return pneu

def berechneRechteckUmPunkte(punkteliste):
    """
    Berechnet das Rechteck, welches die in der uebergebenen Liste
    enthaltenen Punkte vollstaendig umgibt.
    Die Masse des Rechteckes werden als Tupel
    (Mittelpunkt:Punkt2D,breite:float,hoehe:float)
    zurueckgegeben.
    """
    maxx = max([p.x for p in punkteliste])
    maxy = max([p.y for p in punkteliste])
    minx = min([p.x for p in punkteliste])
    miny = min([p.y for p in punkteliste])
    mpkt = Punkt2D((minx+maxx)*0.5,(miny+maxy)*0.5)
    breite = maxx-minx
    hoehe = maxy-miny
    return (mpkt,breite,hoehe)

def formatiereObjektkartenDictionary(name,klasse,attribdict):
    """
    Die Methode bekommt eine dictionary-Struktur,
    die jeweils aus 'Attributname' und 'Attributwert'
    besteht (jeweils als string, 'Attributwert'
    darf auch eine Liste aus strings sein).
    Die Ausgabe wird nun an die jeweiligen Laengen
    der Strings angepasst, so dass die Objektkarte
    letztenendes im Format
    
    Objektname  :  Klasse
    ----------------------------
    Attribut     :  Attributwert
    Attributasdf :  Eintrag1
                 :  Eintrag2
    Attributas   :  Attributwert
    Attributd    :  Attributwert
    ----------------------------
    
    als Text zurueckgegeben wird.
    """
    retstr = name + " : " + klasse +"\n"
    breite = max(len(mstr) for mstr in attribdict) + 1
    
    retstr += "-"*breite*2 + "\n"
    
    keylist = attribdict.keys()
    keylist.sort()
    
    for attrib in keylist:
        retstr += attrib
        retstr += " "*(breite-len(attrib)) + ": "
        if str(type(attribdict[attrib]))=="<type 'list'>":
            retstr += attribdict[attrib][0] + "\n"
            if len(attribdict[attrib])>1:
                for item in attribdict[attrib][1:]:
                    retstr += " "*(breite+2) + item +"\n"
        else:
            retstr += attribdict[attrib] + "\n"
    return retstr

def mittelpunkt(pkt1,pkt2):
    """
    Gibt den Mittelpunkt der beiden uebergebenen Punkte zurueck.
    Dabei werden die Parameter als Punkt2D interpretiert
    und ebenfalls ein Objekt der Klasse Punkt2D zurueckgegeben.
    
    Konnten die beiden Parameter nicht als Punkt2D interpretiert
    werden, wird eine Typfehler-Ausnahme erzeugt.
    """
    try:
      x = float((pkt1[0]+pkt2[0])*0.5)
      y = float((pkt1[1]+pkt2[1])*0.5)
    except (TypeError,IndexError,ValueError):
        raise Typfehler("Interpretation als Punkt2D nicht moeglich.")
    mpkt = Punkt2D(x,y)
    return mpkt

def deCasteljauRek(pktliste, rekursionstiefe):
    """
    Berechnet rekursiv eine Annaeherung fuer Bezierkurven
    mit Hilfe des Casteljau-Algorithmus. Ergebnis ist
    eine Punkteliste des annaehernden Polygonzuges.
    """
    if rekursionstiefe < 1: # Abbruchbedingung der Rekursion
        return [ ]
    laenge = len(pktliste)
    kpkt_bz1 = [ ]
    kpkt_bz2 = [ ]
    while laenge > 1:
        #ersten und letzten Punkt zu beiden getrennten Kontrollpolygonen hinzufügen
        kpkt_bz1.append(pktliste[0])
        kpkt_bz2.insert(0, pktliste[laenge - 1])
        tmplist = [ ]
        for i in xrange(laenge - 1):
            tmplist.append(mittelpunkt(pktliste[i], pktliste[i + 1]))
        pktliste = tmplist
        laenge -= 1
    # Letzten Punkt hinzufügen
    kpkt_bz1.append(pktliste[0])
    kpkt_bz2.insert(0, pktliste[0])
    
    # Hier ist die durch 'pktliste' gegebene Bezierkurve in zwei
    #   getrennte Bezierkurven geteilt, die durch die Punktelisten
    #   kpkt_bz1 und kpkt_bz2 gegeben sind und durch den Punkt
    #   'pktliste[0]' verbunden werden.
    #
    # Die beiden Bezierkurventeile werden in der gewünschten Rekursionstiefe
    #   ebenfalls auf diese Art und Weise berechnet.
    #
    # TODO: Rekursionsabbruch durch minimales Epsilon beim Punkteabstand
    rekleft = deCasteljauRek(kpkt_bz1, rekursionstiefe - 1)
    rekright = deCasteljauRek(kpkt_bz2, rekursionstiefe - 1)
    returnlist = []
    for p in rekleft:
        returnlist.append(p)
    returnlist.append(pktliste[0])
    for p in rekright:
        returnlist.append(p)
    return returnlist

def SVGDateiImportRek(element):
    """
    Methode fuer Rekursiven SVGElementImport.
    Der Aufruf der Methode ist nur im Kontext der
    Methode 'SVGDateiImport' vorgesehen.
    Siehe dort fuer eine ausfuehrliche Beschreibung.
    """
    repattern = re.compile(r"""
      (?P<namensraum>\{.*\})  # Gruppe 'namensraum' zum Speichern
                              # des xml-Namensraum
      (?P<tagname>.*)         # Gruppe 'tagname' zum Erfassen des
                              # eigentlichen Tag-Namen     
     """,re.VERBOSE)
    
    retliste = [ ]
    for elem in element:
        tag = repattern.match(elem.tag).group('tagname')
        if debug: print "SVGImport Tag:",tag
        objname = elem.attrib.get('id')
        # falls objname = None ist (in svg nicht gesetzt)
        # werden Objekte "ohne Name" erstellt, d.h.
        # nach Implementierung wird automatisch ein noch
        # nicht vergebener Name gesetzt.
        if debug: print "Name:",objname
        nobj=None #damit die Variable existiert
        # falls in der Fallunterscheidung kein
        # Objekt erzeugt wird.
        if tag == 'g':
            try:
                nobj = GrafikGruppe(objname)
            except Namensfehler:
                nobj = GrafikGruppe()
            objliste = SVGDateiImportRek(elem)
            # falls objektliste Leer, ist damit
            # auch Gruppe leer => kein Problem.
            for obj in objliste:
                nobj.objektVerbinden(obj)
            nobj._SVGImport(elem)
        elif tag == 'rect':
            try:
                nobj = Rechteck(objname)
            except Namensfehler:
                nobj = Rechteck()
            nobj._SVGImport(elem)
        elif tag == 'circle':
            try:
                nobj = Kreis(objname)
            except Namensfehler:
                nobj = Kreis()
            nobj._SVGImport(elem)
        elif tag == 'ellipse':
            try:
                nobj = Ellipse(objname)
            except Namensfehler:
                nobj = Ellipse()
            nobj._SVGImport(elem)
        elif tag == 'line':
            try:
                nobj = Linie(objname)
            except Namensfehler:
                nobj = Linie()
            nobj._SVGImport(elem)
        elif tag == 'polyline':
            try:
                nobj = Polygonzug(objname)
            except Namensfehler:
                nobj = Polygonzug()
            nobj._SVGImport(elem)
        elif tag == 'polygon':
            try:
                nobj = Polygon(objname)
            except Namensfehler:
                nobj = Polygon()
            nobj._SVGImport(elem)
        elif tag == 'text':
            try:
                nobj = Text(objname)
            except Namensfehler:
                nobj = Text()
            nobj._SVGImport(elem)
        elif tag == 'path':
            nobj = SVGPathImport(objname,elem)
        # Import der Transformationen bereits
        # an dieser Stelle moeglich, anstatt
        # in den Importfunktionen der Klassen
        if nobj is not None:
            retliste.append(nobj)
    return retliste

def SVGDateiImport(dateiname):
    """
    Diese Methode extrahiert aus einer SVG-Datei die
    enthaltenen Grafikobjekte und erzeugt daraus
    fuer PyObjVG verfuegbare Python-Objekte, die
    im zurueckgegebenen Vektorgrafikobjekt zusammen
    gefasst sind.
    
    Ist die Extrahierung nicht moeglich, weil
    eine Datei mit dem uebergebenen Namen nicht
    existiert, wird eine Zugriffsfehler-Ausnahme erzeugt.
    
    Ist eine Extrahierung aus anderen Gruenden
    nicht moeglich, so wird eine SVGImportfehler-Ausnahme
    erzeugt.
    
    Allgemeine Hinweise:
    ---------------------
    - Das Attribut 'id' wird als Grafikobjektname interpretiert,
      solange der Name noch nicht vergeben ist,
      sonst wird ein Standardname 'Grafik(None)' genutzt.
    - Die folgenden Transformationsbefehle werden verstanden:
      x translate(x,y) -> Verschiebung um Vektor (x,y)
      x rotate(w,x,y) -> Drehung um Winkel 'w' um Punkt (x,y)
      x scale(f) -> Skaliere mit Faktor f 
          eine getrennte Skalierung in x- bzw y-Richtung ist
          nicht moeglich.
      x matrix(a,b,-b,a,0,0) -> also eine Drehmatrix mit
          Winkel arccos(a).
    
    Importablauf:
    ----------
    - Suchen des Wurzelelement mit Namen SVG
      x Hoehe (height) und Breite (width) werden nicht
        interpretiert, eine Beschraenkung der Sichtflaeche
        ist in PyObjVG nicht vorgesehen (mit Ausnahme der
        Beschraenkung auf Quadranten mit positiven
        Koordinaten)
        
    - Dursuchen der Kind-Elemente des SVG-Tags.
      x Gruppenelemente (<g>) werden als GrafikGruppe
        interpretiert und es wird rekursiv nach
        weiteren Kind-Elementen gesucht.
      x Die folgende Zuordnung von Tag-Name
        zu PyObjVG-Klassen ist vorgenommen.
        rect -> Rechteck
        circle -> Kreis
        ellipse -> Ellipse
        line -> Linie
        polyline -> Polygonzug
        polygon -> Polygon
        text -> Text
        Dabei geschieht die Interpretation des
        passenden Attribut durch die Methode
        '_SVGImport' der passenden PyObjVG-Klasse.
      x Pfad-Elemente (<path>) werden gesondert
        importiert.
        Die Maechtigkeit der SVG-Pfade kann noch nicht
        adaequat in PyObjVG abgebildet werden, daher
        sind zur Zeit noch erhebliche Einschraenkungen
        beim Import von Pfadelementen notwendig.
        Folgende Elemente KOENNEN interpretiert
        werden, alles andere wird vom Programm IGNORIERT:
        - Pfad, die aus Move-To- bzw. Line-To-Operationen
          bestehen, werden als Polygonzug interpretiert.
          Ist der erste und letzte Punkt gleich, so wird
          anstelle des Polygonzugs ein Polygon erzeugt
          und zusaetzlich zum Linienstil auch Daten zur
          Fuellung extrahiert.
        - Pfade, die aus Move-To bzw. Line-To-Operationen
          UND quadratischen bzw. kubischen Bezierkurven
          bestehen. Dabei werden Bezierkurven extrahiert
          und die restlichen Operationen als einzelne
          Polygonzuege abgespeichert.        
    """
    try:
      dateibaum = parse(dateiname)
      wurzel = dateibaum.getroot()
    except IOError:
        raise Zugriffsfehler("Datei konnte nicht gefunden werden.")
    
    # regulaeren Ausdruck zur Analyse der Tag-Namen
    # des (c)ElementTree. Diese bestehen aus folgendem String:
    #   {xml-namensraum}tagname
    repattern = re.compile(r"""
      (?P<namensraum>\{.*\})  # Gruppe 'namensraum' zum Speichern
                              # des xml-Namensraum
      (?P<tagname>.*)         # Gruppe 'tagname' zum Erfassen des
                              # eigentlichen Tag-Namen     
     """,re.VERBOSE)
    
    wurzelname = repattern.match(wurzel.tag).group('tagname')
    
    # Herausfinden, warum ''wurzelname is not "svg"'' nicht
    # geklappt hat... Zufall? Bug?
    if wurzelname != "svg":
        raise SVGImportfehler("Wurzelobjekt ist kein SVG-Tag.")
    objektname = wurzel.attrib.get('id')
    
    # Vektorgrafikobjekt anlegen
    try:
      vg = Vektorgrafik(objektname)
    except Namensfehler:
        vg = Vektorgrafik()
    
    # Rekursiv die Kind-Elemente durchsuchen
    # und zur Vektorgrafik hinzufuegen.
    objliste = SVGDateiImportRek(wurzel)
    for obj in objliste:
        vg.objektVerbinden(obj)
    
    # Vektorgrafik zurueckgeben
    return vg

def SVGStyleExtract(elem):
    """
    Diese Methode wandelt Styledaten von Elementen
    in ein Python-Dictionary bestehen aus str-Schluesseln
    und str-Werte um. Dabei werden zum einen 
    stroke-, stroke-width und fill-Attribut eingetragen,
    weiterhin ebenfalls das style-Attribut analysiert.
    style-Daten im CSS-Stil werden vorrangig behandelt.
    """
    mdict = {}
    strokewidth = elem.get('stroke-width')
    if strokewidth is not None:
        mdict['stroke-width'] = strokewidth
    stroke = elem.get('stroke')
    if stroke is not None:
        mdict['stroke'] = stroke
    fill = elem.get('fill')
    if fill is not None:
        mdict['fill'] = fill
    
    style = elem.get('style')
    if style is not None:
        #zerlegen in 'name:wert'-Paare
        style = style.split(';')
        #als Python tupel (name,wert) interpretieren
        for elem in style:
            tupel = elem.split(':')
            mdict[tupel[0]]=tupel[1]
    return mdict

def SVGPathImport(name,element):
    """
    Methode fuer die Interpretation eines SVG Path-Tag.
    Der Aufruf der Methode ist nur im Kontext der
    Methode 'SVGDateiImport' vorgesehen.
    Siehe dort fuer eine ausfuehrliche Beschreibung.
    """
    objliste = [ ]
    
    # --------------------------------
    # Pfad zerlegen in Tupel der Form
    # (Kommando,Argument(e))
    # --------------------------------
    path = element.attrib['d']
    regex = r'([MmLlHhVvCcSsQqTtAaz])([\+\-,. \d]*)'
    cmdliste = re.findall(regex,path)
    # re.split('[, ]',argumente.strip())
    # macht aus argumenten eine Zahlenliste
    cmdliste = [ (cmd,re.split('[, ]',arg.strip())) for (cmd,arg) in cmdliste]
    # wandelt die Zahlenliste in echte floats um
    cmdfloats = []
    for (cmd,arg) in cmdliste:
        if cmd != 'z' and cmd != 'Z':
            cmdfloats.append((cmd,[float(x) for x in arg]))
        else:
            cmdfloats.append((cmd,arg))
    cmdlist = cmdfloats
    
    # --------------------------------------
    # Befehle nacheinander interpretieren.
    #   und zum korrekten Zeitpunkt Objekte
    #   erzeugen.
    # --------------------------------------
    # Erster Befehl muss immer ein absolut interpretierter
    # Move-To-Befehl sein, egal ob 'M' oder 'm'
    
    polygonweiter = 'LlHhVv'
    
    ersterpunkt = Punkt2D(cmdliste[0][1][0],cmdliste[0][1][1])
    # fuer das 'z' Kommando
    punkteliste = [Punkt2D(cmdliste[0][1][0],cmdliste[0][1][1])]
    # dabei ist punkteliste[-1] die aktuelle Stiftposition
    for (cmd,arg) in cmdliste[1:]:
        #  Ein Polygonzug bzw. ein Polygon wird weitergezeichnet,
        #  solange L,l,H,h oder V,v Kommandos kommen.
        #  Kommt ein anderes Kommando bzw. ist die Kommandoliste zuende,
        #  so kann nun entschieden werden, ob ein Polygon oder
        #  Polygonzug vorliegt.
        if cmd not in polygonweiter:
            if punkteliste[0] == punkteliste[-1]:
                nobj = Polygon()
                Fuellung._SVGImport(nobj,element)
            else:
                nobj = Polygonzug()
            Linien._SVGImport(nobj,element)
            SVGTransformationsImport(nobj,element)
            nobj.setzePunkteliste(punkteliste)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste fuer andere Operationen
            # auf aktuellen Punkt setzen
            punkteliste = [punkteliste[-1]]
        
        # Potentielle Polygone sind nach der
        # Abfrage erstellt worden.
        if cmd == 'L':
            punkteliste.append(Punkt2D(arg[0],arg[1]))
        elif cmd == 'l':
            punkteliste.append(Punkt2D(arg[0],arg[1])+stiftposition)
        elif cmd == 'H':
            npkt = stiftposition
            npkt.x = arg[0]
            punkteliste.append(npkt)
        elif cmd == 'h':
            npkt = stiftposition
            npkt.x = npkt.x+arg[0]
            punkteliste.append(npkt)
        elif cmd == 'V':
            npkt = stiftposition
            npkt.y = arg[0]
            punkteliste.append(npkt)
        elif cmd == 'v':
            npkt = stiftposition
            npkt.y = npkt.y+arg[0]
            punkteliste.append(npkt)
            
        # Nach diesen Abfragen sind eventuell im Bau befindlichen
        # Polygone erweitert worden:
        # Nun die Bezierkurven, hier ist noch Optimierungsbedarf,
        # im Speziellen um Redudanz im Code zu vermeiden.
        if cmd == 'C':
            kontrolle1 = Punkt2D(arg[0],arg[1])
            kontrolle2 = Punkt2D(arg[2],arg[3])
            ende = Punkt2D(arg[4],arg[5])
            letzterkontrollpunkt = kontrolle2
            punkteliste.append(kontrolle1)
            punkteliste.append(kontrolle2)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        elif cmd == 'c':
            kontrolle1 = Punkt2D(arg[0],arg[1])+punkteliste[-1]
            kontrolle2 = Punkt2D(arg[2],arg[3])+punkteliste[-1]
            ende = Punkt2D(arg[4],arg[5])+punkteliste[-1]
            letzterkontrollpunkt = kontrolle2
            punkteliste.append(kontrolle1)
            punkteliste.append(kontrolle2)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        if cmd == 'S':
            kontrolle1 = punkteliste[-1]+(punkteliste[-1]-letzterkontrollpunkt)
            kontrolle2 = Punkt2D(arg[0],arg[1])
            ende = Punkt2D(arg[2],arg[3])
            letzterkontrollpunkt = kontrolle2
            punkteliste.append(kontrolle1)
            punkteliste.append(kontrolle2)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        if cmd == 's':
            kontrolle1 = punkteliste[-1]+(punkteliste[-1]-letzterkontrollpunkt)
            kontrolle2 = Punkt2D(arg[0],arg[1])+punkteliste[-1]
            ende = Punkt2D(arg[2],arg[3])+punkteliste[-1]
            letzterkontrollpunkt = kontrolle2
            punkteliste.append(kontrolle1)
            punkteliste.append(kontrolle2)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
            
        # Jetzt sind Kubische Bezierkurven interpretiert
        # Nun zu quadratischen Bezierkurven
        if cmd == 'Q':
            kontrolle = Punkt2D(arg[0],arg[1])
            ende = Punkt2D(arg[2],arg[3])
            letzterkontrollpunkt = kontrolle
            punkteliste.append(kontrolle)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        elif cmd == 'q':
            kontrolle = Punkt2D(arg[0],arg[1])+punkteliste[-1]
            ende = Punkt2D(arg[2],arg[3])+punkteliste[-1]
            letzterkontrollpunkt = kontrolle
            punkteliste.append(kontrolle)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        elif cmd == 'T':
            kontrolle = punkteliste[-1]+(punkteliste[-1]-letzterkontrollpunkt)
            ende = Punkt2D(arg[0],arg[1])
            letzterkontrollpunkt = kontrolle
            punkteliste.append(kontrolle)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        elif cmd == 't':
            kontrolle = punkteliste[-1]+(punkteliste[-1]-letzterkontrollpunkt)
            ende = Punkt2D(arg[0],arg[1])+punkteliste[-1]
            letzterkontrollpunkt = kontrolle
            punkteliste.append(kontrolle)
            punkteliste.append(ende)
            nobj = Bezierkurve()
            nobj.setzePunkteliste(punkteliste)
            Linien._SVGImport(nobj,element)
            # Neues Objekt zur Liste der neuen Objekt
            # hinzufuegen
            objliste.append(nobj)
            # Punkteliste zuruecksetzen
            punkteliste = [punkteliste[-1]]
        if cmd == 'z':
            punkteliste.append(ersterpunkt)
            
    # Nach der Schleife muess auf noch nicht beendete Polygone
    # ueberprueft werden:
    if len(punkteliste)>1:
        if punkteliste[0] == punkteliste[-1]:
            nobj = Polygon()
            Fuellung._SVGImport(nobj,element)
        else:
            nobj = Polygonzug()
        Linien._SVGImport(nobj,element)
        SVGTransformationsImport(nobj,element)
        nobj.setzePunkteliste(punkteliste)
        # Neues Objekt zur Liste der neuen Objekt
        # hinzufuegen
        objliste.append(nobj)
    
    # -------------------------------------------------
    # Verschiebe Objekte in eine Gruppe zusammenfassen
    # -------------------------------------------------
    if len(objliste)==1:
        retobj = objliste[0]
        try:
            retobj.setzeName(name)
        except Namensfehler:
            pass
    else:
        try:
            retobj = GrafikGruppe(name)
        except Namensfehler:
            retobj = GrafikGruppe()
        for obj in objliste:
            retobj.objektVerbinden(obj)
    return retobj

def SVGTransformationsImport(obj,elem):
    """
    Extrahiert die Transformationsoptionen
    aus dem SVG-Attribut 'transform' des 
    mitgegebenen Elements 'elem' und
    fuehrt die entsprechenden Methoden
    mit dem angegebenen Objekt 'obj' durch.
    
    Der Aufruf dieser Methode ist nur im
    Kontext der Methode 'SVGDateiImport'
    vorgesehen. Siehe dort fuer eine
    ausfuehrliche Beschreibung.
    """
    transform = elem.get('transform')
    if transform is not None:
        if debug: print transform
        repattern = r'(\w*)\(([ \+\-,.\d]*)\)'
        translist = re.findall(repattern,transform)
        # Transformationen werden von rechts nach links
        # durchgefuehrt, daher hier die Umkehrung der transliste
        # und damit die Abarbeitung der Transformationen
        # ebenfalls von rechts nach links
        translist.reverse()
        for elem in translist:
            (name,arg) = elem
            al = [float(x) for x in arg.split(',')]
            if name == 'matrix':
                if debug: print "Matrixtransformation analysieren ..."
                if (al[0]==al[3]) and (al[1]==-al[2]) and (al[4]==al[5]==0):
                    # es ist a,b,-b,a,0,0 eine einfache rotationsmatrix
                    # TODO: Drehzentrum ueberpruefen
                    winkel = math.acos(al[0])
                    obj.dreheUm(winkel)
                else:
                    if debug: print "Matrixtransformation verworfen, da nicht als einfache Rotation erkannt."
            elif name == 'rotate':
                if debug: print "Drehtransformation analysieren ..."
                winkel = al[0]
                drehzentrum = Punkt2D(al[1],al[2])
                obj.dreheUm(winkel,drehzentrum)
            elif name == 'translate':
                if debug: print "Verschiebetransformation analysieren ..."
                obj.verschiebeUm(Punkt2D(al[0],al[1]))
            elif name == 'scale':
                if debug: print "Skalierungstransformation analysieren ..."
                if len(al)==1:
                    obj.skaliereMitFaktor(al[0])
                else:
                    if debug: print "Skalierungstransformation verworfen, da nicht exakt ein Faktor angegeben wurde."
            else:
                if debug: print "Nicht unterstuetzte Transformation vorhanden."

def SVGHeader(breite,hoehe):
    """
    Gibt Standardkonformen SVG-Header fuer ein
    Bild mit den uebergebenen Massen zurueck.
    """
    return """<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
          "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink"
     width="%f" height="%f">
""" % (float(breite),float(hoehe))


class Punkt2D(object):
    """
    Darstellung eines Punktes mit x- und y-Koordinate
    Zur sauberen Speicherung werden eingegebene Werte
    vor der Nutzung mit float(...) umgewandelt.
    """
    def __init__(self,x=0,y=0):
        self.__x = float(x)
        self.__y = float(y)
    
    def gibX(self):
        return self.__x
    
    def gibY(self):
        return self.__y
    
    def setzeX(self,x):
        self.__x = float(x)
    
    def setzeY(self,y):
        self.__y = float(y)
    x = property(fset=setzeX,fget=gibX,doc="x-Koordinate")
    y = property(fset=setzeY,fget=gibY,doc="y-Koordinate")
    
    def __str__(self):
        return str((self.__x,self.__y))
    
    def __add__(self,pkt):
        try:
            xtoadd,ytoadd = float(pkt.x),float(pkt.y)
        except AttributeError:
            try:
                xtoadd,ytoadd = float(pkt[0]),float(pkt[1])
            except TypeError:
                raise Typfehler("Kann Summand nicht als Punkt2D interpretierern.")
        return Punkt2D(self.__x+xtoadd,self.__y+ytoadd)
    
    def __radd__(self,pkt):
        try:
            xtoadd,ytoadd = float(pkt.x),float(pkt.y)
        except AttributeError:
            try:
                xtoadd,ytoadd = float(pkt[0]),float(pkt[1])
            except TypeError:
                raise Typfehler("Kann Summand nicht als Punkt2D interpretierern.")
        return Punkt2D(self.__x+xtoadd,self.__y+ytoadd)
    
    def __sub__(self,pkt):
        try:
            xtosub,ytosub = float(pkt.x),float(pkt.y)
        except AttributeError:
            try:
                xtosub,ytosub = float(pkt[0]),float(pkt[1])
            except TypeError:
                raise Typfehler("Kann Summand nicht als Punkt2D interpretierern.")
        return Punkt2D(self.__x-xtosub,self.__y-ytosub)
    
    def __rsub__(self,pkt):
        try:
            xtoadd,ytoadd = float(pkt.x),float(pkt.y)
        except AttributeError:
            try:
                xtoadd,ytoadd = float(pkt[0]),float(pkt[1])
            except TypeError:
                raise Typfehler("Kann Summand nicht als Punkt2D interpretierern.")
        return Punkt2D(-self.__x+xtoadd,-self.__y+ytoadd)
    
    def __mul__(self,faktor):
        f = float(faktor)
        return Punkt2D(self.__x*f,self.__y*f)
    
    def __rmul__(self,faktor):
        return self.__mul__(faktor)
    
    def __getitem__(self,index):
        if (index != 0 and index != 1):
            raise IndexError("list index out of range")
        if (index == 0):
            return self.__x
        else:
            return self.__y
    
    def __eq__(self,pkt):
        if (self.x == pkt.x and self.y == pkt.y):
            return True
        else:
            return False
    

svgfarbmapping = {
  'aquamarine': 'rgb(127, 255,212)',
  'black': 'rgb( 0, 0, 0)',
  'blue' : 'rgb( 0, 0, 255)',
  'cyan' : 'rgb( 0, 255, 255)',
  'grey' : 'rgb( 128, 128, 128)',
  'gray' : 'rgb( 128, 128, 128)',
  'green' : 'rgb( 0, 128, 0)',
  'lime' : 'rgb( 0, 255, 0)',
  'magenta' : 'rgb( 255, 0, 255)',
  'orange' : 'rgb( 255, 165, 0)',
  'red' : 'rgb( 255, 0, 0)',
  'white' : 'rgb( 0, 0, 0)',
  'yellow' : 'rgb( 255, 255, 0)'
  # TODO: Weiter (alle) Farben von
  # http://www.w3.org/TR/SVG/types.html#ColorKeywords
  # hinzufuegen.
}
class Farbe(object):
    """
    Speichert Farben durch Angabe der RGB-Werte,
    jeweils aus dem Zahlenbereich 0 bis 255
    """
    
    def __init__(self,r=255,g=255,b=255):
        if not ((0 <= r <=255) and (0 <= g <= 255) and (0 <= b <=255)):
            raise Wertefehler("RGB-Werte muessen zwischen 0 und 255 liegen.")
        self.__r=r
        self.__g=g
        self.__b=b
    
    def setzeFarbwerte(self,r=None,g=None,b=None):
        """Legt neue RGB-Werte fest."""
        if r is not None:
            if not (0 <= r <= 255):
                raise Wertefehler("RGB-Werte muessen zwischen 0 und 255 liegen.")
        if g is not None:
            if not (0 <= g <= 255):
                raise Wertefehler("RGB-Werte muessen zwischen 0 und 255 liegen.")
        if r is not None:
            if not (0 <= r <= 255):
                raise Wertefehler("RGB-Werte muessen zwischen 0 und 255 liegen.")
        # Werte nun auf Gueltigkeit ueberprueft, es kann neu gesetzt werden
        if r is not None: self.__r = r
        if g is not None: self.__g = g
        if b is not None: self.__b = b
    
    def setzeRotwert(self,rneu):
        self.setzeFarbwerte(r=rneu)
    
    def setzeGruenwert(self,gneu):
        self.setzeFarbwerte(g=gneu)
    
    def setzeBlauwert(self,bneu):
        self.setzeFarbwerte(b=bneu)
    
    def gibRotwert(self):
        return self.__r
    
    def gibGruenwert(self):
        return self.__g
    
    def gibBlauwert(self):
        return self.__b
    
    r = property(fset=setzeRotwert,fget=gibRotwert,doc="Rotwert")
    g = property(fset=setzeGruenwert,fget=gibGruenwert,doc="Gruenwert")
    b = property(fset=setzeBlauwert,fget=gibBlauwert,doc="Blauwert")
    rot = r
    gruen = g
    blau = b
    
    def __str__(self):
        return "RGB(" + str(self.__r)+","+str(self.__g)+","+str(self.__b)+")"
    
    # Hilfmethoden fuer verschiedene externe Anwendungen.
    def gibSUMFarbe(self):
        """
        Gibt die Farbinformation wieder, so dass sie von Zeichnungsmethoden
        der Stifte und Maeuse Bibliothek verwendet werden koennen.
        """
        return (self.r,self.g,self.b)
    
    def gibSVGFarbe(self):
        """
        Gibt die Farbinformation wieder, so dass sie als Farbattributwert
        innerhalb von SVG-Dateien verwendet werden kann.
        """
        return "rgb(%d,%d,%d)" % (self.__r,self.__g,self.__b)
    
    def setzeSVGFarbe(self,colorstring):
        # 1. Variante: Farbnamen
        if colorstring in svgfarbmapping:
            colorstring = svgfarbmapping[colorstring]
        # 2. Variante: Hexadezimal als #ABCDEF
        rehex = r'#(?P<r>[\daAbBcCdDeEfF]{2,2})(?P<g>[\daAbBcCdDeEfF]{2,2})(?P<b>[\daAbBcCdDeEfF]{2,2})'
        # 3. Variante: rgb(1,2,3) als Dezimalzahlen
        redez = r'rgb\( *(?P<r>\d*) *, *(?P<g>\d*) *, *(?P<b>\d*) *\)'
        # 4. Variante: rgb(1%,2%,3%) als Prozentwerte
        reproz = r'rgb\( *(?P<r>\d*)% *, *(?P<g>\d*)% *, *(?P<b>\d*)% *\)'
        
        # ueberpruefe auf hexdarstellung
        m = re.match(rehex,colorstring)
        if m is not None:
            rot = int(m.group('r'),16)
            gruen = int(m.group('g'),16)
            blau = int(m.group('b'),16)
        
        if m is None:
            # keine hexdarstellung
            m = re.match(redez,colorstring)
            if m is not None:
                rot = int(m.group('r'))
                gruen = int(m.group('g'))
                blau = int(m.group('b'))
                
        if m is None:
            # keine hexdarstellung
            # keine dezimaldarstellung
            m = re.match(reproz,colorstring)
            if m is not None:
                rot = int(255*float(m.group('r'))/100)
                gruen = int(255*float(m.group('g'))/100)
                blau = int(255*float(m.group('b'))/100)
        
        if debug: print rot,gruen,blau
        
        if m is None:
            raise Wertefehler('Kann Farbwert nicht interpretieren',colorstring)
        else:
            self.setzeFarbwerte(rot,gruen,blau)
        
    

#Einige Farben bereits vorgegeben
WEISS   = weiss   = Farbe(255, 255, 255)
SCHWARZ = schwarz = Farbe(0, 0, 0)
ROT     = rot     = Farbe(255, 0, 0)
GRUEN   = gruen   = Farbe(0, 255, 0)
BLAU    = blau    = Farbe(0, 0, 255)
GELB    = gelb    = Farbe(255, 255, 0)
GRAU    = grau    = Farbe(100, 100, 100)


# ===========================
# Ausnahmen
# ===========================
class Namensfehler(Exception):
    """
    Diese Ausnahme wird erzeugt, falls
    ein bereits existierender Objektname
    verwendet werden soll.
    """
    pass

class Typfehler(Exception):
    """
    Die Ausnahme wird erzeugt, falls ein uebergebener
    Parameter nicht interpretiert werden kann.
    """
    pass

class Verbindungsfehler(Exception):
    """
    Die Ausnahme wird erzeugt, falls beim Herstellen
    oder Trennen einer Verbindung Fehler auftreten.
    """
    pass

class Zugriffsfehler(Exception):
    """
    Die Ausnahme wird erzeugt, falls innerhalb einer EbenenGruppe ein
    Objekt verschoben werden soll, welches sich nicht in der Gruppe
    befindet.
    """
    pass

class Ebenenfehler(Exception):
    """
    Die Ausnahme wird erzeugt, falls innerhalb nicht moegliche
    Ebenenverschiebungen in einer EbenenGruppe durchgefuehrt werden
    sollen. Dieses sind Verschiebungen nach vorne bzw. hinten,
    obwohl sich das aktuelle Objekt bereits in der vordersten bzw.
    hintersten Ebene befindet
    """
    pass

class Wertefehler(Exception):
    """
    Diese Ausnahme wird erzeugt, wenn ein Wert nicht zu
    seiner Bedeutung passt, z.B. eine negative Breite oder Hoehe.
    """
    pass

class SVGImportfehler(Exception):
    """
    Diese Ausnahme wird erzeugt, wenn der Export einer
    SVG-Datei nicht moeglich ist.
    """
    pass

# ===========================================
# Klassen zur Verwaltung von Grafikobjekten
# ==========================================
class EindeutigesObjekt(object):
    """
    Die Klasse 'EindeutigesObjekt' ermoeglicht eine eindeutige
    Namensvergabe fuer Objekte.
    
    Durch die Nutzung eines Dictionarys ist eine effiziente Suche nach
    Objekten mit gegebenem Namen moeglich, welche durch eine statische
    Suchmethode realisiert ist.
    
    Ausserdem ist die Verbindung zu einem Objekt moeglich,
    welche die Aenderung des Objektes automatisiert als Signal
    an das verbundene Objekt weiterleitet.
    Mit diesem Signal kann z.B. die automatische Neuzeichnung
    einer Vektorgrafik angestossen werden, falls sich eines der
    enthaltenen Grafikobjekte veraendert hat.
    """
    __namenliste = {}
    __naechsteNummer = 1
    
    def __init__(self,name=None):
        """
        Erzeugt ein Objekt der Klasse 'EindeutigesObjekt'.
        
        - Falls kein Name uebergeben wurde, so wird ein
          noch nicht vergebener Name erzeugt.
        - Falls eine Name uebergeben wird, wird die Verwendung analog
          zur 'setzeName'-Methode versucht, d.h. ein bereits vergebener
          Name erzeugt eine 'Namensfehler'-Ausnahme.
        """
        if debug: print str(self) + " __init__ (EindeutigesObjekt)"
        self.__verbundenesObjekt = None
        self.__signalstatus = False
        if name is not None:
            self.name = name
            # falls name bereits vergeben, wird durch
            # den Aufruf der setzeName-Methode die
            # entsprechende Ausnahme erzeugt.
        else:
            # noch nicht vergebenen Namen suchen
            name = "Objekt" + str(EindeutigesObjekt.__naechsteNummer)
            EindeutigesObjekt.__naechsteNummer += 1
            while (name in EindeutigesObjekt.__namenliste):
                name = "Objekt" + str(EindeutigesObjekt.__naechsteNummer)
                EindeutigesObjekt.__naechsteNummer += 1
            if debug: print "Name '" + name + "' ist noch frei."
            self.__name = name
        
    
    
    def findeObjekt(name):
        liste = EindeutigesObjekt.__namenliste
        if name in liste:
            return liste[name]
        else:
            return None
    findeObjekt = staticmethod(findeObjekt)
    
    def gibObjektliste():
        return copy(EindeutigesObjekt.__namenliste)
        # damit kann auf die Objekte schreiben zugegriffen
        # werden, die namenliste selbst wird jedoch nicht
        # modifiziert
    gibObjektliste = staticmethod(gibObjektliste)
    
    def gibName(self):
        """Gibt den aktuellen Namen des Objektes zurueck."""
        if debug: print str(self) + ": gibName"
        return self.__name
    
    def setzeName(self,name):
        """
        Setzt einen neuen Namen fuer das Objekt.
        'name': Gewuenschter neuer Name fuer das Objekt.
                Er wird mit str(name) genutzt.
        Falls der uebergebene Name bereits vergeben ist,
        wird eine Namensfehler-Ausnahme erzeugt.
        """
        if debug: print str(self) + " setzeName " + name
        name = str(name) # zur Sicherheit, falls Name was
        if name not in EindeutigesObjekt.__namenliste:
            try:
                if self.__name is not None:
                    # Es wurde bereits ein Name gesetzt
                    # die Zuordnung aus Namensliste loeschen
                    del EindeutigesObjekt.__namennliste[self.__name]
                    self.__name = name
                    EindeutigesObjekt.__namenliste[self.__name]=self
            except AttributeError:
                # Aufruf aus __init__, self.__name existiert noch nicht
                self.__name = name
                EindeutigesObjekt.__namenliste[self.__name]=self
        else:
            raise Namensfehler(EindeutigesObjekt.__namenliste[name])
        self.geaendert()
    
    name = property(fget=gibName,fset=setzeName,fdel=None,doc="Name des Objektes")
    
    def trenneVonObjekt(self):
        """
        Die Verbindung zu einem Objekt, welche mit der Methode
        'verbindeMitObjekt' hergestellt wurde, wird hiermit getrennt.
        Eine Verbindungsfehler-Ausnahme wird in einem der folgenden Faelle erzeugt:
        - Es hat keine Verbindung bestanden.
        - Das Zielobjekt stellt keine Methode 'objektTrennen' zur Verfuegung,
          dennoch wird das Attribut 'verbundenesObjekt' auf None gesetzt.
        - Das Zielobjekt lehnt eine Trennung ab (weiss der Teufel, wofuer soetwas
          sinnvoll sein koennte)
        """
        if debug: print str(self) + ": trenneVonObjekt"
        if self.__verbundenesObjekt is None:
            raise Verbindungsfehler("Es besteht keine Verbindung, die getrennt werden koennte.")
        try:
            backup = self.__verbundenesObjekt
            self.__verbundenesObjekt = None
            # das Setzen der Variable __verbundenesObjekt=None ist UNBEDINGT
            # VOR dem Aufruf von objektTrennen zu geschehen, da die Methode
            # durch den Aufruf von gibVerbundenesObjekt die Verbindung ueberprueft.
            # (das verbundene Objekt ist fuer die Synchronisation zustaendig)
            if not (backup.objektTrennen(self)):
                self.__verbundenesObjekt = backup
                raise Verbindungsfehler("Zielobjekt lehnt die Trennung ab.")
        except AttributeError:
            self.__verbundenesObjekt = None
            raise Verbindungsfehler("Zielobjekt stellt keine Methode zur Trennung zur Verfuegung. Trennung daher nur im Quellobjekt vorgenommen.")
    
    def verbindeMitObjekt(self,zielobjekt):
        """
        Erstellt eine Verbindung zu einem beliebigen Objekt.
        Dieses Objekt muss Methoden fuer folgende Aufrufe bereitsstellen:
            >>> zielobjekt.objektVerbinden(self) <<<
            >>> zielobjekt.objektTrennen(self) <<<
            >>> zielobjekt.objektGeaendert(self) <<<
        Falls die 'aenderungenverfolgen'-Option aktiviert ist,
        werden aenderungen an dieses Objekt durch den Aufruf
            >>> zielobjekt.objektGeaendert(self) <<<
        mitgeteilt.
        Eine Verbindungsfehler-Ausnahme wird in einem der folgenden Faelle erzeugt:
        - Es besteht bereits eine Verbindung.
        - Das Zielobjekt stellt keine Methode 'objektVerbinden' zur Verfuegung.
        - Das Zielobjekt lehnt eine Verbindung ab.
        """
        if debug: print str(self) + ": verbindeMitObjekt "+ str(zielobjekt)
        if self.__verbundenesObjekt is not None:
            raise Verbindungsfehler("Es besteht bereits eine Verbindung zu einem Objekt.")
        try:
            self.__verbundenesObjekt = zielobjekt
            # das Setzen der Variable __verbundenesObjekt=zielobjekt ist UNBEDINGT
            # VOR dem Aufruf von objektVerbinden zu geschehen, da die Methode
            # durch den Aufruf von gibVerbundenesObjekt die Verbindung ueberprueft.
            # (das verbundene Objekt ist fuer die Synchronisation zustaendig)
            if not (self.__verbundenesObjekt.objektVerbinden(self)):
                self.__verbundenesObjekt = None
                raise Verbindungsfehler("Zielobjekt lehnt die Verbindung ab.")
        except AttributeError:
            raise Verbindungsfehler("Zielobjekt stellt keine Methode zur Verbindung zur Verfuegung.")
    
    def gibVerbundenesObjekt(self):
        """Gibt das Objekt zurueck, welches durch die Methode ."""
        if debug: print str(self) + ": gibVerbundenesObjekt"
        return self.__verbundenesObjekt
    
    verbundenesObjekt = property(fget=gibVerbundenesObjekt,fset=verbindeMitObjekt,\
        fdel=trenneVonObjekt,doc="Objekt, welches durch die Methode 'verbindeMitObjekt' verbunden wurde.")
    
    def aktiviereSignale(self):
        """Aktiviert das Senden von Signalen bei Veraenderungen des Objektes."""
        self.setzeSignalstatus(True)
    
    def deaktiviereSignale(self):
        """Deaktiviert das Senden von Signalen bei Veraenderungen des Objektes"""
        self.setzeSignalstatus(False)
    
    def gibSignalstatus(self):
        """Gibt zurueck, ob bei Veraenderungen Signale versendet (True) oder nicht versendet (False) werden."""
        return self.__signalstatus
    
    def setzeSignalstatus(self,neuerstatus):
        """Legt fest, ob Signale versendet werden (True) oder nicht (False)."""
        if debug: print str(self) + ": setzeSignalstatus"
        if (neuerstatus==True):
            self.__signalstatus=True
        elif (neuerstatus==False):
            self.__signalstatus=False
        else:
            raise Typfehler("Der neue Status kann nicht als Wahrheitswert interpretiert werden.")
    
    signalstatus = property(fget=gibSignalstatus,fset=setzeSignalstatus,fdel=None, \
        doc="""
        Gibt an, ob bei Veraenderungen Signale versendet werden (True) oder nicht (False).
        Die Methoden, die aenderungen vornehmen, muessen zur Signalweiterleitung
        selbststaendig die Methode 'objektWurdeGeaendert()' aufrufen.
        """)
    
    def geaendert(self):
        """
        Sorgt fuer eine Signalweiterleitung, falls das Objekt sich veraendert hat.
        Methoden, die aenderungen vornehmen, muessen diese Methode manuell aufrufen.
        So kann selbst festgelegt werden, welche Attributsaenderung auch im
        Kontext einer bestimmten Aufgabenstellung eine aenderung darstellt und
        weitergeleitet werden muss.
        Die Methode wird in dieser Klasse beispielsweise NICHT aufgerufen,
        falls der Name des Objektes geaendert wird.
        Falls das verbundene Objekt keine Methoden zur Signalverarbeitung zur Verfuegung
        stellt, wird eine Verbindungsfehler-Ausnahme erzeugt.
        """
        if (debug): print str(self) + " geaendert"
        if ((self.__signalstatus==True) and (self.__verbundenesObjekt is not None)):
            self.__verbundenesObjekt.objektGeaendert(self)
    
    def _gibObjektkartenDictionary(self):
        if self.verbundenesObjekt is None:
            objverbindung = "Nicht verbunden"
        else:
            objverbindung = self.verbundenesObjekt.name
        if self.signalstatus is True:
            sigstatus = "aktiviert (True)"
        else:
            sigstatus = "deaktiviert (False)"
        
        retdict = {"Verbunden zu Objekt":objverbindung,"Signalstatus":sigstatus}
        return retdict
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"EindeutigesObjekt",mdict)
    

class Gruppe(EindeutigesObjekt):
    """
    Die Klasse 'Gruppe' ermoeglicht das Zusammenfassen von Objekten
    der Klasse 'EindeutigesObjekt'.
    
    Dazu werden die gewuenschten Methoden der Objektverbindung aus der
    Klasse 'EindeutigesObjekt' implementiert.
    """
    def __init__(self, name=None):
        if debug: print str(self) + " __init__ (Gruppe)"
        EindeutigesObjekt.__init__(self,name)
        self.__objektliste = []
    
    def objektVerbinden(self,neuesobjekt):
        """
        Es wird das uebergebene Objekt zur Gruppe hinzugefuegt.
        
        Falls die Methode nicht ueber das uebergebene Objekt
        aufgerufen wurde, wird auch aus Sicht des Objekts
        die Verbindung hergestellt.
        Eine Verbindungsfehler-Ausnahme wird in einem der folgenden Faelle erzeugt:
        - Ein Objekt ist bereits verbunden
          (moeglich, falls die Methode nicht ueber das Objekt aufgerufen wurde)
        - Ein Objekt ist bereits zu einer anderen Gruppe verbunden
          (moeglich, falls die Methode nicht ueber das Objekt aufgerufen wurde)
        - Objekt stellt keine Methoden zur Verbindungskontrolle
          (verbindeMitObjekt//gibVerbundenesObjekt) zur Verfuegung.
        """
        if debug: print str(self) + ": objektVerbinden " + str(neuesobjekt)
        try:
            con = neuesobjekt.gibVerbundenesObjekt()
            if (con is None):
                neuesobjekt.verbindeMitObjekt(self)
                # Verbindung wird auf Objektseite initialisiert
            elif (con==self):
                if (con in self.__objektliste):
                    raise Verbindungsfehler("Objekt ist bereits verbunden.")
                else:
                    self.__objektliste.append(neuesobjekt)
                    return True
            else:
                raise Verbindungsfehler("Objekt ist anderweitig verbunden.")      
        except AttributeError:
            raise Verbindungsfehler("Objekt stellt keine Methode zur Verbindungskontrolle zur Verfuegung.")
    
    def objektTrennen(self,objekt):
        """
        Es wird das uebergebene Objekt von der Gruppe entfernt.
        Ein Verbindungsfehler wird in einem der folgenden Faelle erzeugt:
        - Das Objekt war nicht mit der Gruppe verbunden.
        - Objekt ist mit einer anderen Gruppe verbunden.
        - Das Objekt stellt keine Methoden zur Verbindungskontrolle
          (trenneVonObjekt//gibVerbundenesObjekt) zur Verfuegung.
        """
        if debug: print str(self) + ": objektTrennen " + str(objekt)
        if objekt not in self.__objektliste:
            raise Verbindungsfehler("Objekt war nicht mit dieser Gruppe verbunden.")
        try:
            con = objekt.gibVerbundenesObjekt()
            if con is not None:
                # eine Verbindung zu einer anderen Gruppe ist durch vorherige
                # Abfrage ausgeschlossen
                objekt.trenneVonObjekt()
                # Trennung auf Objektseite initialisiert
            else:
                self.__objektliste.remove(objekt)
                return True
        except AttributeError:
            raise Verbindungsfehler("Das Objekt stellt keine Methoden zur Verbindungskontrolle zur Verfuegung.")
    
    def objektGeaendert(self,objekt):
        """
        Signale aus verwalteten Objekten fuehren zum Aufruf der 'geaendert' Methode,
        werden also falls verbunden, hierarchisch nach oben weitergeleitet.
        """
        self.geaendert()
    
    def gibObjektliste(self):
        return copy(self.__objektliste)
    
    objektliste=property(fget=gibObjektliste,fset=None,fdel=None,doc=\
        """
        Gibt eine Liste der verbundenen Objekte zurueck.
        ACHTUNG:
        Das Attribut ist NUR LESBAR, Operationen wie 'append' oder 'remove'
        arbeiten nur auf einer Kopie der Liste""")
    
    def _gibObjektkartenDictionary(self):
        anzobj = len(self.__objektliste)
        if anzobj==0:
            objliste = ["Keine"]
        elif anzobj==1:
            objliste = [self.objektliste[0].name]
        else:
            objliste = [self.objektliste[0].name]
            for obj in self.objektliste[1:]:
                objliste.append(obj.name)
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update({"Verwaltete Objekte":objliste})
        return retdict
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Gruppe",mdict)
    
    
    def setzeSignalstatusRekursiv(self,nstatus):
        self.setzeSignalstatus(nstatus)
        for obj in self.gibObjektliste():
            try:
                obj.setzeSignalstatusRekursiv(nstatus)
            except AttributeError:
                obj.setzeSignalstatus(nstatus)
    
    def aktiviereSignaleRekursiv(self):
        self.setzeSignalstatusRekursiv(True)
    
    def deaktivereSignalRekursiv(self):
        self.setzeSignalstatusRekursiv(False)
    
    def _SVGImport(self,elem):
        SVGTransformationsImport(self,elem)
    

class EbenenGruppe(Gruppe):
    """
    In einer EbenenGruppe werden Objekte innerhalb von Ebenen organisiert.
    Dabei wird pro Objekt eine Ebenen festgelegt. Ebene 0 entspricht der obersten Ebene.
    Objekte koennen durch die vorgegebenen Methoden untereinander verschoben werden.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Ebenengruppe)"
        Gruppe.__init__(self,name)
        self.__objektliste = self._Gruppe__objektliste
        # Zugriff auf Variable der Klasse 'Gruppe'
        # eventuell gibt es da eine schoenere Moeglichkeit.
    
    def gibEbeneVonObjekt(self,objekt):
        """
        Gibt ein Tupel aus der Ebenennummer des Objektes und der
        maximalen Ebenennummer zurueck.
        Ein Zugriffsfehler wird erzeugt, falls das Objekt nicht in der
        EbenenGruppe enthalten ist.
        
        Beispiel:
         obj1 ist in Ebene 0
         obj2 ist in Ebene 1
         obj3 ist in Ebene 2
        gibEbeneVonObjekt(obj1) ergibt (0,2)
        gibEbeneVonObjekt(obj2) ergibt (1,2)
        gibEbeneVonObjekt(obj4) Zugriffsfehler
        """
        try:
            return (self.__objektliste.index(objekt),len(self.__objektliste)-1)
        except ValueError:
            raise Zugriffsfehler("Das Objekt ist nicht in der EbenenGruppe enthalten.")
    
    def objektEineEbeneNachVorne(self,objekt):
        """
        Verschiebt das angegebene Objekt eine Ebene nach vorne.
        Ein Zugriffsfehler wird erzeugt, falls das Objekt nicht in der
        EbenenGruppe enthalten ist.
        Ist das Objekt bereits in Ebene 0, so wird ein Ebenenfehler erzeugt.
        """
        try:
            pos_alt = self.__objektliste.index(objekt)
            if (pos_alt==0):
                raise Ebenenfehler("Das Objekt befindet sich bereits ganz vorne.")
            else:
                self.__objektliste.remove(objekt)
                self.__objektliste.insert(pos_alt-1,objekt)
        except ValueError:
            raise Zugriffsfehler("Das Objekt ist nicht in der EbenenGruppe enthalten.")
    
    def objektEineEbeneNachHinten(self,objekt):
        """
        Verschiebt das angegebene Objekt eine Ebene nach hinten.
        Ein Zugriffsfehler wird erzeugt, falls das Objekt nicht in der
        EbenenGruppe enthalten ist.
        Ist das Objekt bereits in der letzten Ebene, so wird ein Ebenenfehler erzeugt.
        """
        try:
            pos_alt = self.__objektliste.index(objekt)
            if (pos_alt==len(self.__objektliste)-1):
                raise Ebenenfehler("Das Objekt befindet sich bereits ganz hinten.")
            else:
                self.__objektliste.remove(objekt)
                self.__objektliste.insert(pos_alt+1,objekt)
        except ValueError:
            raise Zugriffsfehler("Das Objekt ist nicht in der EbenenGruppe enthalten.")
    
    def objektNachVorne(self,objekt):
        """
        Verschiebt das angegebene Objekt in die erste Ebene (Ebene 0).
        Ein Zugriffsfehler wird erzeugt, falls das Objekt nicht in der
        EbenenGruppe enthalten ist.
        Ist das Objekt bereits in Ebene 0, so wird ein Ebenenfehler erzeugt.
        """
        try:
            pos_alt = self.__objektliste.index(objekt)
            if (pos_alt==0):
                raise Ebenenfehler("Das Objekt befindet sich bereits ganz vorne.")
            else:
                self.__objektliste.remove(objekt)
                self.__objektliste.insert(0,objekt)
        except ValueError:
            raise Zugriffsfehler("Das Objekt ist nicht in der EbenenGruppe enthalten.")
    
    def objektNachHinten(self,objekt):
        """
        Verschiebt das angegebene Objekt in die letzte Ebene.
        Ein Zugriffsfehler wird erzeugt, falls das Objekt nicht in der
        EbenenGruppe enthalten ist.
        Ist das Objekt bereits in der letzten Ebene, so wird ein Ebenenfehler erzeugt.
        """
        try:
            pos_alt = self.__objektliste.index(objekt)
            if (pos_alt==len(self.__objektliste)-1):
                raise Ebenenfehler("Das Objekt befindet sich bereits ganz hinten.")
            else:
                self.__objektliste.remove(objekt)
                self.__objektliste.append(objekt)
        except ValueError:
            raise Zugriffsfehler("Das Objekt ist nicht in der EbenenGruppe enthalten.")
    
    
    def _gibObjektkartenDictionary(self):
        return Gruppe._gibObjektkartenDictionary(self)
        # TODO: Nochmal ueberpruefen, ob die Methode
        # nicht durch vererbung von Gruppe vorliegt
    
    def gibSVGCode(self):
        return Gruppe.gibSVGCode(self)
        # TODO: Nochmal ueberpruefen, ob die Methode
        # nicht durch vererbung von Gruppe vorliegt
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"EbenenGruppe",mdict)
    

class GrafikGruppe(EbenenGruppe):
    """
    Die Klasse 'Grafikgruppe' ermoeglicht die Zusammenfassung
    von eindeutigen Grafikobjekten bzw. eindeutigen Grafikgruppen.
    Zusaetzlich zur Verwaltung der Objekte aus der Klasse
    'EbenenGruppe' sind Methoden zur Transformation vorhanden.
    """
    def gibGroesse(self):
        """
        Es wird ein Rechteck berechnet, welches welches die enthaltenen
        Grafikobjekte vollstaendig einschliesst. Die Masse dieses
        Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
        """
        return gibGroesseMitMittelpunkt()[1:]
        # also ohne den ersten Teil des 3-Tupel (Mittelpunkt,Breite,Hoehe)
    
    def gibMittelpunkt(self):
        """
        Es wird ein Rechteck berechnet, welches welches die enthaltenen
        Grafikobjekte vollstaendig einschliesst. Der Mittelpunkt
        dieses Rechteckes wird zurueckgegeben.
        """
        return gibGroesseMitMittelpunkt()[0]
    
    def gibGroesseMitMittelpunkt(self):
        """
        Es wird ein Rechteck berechnet, welches die enthaltenen
        Grafikobjekte vollstaendig einschliesst. Die Masse dieses
        Rechteckes werden als 3-Tupel '(Mittelpunkt,Breite,Hoehe)' zurueckgegeben.
        
        Falls ein enthaltenes Objekt keine passenden Methoden zur
        Groessenberechnung zur Verfuegung stellt, wird ein Typfehler erzeugt.
        """
        objliste = self.gibObjektliste()
        dim = [ ] # liste aus 4-Tupeln: (xmin,xmax,ymin,ymax)
        for obj in objliste:
            try:
                m,b,h = obj.gibGroesseMitMittelpunkt()
                # falls obj eine Grafikgruppe ist, bei der
                # wuerde sonst diese Methode implizit doppelt aufgerufen
            except AttributeError:
                try:
                    m = obj.gibMittelpunkt()
                    b,h = obj.gibGroesse()
                except AttributeError:
                    raise Typfehler("Objekt stellt keine passenden Methoden zur Verfuegung.")
            dim.append((m.x-b*0.5,m.x+b*0.5,m.y-h*0.5,m.y+h*0.5))
        xmaxwert = max([xmax for xmin,xmax,ymin,ymax in dim])
        ymaxwert = max([ymax for xmin,xmax,ymin,ymax in dim])
        xminwert = min([xmin for xmin,xmax,ymin,ymax in dim])
        yminwert = min([ymin for xmin,xmax,ymin,ymax in dim])
        breite = xmaxwert-xminwert
        hoehe = ymaxwert-yminwert
        mittelpunkt = Punkt2D( (xmaxwert+xminwert)*0.5 , (ymaxwert+yminwert)*0.5)
        return mittelpunkt,breite,hoehe
    
    def setzeMittelpunkt(self,nmpkt):
        vvek = nmpkt-self.gibMittelpunkt()
        self.verschiebeUm(vvek)
    
    def verschiebeUm(self,vvek):
        """
        Verschiebt die verwalteten Grafikobjekte bzw. Grafikgruppen.
        Falls ein verwaltetes Objekt keine Methode
        'verschiebenUm' zur Verfuegung stellt, wird ein
        Typfehler erzeugt.
        """
        defekteobjekte = [ ]
        for obj in self.gibObjektliste():
            try:
                obj.verschiebeUm(vvek)
            except AttributeError:
                defekteobjekte.append(obj)
        if len(defekteobjekte)>0:
            raise Typfehler("Es konnten nicht alle Objekte verschoben werden.",defekteobjekte)
    
    def dreheUm(self,winkel,drehzentrum):
        """
        Dreht die verwalteten Grafikobjekte bzw. Grafikgruppen.
        Falls ein verwaltetes Objekt keine Methode 'dreheUm' zur
        Verfuegung stellt, wird ein Typfehler erzeugt.
        """
        defekteobjekte = [ ]
        for obj in self.gibObjektliste():
            try:
                obj.dreheUm(winkel,drehzentrum)
            except AttributeError:
                defekteobjekte.append(obj)
        if len(defekteobjekte)>0:
            raise Typfehler("Es konnten nicht alle Objekte gedreht werden.",defekteobjekte)
    
    def gibSVGCode(self):
        """
        Gibt den entsprechenden SVG-Code
        der enthaltenen Grafikobjekte zurueck,
        eingeschlossen in ein SVG-Group-Tag.
        
        Falls eines der verwalteten Objekte keinen SVG-Export
        implementiert hat, wird ein Typfehler erzeugt.
        """
        objliste = self.gibObjektliste()
        retstr = "<g id=\"%s\">\n" % (self.name)
        try:
            for obj in objliste:
                retstr += obj.gibSVGCode()
        except AttributeError:
            print obj
            raise Typfehler("Ein verwaltetes Objekt stellt keinen SVG-Export zur Verfuegung.")
        retstr += "</g>\n"
        return retstr
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        mpkt = self.gibMittelpunkt()
        objliste = self.gibObjektliste()
        defekteobjekte = [ ]
        for obj in objliste:
            try:
                mobj = obj.gibMittelpunkt()
                # neuer Mittelpunktswert
                mobjneu = mobj-pmkt # Abstand des Objektmittelpunkt vom Vektorgrafikmittelpu    nk
                mobjneu *= f        # Veraenderter Abstand durch Skalierung
                mobjneu += mpkt     # absoluter Mittelpunkt
                mobjneu -= mobj     # notwendige Verschiebung
                obj.verschiebeUm(mobjneu)
                obj.skaliereMitFaktor(f)
            except AttributeError:
                defekteobjekte.append(obj)
        if len(defekteobjekte)>0:
            raise Typfehler("Es konnten nicht alle Objekte skaliert werden.",defekteobjekte)
    

class Vektorgrafik(GrafikGruppe):
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Vektorgrafik)"
        EbenenGruppe.__init__(self,name)
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Vektorgrafik",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = GrafikGruppe._gibObjektkartenDictionary(self)
        return retdict
    
    def gibSVGCode(self,header=None):
        """
        Gibt den entsprechenden SVG-Code der Vektorgrafik zurueck.
        Mit 'header' auf True wird davon ausgegangen, dass
        dieses Objekt das Wurzelelement des Bildes darstellt,
        es wird also ein passender SVG-Header mit ausgegeben.
        
        Ohne Angabe von 'header' wird davon ausgegangen, dass
        dieses Objekt nur einem Teil des ganzen Bildes entspricht,
        die verwalteten Grafikobjekte werden also nur in ein
        'group'-Tag mit der ID der Vektorgrafik 'eingepackt'.
        
        Falls eines der verwalteten Objekte keinen SVG-Export
        implementiert hat, wird ein Typfehler erzeugt.
        """
        if header is True:
            objliste = self.gibObjektliste()
            mpkt,breite,hoehe = self.gibGroesseMitMittelpunkt()
            dimx = mpkt.x+breite*0.5
            dimy = mpkt.y+hoehe*0.5
            retstr = SVGHeader(dimx,dimy) + "\n"
            try:
                for obj in objliste:
                    retstr += obj.gibSVGCode()
            except AttributeError:
                raise Typfehler("Ein verwaltetes Objekt stellt keinen SVG-Export zur Verfuegung.")
            retstr += "</svg>\n"
        else:
            retstr = GrafikGruppe.gibSVGCode(self)
        return retstr
    

# ===============================================
# Klassen zur Beruecksichtung von Stilattributen
# ===============================================
class Linien(object):
    """
    Speichert Daten fuer Linienfarbe und -staerke eines Grafikobjektes.
    Die Linienstaerke ist dabei eine dimensionslos Fliesskommazahl,
    eventuell kann man sich in einer spaeteren Version auf eine
    bestimmte Konvention einigen, so dass die Interpretation der Zahl
    auf immer gleiche Art und Weise gewaehrleistet ist.
    
    TODO: Diskussion, ob das verwendete Farbobjekt
          Klassenintern als Kopie gehandhabt werden sollte.
          Je nach Perspektive scheint das eine oder andere sinnvoller.
    """
    def __init__(self):
        if debug: print str(self) + " __init__ (Linien)"
        self.__linienstaerke = 0
        self.__linienfarbe = Farbe()
    
    def gibLinienstaerke(self):
        """Gibt die Linienstaerke der Umrandung des Grafikobjektes zurueck."""
        return self.__linienstaerke
    
    def gibLinienfarbe(self):
        """Gibt die Linienfarbe der Umrandung des Grafikobjektes zurueck."""
        return self.__linienfarbe
    
    def setzeLinienstaerke(self,staerke):
        """
        Setzt die Linienstaerke der Umrandung des Grafikobjektes.
        Negative Werte sind nicht erlaubt und erzeugen einen Wertefehler.
        """
        if debug: print str(self) + " setzeLinienstaerke "+ str(staerke)
        staerke = float(staerke)
        if staerke<0:
            raise Wertefehler("Eine Negative Linienstaerke ist nicht erlaubt.")
        self.__linienstaerke = staerke
        try:
            self.geaendert()
        except AttributeError:
            pass
    
    def setzeLinienfarbe(self,farbe):
        self.__linienfarbe = copy(farbe)
        # mit dem folgenden Try-Konstrukt koennen auch Objekte,
        # die keine geaendert()-Methode haben, die Linienattribute
        # besitzen.
        try:
            self.geaendert()
        except AttributeError:
            pass
    
    linienstaerke = property(fget=gibLinienstaerke,fset=setzeLinienstaerke,doc="Linienstaerke der Umrandung des Grafikobjektes.")
    linienfarbe = property(fget=gibLinienfarbe,fset=setzeLinienfarbe,doc="Linienfarbe der Umrandung des Grafikobjektes.")
    
    def _gibObjektkartenDictionary(self):
        if self.__linienfarbe is None:
            lf = "Keine"
        else:
            lf = str(self.__linienfarbe)
        return {"Linienstaerke":str(self.__linienstaerke), "Linienfarbe":lf}
    
    def _SVGImport(self,elem):
        """
        Die Methode importiert Style-Daten zur Linienstaerke
        und Linienfarbe unter Benutzung der Methode SVGStyleExtract.
        """
        styledict = SVGStyleExtract(elem)
        if 'stroke-width' in styledict:
            self.linienstaerke = float(re.findall('[\+\-.\d]*\d',styledict['stroke-width'])[0])
        else:
            self.linienstaerke = 1
            # TODO:
            # Hier globalen standardwert benutzen.
        self.linienfarbe = schwarz
        # TODO:
        # Hier globalen standardwert benutzen.
        if 'stroke' in styledict:
            self.linienfarbe.setzeSVGFarbe(styledict['stroke'])
    

class Fuellung(object):
    """
    Speichert Daten fuer die Fuellfarbe Grafikobjektes.
    TODO: Diskussion, ob das verwendete Farbobjekt
          Klassenintern als Kopie gehandhabt werden sollte.
          Je nach Perspektive scheint das eine oder andere sinnvoller.
    """
    
    def __init__(self):
        if debug: print str(self) + " __init__ (Fuellung)"
        self.__fuellfarbe = Farbe()
    
    def gibFuellfarbe(self):
        """Gibt die Fuellfarbe des Grafikobjektes zurueck."""
        return self.__fuellfarbe
    
    def setzeFuellfarbe(self,farbe):
        """Setzt die Fuellfarbe des Grafikobjektes."""
        self.__fuellfarbe = copy(farbe)
        # mit dem folgenden Try-Konstrukt koennen auch Objekte,
        # die keine geaendert()-Methode haben, die Linienattribute
        # besitzen
        try:
            self.geaendert()
        except AttributeError:
            pass
    
    fuellfarbe = property(fget=gibFuellfarbe,fset=setzeFuellfarbe,doc="Fuellfarbe des Grafikobjektes.")   
    
    def _gibObjektkartenDictionary(self):
        if self.__fuellfarbe is None:
            ff = "Keine"
        else:
            ff = str(self.__fuellfarbe)
        return {"Fuellfarbe":ff}
    
    def _SVGImport(self,elem):
        """
        Die Methode importiert Style-Daten zur Fuellfarbe
        unter Benutzung der Methode SVGStyleExtract.
        """
        styledict = SVGStyleExtract(elem)
        self.fuellfarbe = schwarz
        # TODO:
        # Hier globalen standardwert benutzen.
        if 'fill' in styledict:
            if styledict['fill'] == 'none':
                self.fuellfarbe = None
            else:
                self.fuellfarbe.setzeSVGFarbe(styledict['fill'])    

# ===========================
# Grafikprimitivklassen
# ===========================
class Grafik(object):
    """
    Grafikobjekte sind Objekte mit
    Methoden zur Grafikmanipulation.
    
    Vorgaben zum Koordinatensystem:
    Der Ursprung des Koordinatensystems ist links oben. Die
    positive x-Achse zeigt nach rechts, die positive y-Achse
    nach unten. Negative Zahlenwerte als Koordinaten sind
    grundsaetzlich nicht vorgesehen.
    Zur Darstellung von Koordinaten wird die Klasse 'Punkt2D'
    verwendet.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Grafik)"
        self.__mittelpunkt = Punkt2D()
        self.__drehwinkel = 0
    
    def gibMittelpunkt(self):
        """
        Gibt den Mittelpunkt des Grafikobjektes zurueck.
        Der Mittelpunkt wird dabei immer in absoluten Koordinaten angegeben.
        """
        return copy(self.__mittelpunkt)
    
    def setzeMittelpunkt(self,mpkt):
        """
        Setzt den neuen Mittelpunkt des Grafikobjektes.
        Der Mittelpunkt wird dabei immer in absoluten Koordinaten angegeben.
        """
        if debug: print str(self) + ": setzeMittelpunkt " + str(mpkt)
        try:
            m = Punkt2D(mpkt[0],mpkt[1])
        except TypeError:
            raise Typfehler("Kann neuen Mittelpunkt nicht als Punkt2D interpretieren")
        self.__mittelpunkt = m
        self.geaendert()
    
    def verschiebeUm(self,vvec):
        """
        Verschiebt das Grafikobjekt um um den Verschiebevektor vvec.
        Dieser wird als Objekt der Klasse Punkt2D erwartet.
        """
        if debug: print str(self) + ": verschiebe " + str(vvec)
        try:
            self.__mittelpunkt = self.__mittelpunkt+vvec
        except Typfehler:
            raise Typfehler("Der Parameter konnte nicht als Punkt2D interpretiert werden.")
        self.geaendert()
    
    def verschiebeBis(self,pkt):
        """
        Verschiebt das Grafikobjekt, bis der Mittelpunkt 'pkt' entspricht
        Dies ist aequivalent zu 'setzeMittelpunkt(pkt)'.
        """
        self.setzeMittelpunkt(pkt)
        # geaendert() wird in der 'setzeMittelpunkt' Methode aufgerufen.
    
    mittelpunkt=property(fset=setzeMittelpunkt,fget=gibMittelpunkt,doc="Koordinaten des Mittelpunktes des Grafikobjektes")
    
    def gibDrehwinkel(self):
        """
        Gibt den Winkel zurueck, um den das Grafikobjekt aus dem
        Ursprungszustand gedreht wurde. Die Einheit der
        Winkelgroesse ist das Gradmass.
        """
        return self.__drehwinkel
    
    def setzeDrehwinkel(self,neuerwinkel):
        """
        Setzt den Winkel, um den das Grafikobjekt aus dem
        Ursprungszustand gedreht sein soll. Die Einheit der
        Winkelgroesse ist das Gradmass.
        Negative Winkel werden in den entsprechenden positiven
        Wert umgewandelt. Positive Winkel werden automatisch
        umgewandelt, so dass die Bedingung
        0 <= winkel < 360 erfuellt ist.
        """
        while (neuerwinkel < 0):
            neuerwinkel+=360
        while (neuerwinkel>360):
            neuerwinkel-=360
        # das geht natuerlich auch schneller, aber
        # es klappt zumindestens
        self.__drehwinkel = neuerwinkel
        self.geaendert()
        if debug: print "setzeDrehwinkel interpretiert den Winkel " + str(neuerwinkel)
    
    def dreheUm(self,winkel,drehzentrum=None):
        """
        Dreht das Objekt um den angegebenen Winkel und um das
        angegebene Drehzentrum. Wird kein Drehzentrum angegeben,
        wird der Mittelpunkt als Drehzentrum genutzt.
        Die Einheit der Winkelgroesse ist das Gradmass.
        """
        if drehzentrum is not None:
            self.__mittelpunkt = drehePunkt(self.mittelpunkt,drehzentrum,winkel)
        self.drehwinkel = self.__drehwinkel + winkel
        # impliziter Aufruf von setzeDrehwinkel, der neue Winkel
        # wird also auch in entsprechende Form gebracht.
        # geaendert() wird in der 'setzeDrehwinkel' Methode aufgerufen
    
    drehwinkel=property(fset=setzeDrehwinkel,fget=gibDrehwinkel,doc="Drehwinkel im Gradmass")    
        
    def _gibObjektkartenDictionary(self):
        return {"Mittelpunkt":str(self.__mittelpunkt), "Drehwinkel":str(self.__drehwinkel)}
    

class Rechteck(Grafik,Linien,Fuellung,EindeutigesObjekt):
    """
    Klasse zur Erzeugung von Rechtecken.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Rechteck)"
        EindeutigesObjekt.__init__(self,name)
        Grafik.__init__(self)
        Linien.__init__(self)
        Fuellung.__init__(self)
        self.__breite = 0
        self.__hoehe = 0
    
    
    def gibBreite(self):
        """Gibt die Breite des Rechteckes zurueck."""
        return self.__breite
    
    def gibHoehe(self):
        """Gibt die Hoehe des Rechteckes zurueck."""
        return self.__hoehe
    
    def setzeBreite(self,breite):
        """Setzt die Breite des Rechtecks. Ein negativer Wert erzeugt eine Wertefehler-Ausnahme."""
        b = float(breite)
        if (b<0):
            raise Wertefehler("Breite darf nicht negativ sein.")
        self.__breite = b
        self.geaendert()
    
    def setzeHoehe(self,hoehe):
        """Setzt die Hoehe des Rechtecks. Ein negativer Wert erzeugt eine Wertefehler-Ausnahme."""
        h = float(hoehe)
        if (h<0):
            raise Wertefehler("Hoehe darf nicht negativ sein.")
        self.__hoehe = h
        self.geaendert()
    
    breite = property(fset=setzeBreite,fget=gibBreite,doc="Die Breite des Rechteckes")
    hoehe = property(fset=setzeHoehe,fget=gibHoehe,doc="Die Hoehe des Rechteckes")
    
    def gibGroesse(self):
        """
        Es wird ein Rechteck berechnet, welches welche das Grafikobjekt vollstaendig
        einschliesst. Die Masse dieses Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
        """
        epkt = self.gibEckpunkte()
        maxx = max([p.x for p in epkt])
        maxy = max([p.y for p in epkt])
        minx = min([p.x for p in epkt])
        miny = min([p.y for p in epkt])
        return (maxx-minx,maxy-miny)
    
    def gibEckpunkte(self):
        """
        Gibt Liste der Eckpunkte im Uhrzeigersinn zurueck.
        """
        m = self.mittelpunkt
        bh = self.__breite*0.5
        hh = self.__hoehe*0.5
        w = self.drehwinkel
        # Liste mit Eckpunkten erstellen
        epl = [m+(bh,hh),m+(-bh,hh),m+(-bh,-hh),m+(bh,-hh)]
        # Ecken um Mittelpunkt drehen
        return [drehePunkt(p, m, w) for p in epl]
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        self.__breite*=f
        self.__hoehe*=f
        self.geaendert()
    
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Rechteck",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        retdict.update(Fuellung._gibObjektkartenDictionary(self))
        retdict.update({"Breite":str(self.__breite), "Hoehe":str(self.__hoehe)})
        return retdict
    
    def gibSVGCode(self):
        x,y = self.mittelpunkt
        b,h = self.breite, self.hoehe
        w = self.drehwinkel
        if self.fuellfarbe is not None:
            ff = self.fuellfarbe.gibSVGFarbe()
        else:
            ff = "none"
        ls = self.linienstaerke
        if self.linienfarbe is not None:
            lf = self.linienfarbe.gibSVGFarbe()
        else:
            lf = "none"
        rstr =  "<rect id=\"%s\" x=\"%f\" y=\"%f\" width=\"%f\" height=\"%f\"\n"
        if w is not 0:
            rstr +="      transform=\"rotate(%f,%f,%f)\"\n"
        rstr += "      fill=\"%s\" stroke=\"%s\" stroke-width=\"%f\"/>\n"
        if w is not 0:
            rstr = rstr % (self.name,x-b*0.5, y-h*0.5,b,h,w,x,y,ff,lf,ls)
        else:
            rstr = rstr % (self.name,x-b*0.5, y-h*0.5,b,h,ff,lf,ls)
        return rstr
    
    def _SVGImport(self,elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Rechteck
        Spezifikation.
        """
        # Allgeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        Fuellung._SVGImport(self,elem)
        # Spezielle Attribut des 'rect'-Elements
        self.breite = float(elem.get('width'))
        self.hoehe = float(elem.get('height'))
        mx = float(elem.get('x'))+self.breite*0.5
        my = float(elem.get('y'))+self.hoehe*0.5
        self.mittelpunkt = Punkt2D(mx,my)
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    
    
    def gibGrafikprimitivname(self):
        """
        Anhand des Rueckgabewertes kann ein Grafikzeichner
        die Grafikprimitive auseinanderhalten.
        Damit wird die Interpretation des Klassennamens
        umgangen.
        """
        return "Rechteck"
    

class Kreis(Grafik,Linien,Fuellung,EindeutigesObjekt):
    """
    Klasse zur Erzeugung von Kreisen.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Kreis)"
        EindeutigesObjekt.__init__(self,name)
        Grafik.__init__(self)
        Linien.__init__(self)
        Fuellung.__init__(self)
        self.__mittelpunkt = Punkt2D()
        self.__radius = 0
    
    
    def gibRadius(self):
        """Gibt den Radius des Kreises zurueck."""
        return self.__radius
    
    def setzeRadius(self,radius):
        """Setzt den Radius des Kreises. Ein negativer Wert erzeugt eine Wertefehler-Ausnahme."""
        r = float(radius)
        if (r<0):
            raise Wertefehler("Radius darf nicht negativ sein.")
        self.__radius = r
        self.geaendert()
    
    radius = property(fset=setzeRadius,fget=gibRadius,doc="Der Radius des Kreises")
    
    def gibGroesse(self):
        """
        Es wird ein Rechteck berechnet, welches welche das Grafikobjekt vollstaendig
        einschliesst. Die Masse dieses Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
        """
        return (self.__radius*2,self.__radius*2)
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        self.__radius*=f
        self.geaendert()
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Kreis",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        retdict.update(Fuellung._gibObjektkartenDictionary(self))
        retdict.update({"Radius":str(self.__radius)})
        return retdict
    
    
    def gibSVGCode(self):
        x,y = self.mittelpunkt
        r = self.radius
        if self.fuellfarbe is not None:
            ff = self.fuellfarbe.gibSVGFarbe()
        else:
            ff = "none"
        if self.linienfarbe is not None:
            lf = self.linienfarbe.gibSVGFarbe()
        else:
            lf = "none"
        ls = self.linienstaerke
        rstr = "<circle id=\"%s\" cx=\"%f\" cy=\"%f\" r=\"%f\"\n"\
              +"        fill=\"%s\" stroke=\"%s\" stroke-width=\"%f\"/>\n"
        rstr = rstr % (self.name,x, y, r, ff, lf, ls)
        return rstr
    
    def _SVGImport(elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Kreis
        Spezifikation.
        """
        # Allgeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        Fuellung._SVGImport(self,elem)
        # Spezielle Attribut des 'circle'-Elements
        mx = float(elem.get('cx'))
        my = float(elem.get('cy'))
        mittelpunkt = Punkt2D(mx,my)
        radius = float(elem.get('r'))
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    

class Ellipse(Grafik,Linien,Fuellung,EindeutigesObjekt):
    """
    Klasse zur Erzeugung von Ellipsen.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Ellipse)"
        EindeutigesObjekt.__init__(self,name)
        Grafik.__init__(self)
        Linien.__init__(self)
        Fuellung.__init__(self)
        self.__radiusx = 0
        self.__radiusy = 0
    
    
    def gibRadiusX(self):
        """Gibt den Breitenradius der Ellipse zurueck."""
        return self.__radiusx
    
    def gibRadiusY(self):
        """Gibt den Hoehenradius des Kreises zurueck."""
        return self.__radiusy
    
    def setzeRadiusX(self,radiusx):
        """Setzt den Breitenradius der Ellipse. Ein negativer Wert erzeugt eine Wertefehler-Ausnahme."""
        rx = float(radiusx)
        if (rx<0):
            raise Wertefehler("Radius darf nicht negativ sein.")
        self.__radiusx = rx
        self.geaendert()
    
    def setzeRadiusY(self,radiusy):
        """Setzt den Hoehenradius der Ellipse. Ein negativer Wert erzeugt eine Wertefehler-Ausnahme."""
        ry = float(radiusy)
        if (ry<0):
            raise Wertefehler("Radius darf nicht negativ sein.")
        self.__radiusy = ry
        self.geaendert()
    
    radiusx = property(fset=setzeRadiusX,fget=gibRadiusX,doc="Der Breitenradius der Ellipse")
    radiusy = property(fset=setzeRadiusY,fget=gibRadiusY,doc="Der Hoehenradius der Ellipse")
    
    def gibGroesse(self):
        """
        Es wird ein Rechteck berechnet, welches welche das Grafikobjekt vollstaendig
        einschliesst. Die Masse dieses Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
        Aktuell schliesst das Rechteck den Kreis mit Radius max(radiusx,radiuy)ein.
        Genauere Einschliessung vielleicht in einer spaeteren Version.
        """
        dimmax = max(self.__radiusx,self.__radiusy)
        return (dimmax*2,dimmax*2)
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        self.__radiusx*=f
        self.__radiusy*=f
        self.geaendert()
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Ellipse",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        retdict.update(Fuellung._gibObjektkartenDictionary(self))
        retdict.update({"RadiusX":str(self.__radiusx), "RadiusY":str(self.__radiusy)})
        return retdict
    
    
    def gibSVGCode(self):
        x,y = self.mittelpunkt
        rx,ry = self.radiusx, self.radiusy
        w = self.drehwinkel
        if self.fuellfarbe is not None:
            ff = self.fuellfarbe.gibSVGFarbe()
        else:
            ff = "none"
        ls = self.linienstaerke
        if self.linienfarbe is not None:
            lf = self.linienfarbe.gibSVGFarbe()
        else:
            lf = "none"
        rstr =  "<ellipse id=\"%s\" cx=\"%f\" cy=\"%f\" rx=\"%f\" ry=\"%f\"\n"
        if w is not 0:
            rstr +="        transform=\"rotate(%f,%f,%f)\"\n"
        rstr +=  "         fill=\"%s\" stroke=\"%s\" stroke-width=\"%f\"/>\n"
        if w is not 0:
            rstr = rstr % (self.name,x, y, rx, ry, w, x, y,ff,lf,ls)
        else:
            rstr = rstr % (self.name,x, y, rx, ry,ff,lf,ls)
        return rstr
    
    def _SVGImport(elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Ellipsen-
        Spezifikation.
        """
        # Allgeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        Fuellung._SVGImport(self,elem)
        # Spezielle Attribut des 'ellipse'-Elements
        mx = float(elem.get('cx'))
        my = float(elem.get('cy'))
        mittelpunkt = Punkt2D(mx,my)
        radiusx = float(elem.get('rx'))
        radiusy = float(elem.get('ry'))
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    

class Polygonzug(Grafik,Linien,EindeutigesObjekt):
    """
    Klasse zur Erzeugung von Polygonzuegen.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Polygonzug)"
        Grafik.__init__(self)
        EindeutigesObjekt.__init__(self,name)
        Linien.__init__(self)
        self.__punkteliste = [ ]
    
    def gibPunkteliste(self):
        """
        Es wird eine Kopie der Punkteliste des Polygonzugs zurueckgegeben.
        
        Sind keine Punkte festgelegt, so wird eine leere Liste zurueckgegeben.
        
        Dadurch, dass eine tiefe Kopie zurueckgegeben wird, kann sowohl die
        Punkteliste auf diese Art und Weise nicht geaendert werden als auch
        die Koordinaten der einzelnen Punkte nicht, da nur auf einer Kopie
        gearbeitet wird.
        """
        if debug: print str(self) + " gibPunkteliste"
        # Punkte liegen als relative Koordinaten vor, daher muss erst
        # eine Punkteliste mit absoluten Koordinaten erzeugt werden.
        mpkt = self.mittelpunkt
        abslist = [ ]
        for pkt in self.__punkteliste:
            abslist.append(mpkt+pkt)
        
        return [drehePunkt(p,mpkt,self.drehwinkel) for p in abslist]
    
    def setzePunkteliste(self,pktliste):
        """
        Setzt eine neue Punkteliste.
        Die uebergebene Liste wird als Punkt2D interpretiert
        (und dabei kopiert). Ist die Interpretation nicht moeglich,
        so wird eine Typfehler-Ausnahme erzeugt.
        
        Eine neue Punkteliste setzt automatisch den Mittelpunkt auf
        die Mitte des alle Punkte umschreibenden Rechteckes und den
        Drehwinkel auf 0.
        """
        # Temporaere Punkteliste aufbauen, Punkte werden dabei mit
        # absoluten Koordinaten interpretiert.
        tmpliste = [ ]
        for pkt in pktliste:
            try:
                npkt = Punkt2D(pkt.x,pkt.y)
            except AttributeError:
                try:
                    npkt = Punkt2D(pkt[0],pkt[1])
                except TypeError:
                    raise Typfehler("Kann Listeneintrag nicht als Punkt2D interpretieren.")
            tmpliste.append(npkt)
        
        # Berechnung und Setzen des Mittelpunktes
        mpkt, breite, hoehe = berechneRechteckUmPunkte(tmpliste)
        self.mittelpunkt = mpkt
        if debug: print "Mittelpunkt: " + str(mpkt)
        self.drehwinkel = 0
        
        # Nun ist der Mittelpunkt bekannt, die Koordinaten werden
        # nun zu relativen Koordinaten umgerechnet.
        relliste = [ ]
        for pkt in tmpliste:
            relliste.append(pkt-mpkt)
            if debug: print "Absolut als " + str(pkt) + " realtiv als " + str(pkt-mpkt)
        
        self.__punkteliste = relliste
        self.geaendert()
    
    def gibGroesse(self):
        """
        Es wird ein Rechteck berechnet, welches welche das Grafikobjekt vollstaendig
        einschliesst. Die Masse dieses Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
        """
        punkte = self.gibPunkteliste()
        if punkte == []:
            return (0,0)
        maxx = max([p.x for p in punkte])
        maxy = max([p.y for p in punkte])
        minx = min([p.x for p in punkte])
        miny = min([p.y for p in punkte])
        return (maxx-minx,maxy-miny)
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        mpkt = self.mittelpunkt
        self.__punkteliste = [f*pkt for pkt in self.__punkteliste]
        if self.__punkteliste != []:
            self.geaendert()
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Polygonzug",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        
        pktliste = self.gibPunkteliste()
        anzpkt = len(pktliste)
        if anzpkt==0:
            pktstr = ["Keine"]
        elif anzpkt==1:
            pktstr = [str(pktliste[0])]
        else:
            pktstr = [str(pktliste[0])]
            for pkt in pktliste[1:]:
                pktstr.append(str(pkt))
        
        retdict.update({"Punkte (gedreht)":pktstr})
        return retdict
    
    def _SVGImport(self,elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Polygonzug-
        Spezifikation.
        """
        # Allgeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        # Spezielle Attribut des 'polyline'-Elements
        plist = elem.get('points')
        # Split an Komma und Leerzeichen
        plist = re.split(r"[ ,]+",plist)
        punkteliste = [ ]
        for i in range(len(plist)/2):
            punkteliste.append(Punkt2D(float(plist[2*i]),float(plist[2*+1])))
        self.setzePunkteliste(punkteliste)
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    
    def gibSVGCode(self):
        pl = self.gibPunkteliste()
        if len(pl) < 2:
            rstr = ""
        else:
            if self.linienfarbe is not None:
                lf = self.linienfarbe.gibSVGFarbe()
            else:
                lf = "none"
            rstr = "<polyline id=\"%s\" fill=\"none\" stroke=\"%s\" stroke-width=\"%s\" points=\"\n"\
                    % (self.name,lf,self.linienstaerke)
            anz = 0
            for (x, y) in pl:
                anz = anz +1
                rstr += " %f,%f " % (x, y) 
                if anz%10 is 0:
                    rstr += "\n"
            rstr += "\" />\n"
        return rstr
    

class Polygon(Polygonzug,Fuellung):
    """
    Klasse zur Erzeugung von Polygonen.
    Der einzige Unterschied zur Klasse 'Polygonzug'
    sind die zusaetzlichen Moeglichkeiten 
    zur Festlegung einer Fuellfarbe.
    
    Im Vergleich zum Polygonzug ist ein Polygon geschlossen.
    Die Kante vom letzten Punkt wieder zurueck zum Anfangspunkt
    ist implizit enthalten und muss nicht durch zusaetzliches
    Hinzufuegen des Anfangspunktes geschehen.
    Die 'gibPunkteliste'-Methode gibt daher den Anfangspunkt
    nicht zusaetzlich als letzten Punkt mit an.
    (Ausser, man setzt mit Hilfe der "setzePunkteliste"
    manuell den Anfangspunkt auch als Endpunkt, dies ist
    aber nur in Ausnahmefaellen sinnvoll.)
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Polygon)"
        Polygonzug.__init__(self,name)
        Fuellung.__init__(self)
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Polygon",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = Polygonzug._gibObjektkartenDictionary(self)
        retdict.update(Fuellung._gibObjektkartenDictionary(self))
        return retdict
    
    def _SVGImport(self,elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Polygon-
        Spezifikation.
        """
        Fuellung._SVGImport(self,elem)
        Polygonzug._SVGImport(self,elem)
    
    def gibSVGCode(self):
        pl = self.gibPunkteliste()
        if len(pl) < 2:
            rstr = ""
        else:
            if self.linienfarbe is not None:
                lf = self.linienfarbe.gibSVGFarbe()
            else:
                lf = "none"
            rstr = "<polygon id=\"%s\" fill=\"none\" stroke=\"%s\" stroke-width=\"%s\" points=\"\n"\
                    % (self.name,lf,self.linienstaerke)
            anz = 0
            for (x, y) in pl:
                anz = anz +1
                rstr += " %f,%f " % (x, y) 
                if anz%10 is 0:
                    rstr += "\n"
            rstr += "\" />\n"
        return rstr
    

class Bezierkurve(Polygonzug,Linien):
    """
    Klasse zur Erzeugung von Bezierkurven. Die Klasse ist
    nicht beschraenkt auf quadratische oder kubische
    Bezierkurven, da analog zur Polygonzug-Klasse
    eine beliebig lange Punkteliste zugewiesen werden kann.
    Zur Zeichnung einer Bezierkurve ohne entsprechende
    Kurvenroutinen (bei SVG sind z.B. quadratische und
    kubische Kurven durch Angabe entsprechender Codes
    in der Pfadbeschreibung moeglich, hoehere Kurven
    jedoch nicht.) gibt es die Methode 'gibNaeherungspunkteliste',
    welche auf der Basis des casteljau-Algorithmus eine
    Naeherung der Kurve berechnet. Die Rekursiontiefe
    kann dabei angegeben werden.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Bezierkurve)"
        Polygonzug.__init__(self,name)
    
    def gibNaeherungspunkteliste(self,rekursionstiefe=5):
        """
        Nutzt den Casteljau-Algorithmus zur Erzeugung
        eines Polygonzuges, der die Bezierkurve annaehert.
        Die Genauigkeit ist indirekt durch die Rekursionstiefe
        bestimmt.
        """
        liste = self.gibPunkteliste()
        approxlist = [ ]
        if len(liste)>0: 
            approxlist = deCasteljauRek(liste, rekursionstiefe)
            approxlist.append(liste[len(liste) - 1])
            approxlist.insert(0, liste[0])
        return approxlist
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Bezierkurve",mdict)
    
    def gibSVGCode(self):
        pl = self.gibNaeherungspunkteliste()
        if len(pl) < 2:
            rstr = ""
        # TODO: Spezielle Pfade fuer quadratische und kubische Kurven
        #       erstellen.
        else:
            if self.linienfarbe is not None:
                lf = self.linienfarbe.gibSVGFarbe()
            else:
                lf = "none"
            rstr = "<polyline id=\"%s\" fill=\"none\" stroke=\"%s\" stroke-width=\"%s\" points=\"\n"\
                    % (self.name,lf,self.linienstaerke)
            anz = 0
            for (x, y) in pl:
                anz = anz +1
                rstr += " %f,%f " % (x, y) 
                if anz%10 is 0:
                    rstr += "\n"
            rstr += "\" />\n"
        return rstr
    

class Linie(Grafik,EindeutigesObjekt,Linien):
    """
    Klasse zur Erzeugung von Linien.
    
    Implementierungshinweis:
    Linienklasse ist leicht modifizierte Polygonzugklasse.
    Moeglichkeiten zur Optimierung bestimmt vorhanden.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Linie)"
        Grafik.__init__(self)
        EindeutigesObjekt.__init__(self,name)
        Linien.__init__(self)
        self.__punkteliste = [ ]
    
    def gibPunkte(self):
        """
        Anfangs- und Endpunkt der Linie werden als Punkt2D-Tupel
        zurueckgegeben. Sind keine Punkte festgelegt, wird eine
        leere Liste zurueckgegeben.
        """
        if debug: print str(self) + " gibPunkte"
        # Punkte liegen als relative Koordinaten vor, daher muss erst
        # eine Punkteliste mit absoluten Koordinaten erzeugt werden.
        mpkt = self.mittelpunkt
        abslist = [ ]
        for pkt in self.__punkteliste:
            abslist.append(mpkt+pkt)
        
        return [drehePunkt(p,mpkt,self.drehwinkel) for p in abslist]
    
    def setzePunkte(self,anfang,ende):
        """
        Setzt Anfangs- und Enpunkt der Linie neu.
        Die uebergebenen Punkte werden als Punkt2D interpretiert
        (und dabei kopiert). Ist die Interpretation nicht moeglich,
        so wird eine Typfehler-Ausnahme erzeugt.
        
        Mit dem Setzen neuer Punkte wird automatisch der Mittelpunkt auf
        die Mitte des Linie und der Drehwinkel auf 0 gesetzt.
        """
        # Temporaere Punkteliste aufbauen, Punkte werden dabei mit
        # absoluten Koordinaten interpretiert.
        tmpliste = [ ]
        try:
            anf = Punkt2D(anfang[0],anfang[1])
        except TypeError:
            raise Typfehler("Kann Eintrag nicht als Punkt2D interpretieren.")
        tmpliste.append(anf)
        try:
            end = Punkt2D(ende[0],ende[1])
        except TypeError:
            raise Typfehler("Kann Eintrag nicht als Punkt2D interpretieren.")
        tmpliste.append(end)
        
        # Berechnung und Setzen des Mittelpunktes
        mpkt = berechneRechteckUmPunkte(tmpliste)[0]
        self.mittelpunkt = mpkt
        if debug: print "Mittelpunkt: " + str(mpkt)
        self.drehwinkel = 0
        
        # Nun ist der Mittelpunkt bekannt, die Koordinaten werden
        # nun zu relativen Koordinaten umgerechnet.
        relliste = [ ]
        for pkt in tmpliste:
            relliste.append(pkt-mpkt)
            if debug: print "Absolut als " + str(pkt) + " realtiv als " + str(pkt-mpkt)
        
        self.__punkteliste = relliste
        self.geaendert()
    
    def skaliereMitFaktor(self,faktor):
        """
        Es wird das Grafikobjekt mit dem positiven Faktor 'faktor'
        vergroessert (faktor>1) bzw. verkleinert (faktor<1).
        Es wird eine Wertefehler-Ausnahme erzeugt, falls 'faktor'
        negativ ist.
        """
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        mpkt = self.mittelpunkt
        self.__punkteliste = [f*pkt for pkt in self.__punkteliste]
        if self.__punkteliste != []:
            self.geaendert()
    
    def gibGroesse(self):
       """
       Es wird ein Rechteck berechnet, welches welche das Grafikobjekt vollstaendig
       einschliesst. Die Masse dieses Rechteckes werden als 2-Tupel '(breite,hoehe)' zurueckgegeben.
       """
       try:
           (p1,p2) = self.gibPunkte()
       except TypeError:
           return (0,0)
       maxx = max(p1.x,p2.x)
       maxy = max(p1.y,p2.y)
       minx = min(p1.x,p2.x)
       miny = min(p1.y,p2.y)
       return (maxx-minx,maxy-miny)
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Linie",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        
        pktliste = self.gibPunkte()
        anzpkt = len(pktliste)
        if anzpkt==0:
            anfang = "Keiner"
            ende = "Keiner"
        else:
            anfang = str(pktliste[0])
            ende   = str(pktliste[1])
        
        retdict.update({"Anfangspunkt":anfang,"Endpunkt":ende})
        return retdict
    
    def gibSVGCode(self):
        if self.linienfarbe is not None:
            lf = self.linienfarbe.gibSVGFarbe()
        else:
            lf = "none"
        ls = self.linienstaerke
        return "<line id=\"%s\" stroke=\"%s\" stroke-width=\"%f\" x1=\"%f\" y1=\"%f\" x2=\"%f\" y2=\"%f\" />"\
            % (self.name,svg_color(self.linienfarbe),self.linienstaerke,x1, y1, x2, y2)
    
    def _SVGImport(self,elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Ellipsen-
        Spezifikation.
        """
        # Allgeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        Fuellung._SVGImport(self,elem)
        # Spezielle Attribut des 'ellipse'-Elements
        x1 = float(elem.get('x1'))
        y1 = float(elem.get('y1'))
        x2 = float(elem.get('x2'))
        y2 = float(elem.get('y2'))
        self.setzePunkte(Punkt2D(x1,y1),Punkt2D(x2,y2))
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    

class Text(Grafik,EindeutigesObjekt,Linien,Fuellung):
    """
    Klasse zur Realisierung von Textdarstellungen.
    Der Schriftgrad ist dabei eine dimensionslos Fliesskommazahl,
    eventuell kann man sich in einer spaeteren Version auf eine
    bestimmte Konvention einigen, so dass die Interpretation der Zahl
    auf immer gleiche Art und Weise gewaehrleistet ist.
    """
    def __init__(self,name=None):
        if debug: print str(self) + " __init__ (Text)"
        EindeutigesObjekt.__init__(self,name)
        Grafik.__init__(self)
        Linien.__init__(self)
        Fuellung.__init__(self)
        self.__text = ""
        self.__grad = float(10)
    
    def setzeText(self,txt):
        self.__text = str(txt)
        self.geaendert()
    
    def gibText(self):
        return self.__text
    
    text = property(fset=setzeText,fget=gibText,doc="Darzustellender Text.")
    def setzeSchriftgrad(self,grad):
        self.__grad = float(grad)
        self.geaendert()
    
    def gibSchriftgrad(self):
        return self.__grad
    
    schriftgrad = property(fset=setzeSchriftgrad,fget=gibSchriftgrad,doc="Schriftgrad des darzustellenden Textes.")
    
    def gibObjektkartenText(self):
        """
        Gibt eine Objektkarte in Textform zurueck.
        Diese kann print ausgegeben oder anderweitig
        genutzt werden.
        """
        mdict = self._gibObjektkartenDictionary()
        return formatiereObjektkartenDictionary(self.name,"Text",mdict)
    
    def _gibObjektkartenDictionary(self):
        retdict = EindeutigesObjekt._gibObjektkartenDictionary(self)
        retdict.update(Grafik._gibObjektkartenDictionary(self))
        retdict.update(Linien._gibObjektkartenDictionary(self))
        retdict.update(Fuellung._gibObjektkartenDictionary(self))
        retdict.update({"Text":self.__text,"Schriftgrad":self.__grad})
        return retdict
    
    def skaliereMitFaktor(self,faktor):
        f = float(faktor)
        if (f<0):
            raise Wertefehler("Eine Skalierung mit negativem Faktor ist nicht erlaubt.")
        self.__schriftgrad *= f
        self.geaendert()
    
    def gibGroesse(self):
        """
        Die Masse eines Textes zu berechnen ist nicht ganz einfach.
        Eventuell wird in einer zukuenftigen Version eine
        verbesserte Berechnung genutzt. Vorschlaege sind willkommen.
        Hier nur eine Behelfsloesung implementiert.
        """
        bh = (len(self.text) * self.schriftgrad * 0.5) *0.5
        hh = (self.schriftgrad)*0.5
        m = self.mittelpunkt
        w = self.drehwinkel
        # Liste mit Eckpunkten erstellen
        epl = [m+(bh,hh),m+(-bh,hh),m+(-bh,-hh),m+(bh,-hh)]
        # Ecken um Mittelpunkt drehen
        epkt = [drehePunkt(p, m, w) for p in epl]
        maxx = max([p.x for p in epkt])
        maxy = max([p.y for p in epkt])
        minx = min([p.x for p in epkt])
        miny = min([p.y for p in epkt])
        return (maxx-minx,maxy-miny)
    
    def gibSVGCode(self):
        """
        In SVG sind die Korrdinaten x und y standardmaessig nicht
        der Mittelpunkt des Textes, daher werden als Workaround
        zusaetzliche Attribute beigefuegt, die den Mittelpunkt
        verlegen. Mit
            text-anchar="middle"
        wird der Mittelpunkt horizontal zentriert, mit
            dominant-baseline="central" und
            baseline-shift="-30%"
        wird eine vertikale Verschiebung ungefaehr in die Mitte
        erreicht. Hier ist noch Verbesserungsbedarf, ...
        falls jemand eine gute Idee hat, E-Mail an mich.
        """
        x,y = self.mittelpunkt
        w = self.drehwinkel
        if self.fuellfarbe is not None:
            ff = self.fuellfarbe.gibSVGFarbe()
        else:
            ff = "none"
        if self.linienfarbe is not None:
            lf = self.linienfarbe.gibSVGFarbe()
        else:
            lf = "none"
        ls = self.linienstaerke
        rstr ='<text id="%s" x="%f" y="%f" fill="%s" stroke="%s" stroke-width="%f"\n'\
             +'      font-size="%f" text-anchor="middle" dominant-baseline="central"'
        if w is not 0:
            rstr +="      transform=\"rotate(%f,%f,%f)\"\n"
        if w is not 0:
            rstr = rstr % (self.name,x,y,ff,lf,ls,self.schriftgroesse,w,x,y)
        else:
            rstr = rstr % (self.name,x,y,ff,lf,ls,self.schriftgroesse)
        rstr += '      baseline-shift="-30%">'
        rstr += self.text+"</text>\n"
        return rstr
    
    def _SVGImport(self,elem):
        """
        Extrahiert aus (c)ElementTree-Element
        die notwendigen Attribute zur Kreis
        Spezifikation.
        """
        # Allgemeine Spezifikationen zu Fuellung
        # und Rahmen
        Linien._SVGImport(self,elem)
        Fuellung._SVGImport(self,elem)
        # Spezielle Attribut des 'text'-Elements
        x = float(elem.get('x'))
        y = float(elem.get('y'))
        text = elem.text
        schriftgrad = float(elem.get('font-size'))
        mittelpunkt = Punkt2D(x,y)
        # Interpretation der Transformationen
        SVGTransformationsImport(self,elem)
    


if __name__ == "__main__":
    dateiname='importtest.svg'
    obj = SVGDateiImport(dateiname)
    print obj.gibSVGCode(True)
    dateiname='exporttest.svg'
    export = open(dateiname,'w')
    export.write(obj.gibSVGCode(True))
