SVG Path: Kann man irgendwie ein Element einfach zwischendrin einfügen?

benneq

Fleet Admiral
Registriert
Juli 2010
Beiträge
12.620
Hallo,

ich sitz gerade vor GWT mit der GWT-graphics Library und erstelle ein Panel, das mit SVG Dinge darstellt (im Anhang ist ein Bild).
Im Prinzip sind's einfach nur Striche und an jedem Strich-Ende wird ein Kreis dargestellt. Die Kreise kann man per Drag and Drop bewegen. Soweit ist alles ganz einfach. Jetzt soll man aber auch (per Doppelklick) neue Kreise erstellen können... Und natürlich sollen dann die Linien angepasst werden.

Ich hab's auch schon implementiert, aber ich finde das sooo hässlich :D -->
Koordinaten vom Doppelklick abrufen, dann gucken zwischen welchen bisherigen Punkten ich gelandet bin. und dann den 'nächsten' Punkt auf die Position des Klicks setzen, den übernächsten auf die Position des vorherigen Punktes, etc. Also einfach alle einen zurückschieben und zum Schluss an die Stelle wo der letzte Punkt war einen neuen erstellen.

Viel einfacher wäre es, wenn ich mitten im Path einfach sagen könnte: Bau mir da noch einen Strich zwischen. Aber geht das irgendwie? Oder ist das per Spezifikation unmöglich?


Achja: Die Punkte lassen sich maximal(!) bis zum nächsten Punkt verschieben. Es ist also nicht möglich Punkt 1 zwischen Punkt 2 und Punkt 3 zu schieben. Ordnung muss sein ;)
 

Anhänge

  • Bildschirmfoto 2011-09-03 um 23.13.27.png
    Bildschirmfoto 2011-09-03 um 23.13.27.png
    21,8 KB · Aufrufe: 175
Ich hätte dafür nen RegEx (bzw. einfaches String-Replace) genommen - die bereits vorhandenen Punkte kennst du ja.
Dazwischen einfach nen neuen Punkt einfügen und die anderen Linien darauf "umbiegen" - fertig.
 
Hm... das wird dann wieder so ein gehacke :D
Ich denke, meine Kollegen werden mich killen, wenn ich mitten im objektorientierten Code anfange den DOM-Tree per Hand zu manipulieren ^^.

Also ist es vom W3C nicht vorgesehen, dass man das machen kann oder wie?


Achja. Das ist meine aktuelle Funktion zum Einfügen von Punkten:
Code:
public void addPoint(int x, int y) {
	// Check if new Point is inside Panel
	if (x > panelWidth || x < 0 || y > panelHeight || y < 0) {
		throw new IllegalArgumentException("Position must be inside panel!");
	}

	// If Path is empty: Set the beginning of the path exactly underneath
	// the Point to get a straight line upwards to the first Point
	if (list.size() == 0) {
		this.setX(x);
		super.lineTo(x, y);

	// If it's not the first one, check if the position of the new Point is between some others
	// If a Point is found that has a x-position which is greater than
	// the x-position of the new Point: Shift it!
	} else {
		int tmpX;
		int tmpY;
		for (ConnectedCircle circle : list) {
			if (circle.getX() > x) {
				tmpX = circle.getX();
				tmpY = circle.getY();
				setNewPosition(circle, x, y);
				x = tmpX;
				y = tmpY;
			}
		}

		// The former last line (which has connected the last Point with
		// bottom) now will connect the last 2 Points
		setStep(this.getStepCount() - 1, new LineTo(false, x, y));
	}

	// Create a new circle and insert it at the last position (because all
	// former circles have been shifted)
	Circle circle = new Circle(x, y, circleRadius, this.getStepCount() - 1);
	canvas.add(circle);
	new DragHandler(circle);
	list.add(circle);

	// draw line from the last Point to the bottom
	super.lineTo(x, maxHeight);
}

Noch zur Info: this bezieht sich auf den SVG Path: this.setX(int); setzt also quasi den Anfangs x-Wert der Zeichnung. Und super.lineTo(x, y); fügt am Ende vom Path eine Linie an
 
Zuletzt bearbeitet:
Mal dumm gefragt:
IRGENDWO in deinem Programm musst du doch ein "Verzeichnis" haben, über welches du die entsprechenden Linien ansprechen und verändern kannst.

Sortiere diese Elemente der X-Koordinate der Größe nach und schau, zwischen welche zwei Elemente dein neuer Punkt passt. Diese zwei Punkte entsprechend umbiegen, fertig.
 
Guck dir doch den Code da oben an...
Damit veränder ich die Punkte alle, damit es passt!

Es geht hier aber eher um die W3C Spezifikation: Pfuschen, aber mit Niveau ;) um genau zu sein: SVG Path.
Du beziehst dich (glaube ich) auf eine ganz normale SVG-Linie.

'normale' Linie = (startX, startY, endX, endY)
Pfad-Linie = (endX, endY)

Beim Pfad ist bekannt wo das letzt Element zu Ende ist und deshalb braucht man die 'Startwerte' der neuen Linie nicht. Ich kann also nicht einfach 2 Linien umbiegen. Ich müsste bei Beibehaltung der Reihenfolge meine neue Linie genau zwischen die beiden (im DOM) einfügen, wo sie hin soll.

Bsp: Pfad beginnt bei (0,0)
1. Linie nach (2,4)
2. Linie nach (8,3)

jetzt will ich eine 3. Einfügen und zwar ZWISCHEN 1 und 2. Dann müsste der DOM Baum danach so aussehen:

Pfad beginnt bei (0,0)
1. Linie nach (2,4)
2. Linie nach (5,6) <- Die neue
3. Linie nach (8,3)

Die 2. Pfad-Linie ist nun also die 3. geworden. Denn beim Pfad ist die Reihenfolge der Anweisungen entscheidend... Weiß nicht jemand, ob W3C es vorgesehen hat (oder auch nicht... und wenn ja wie), dass man mitten im Pfad was einfügen kann?


Bevor nun die Frage kommt, warum ich nicht 'normale' Linien mit Start und End-Punkt gewählt habe:
Wie man in der Grafik oben sehen kann, ist der Bereich unterhalb der Linie farblich gefüllt. Und genau das ist mit normalen Linien nicht möglich, da nicht sichergestellt ist, dass sie eine geschlossene Form ergeben. Beim Pfad wird einfach der End-Punkt der letzten Linie mit dem Start-Punkt des Pfades verbunden und alles darin farblich gefüllt...


Vielleicht gibt's aber noch eine ganz andere Lösung für mein Anliegen?!

Grüße
 
Was mir noch einfallen würde:
Wenn du den aktuellen Pfad kennst, sollte es kein Problem für dich sein, diesen Pfad zu kopieren, an der entsprechenden Stelle zu splitten und den neuen Punkt an der entsprechenden Stelle einzufügen.

Ist zwar recht inperformant - aber aktuell das einzige, was mir einfiele, um das "sauber" zu lösen.
 
Das wäre eine Möglichkeit. Und ich denke es wäre nicht inperformater als meine Lösung. Im Worst-Case verschiebe ich ALLE gezeichneten Pfade. Wobei... Wenn der die splittet muss ich danach auch manuell ALLE Punkte neu setzen, weil(!!!) alle Punkte bei mir absolut positioniert sind (man kann sie auch relativ zum vorherigen Punkt positionieren, aber dann bekomm ich Probleme beim verschieben eines Punktes, weil ich jedes Mal alle anderen umrechnen müsste).

Die Seite hatte ich noch gar nicht bedacht. Danke! :)

Ich denke, dass ich dann für einen Pfad die optimale Lösung schon habe. Ärgerlich aber wahr :D
 
Eine Lösung wie z.B. in C das Einfügen eines Elements in eine einfach verkettete Liste wäre toller. So sollte es aber auch gehen. Wirklich schön finde ich das aber nicht.
 
Ja, es ist eine einfach verkettete Liste. Aber der W3C sieht scheinbar nicht vor, dass man da was einfügt :/ Dabei ist SVG doch gerade dazu gedacht, dass man es nachträglich manipulieren kann. Sonst kann man gleich das schnelle Canvas nehmen.

Naja, vielleicht im nächsten Standard :D
 
Naja, immerhin kann man es ja quasi mit CSS und Javascript nutzen ...
Vielleicht kommen nachträgliche Modifikationen ja noch.

Ich sag's mal so: im Endeffekt ist es kein Beinbruch - musst du dir eben irgendwie in dein Klassendesign eine Methode zur Modifikation integrieren.

Sowas a la updatePath(original_path, new_pt) - ist ja (je nach Datenhaltung) relativ schnell gemacht.
Macht es eventuell Sinn, die einzelnen Punkte in einem Array oder einer Liste vorzuhalten - und bei entsprechenden Aktionen auf diese Daten die jeweiligen Elemente mit den neuen Koordinaten zu aktualisieren und dann neu zeichnen zu lassen?
Damit müsste zwar bei jeder Aktion ständig alles neu gezeichnet werden - aber du könntest durch einfaches einfügen oder modifizieren der Daten problemlos zeichnen.
Wie performant das ist weiß ich allerdings nicht - sobald man da einen Punkt um nen Pixel verschiebt zeichnet man ja wieder alles neu. Das ruckelt sicher fürchterlich.

Vermutlich ist deine jetzige Lösung inklusive des Pfad-kopieren-Ansatzes und durchiterieren inkl. einfügen des neuen Punktes + neu zeichnen noch das beste. Wirklich schön find ich das aber nicht.

Ein Framework wie Dojo oder so hast du aber nicht mal ausprobiert, oder? Vielleicht bietet das ja eine schöne Verwaltung solcher Grafiken. Wenn man das ganze Framework nicht nutzen möchte, kann man sich sicher von den vorhandenen Lösungen "inspirieren" lassen.
 
Von Dojo hab ich heut erst gelesen, es gibt auch nen Wrapper für GWT davon. Aber ich habs nun schon umgesetzt ^^
Was ich besonders lustig finde: Es gibt eine remove-Funktion, die mitten im Pfad einen Strich löscht. Einen Strich im Pfad ändern geht auch mit einem simplen Methoden-Aufruf. Nur das doofe Einfügen nicht.

Ich kann mir aber auch noch gut vorstellen, dass das Ganze einen anderen Grund hat: VML (quasi das SVG von Microsoft für den IE ab Version 6). Ich benutze ja schon einen Wrapper, der sowohl im IE als auch überall anders funktioniert. Aber der stellt natürlich nur die Funktionen bereit, die sowohl in SVG als auch in VML funktionieren...


EDIT: Ich hab mir gerade die Implementierung von der Library reingezogen, die ich benutze. Ich hätte nicht gedacht, dass es sooo stumpf ist :D
Für jeden Befehl wird quasi ein String erzeugt mit den Koordinaten und dem Typ (Move, MoveRelative, Close, etc.). Und hinten an eine ArrayList angefügt. Außer bei replace, da wird die Position in der List überschrieben. Und dann wird draw() aufgerufen. Und was macht draw?
StringBuilder und dann ganz stumpf die Liste Stück für Stück zum SVG String zusammensetzen. Fertig!
Ich hab mir das für VML auch mal angesehen. Ist sehr ähnlich zu SVG nur die Kürzel für den Typ heißen anders und manchmal ist die Reihenfolge noch anders.

Ich glaub, ich sollte bei Gelegenheit mal die .jar extrahieren und eine insert()-Methode einbauen. Ist bei 'ner ArrayList ja extrem billig. Da man einfach irgendwo einfügen kann und alles dahinter wird geshiftet. Das wär dann effizient! :)
 
Zuletzt bearbeitet:
benneque schrieb:
Für jeden Befehl wird quasi ein String erzeugt mit den Koordinaten und dem Typ (Move, MoveRelative, Close, etc.). Und hinten an eine ArrayList angefügt. Außer bei replace, da wird die Position in der List überschrieben. Und dann wird draw() aufgerufen. Und was macht draw?
StringBuilder und dann ganz stumpf die Liste Stück für Stück zum SVG String zusammensetzen. Fertig!
So ähnlich meinte ich das im Post vorher auch ...
Dass es wirklich so stupide ist, hätte ich nicht gedacht ;)
 
Also hiermit malt man einen Pfad:
Code:
<path fill="green" fill-opacity="0.5" stroke="black" stroke-opacity="1.0" stroke-width="1" d=" M100 100 L100 20 L173 53 L336 47 L336 100"></path>
Der Interessante Teil ist natürlich das 'd="..."'. Hier sieht man auch schon was passiert:
1. M100 100 = Bewege den Pfad-Anfang nach (100,100)
2. L100 20 = Linie nach (100,20)
3. L173 53 = Linie nach (173,53)
4. L336 47 = Linie nach (336,47)
5. L336 100 = Linie nach (336,100)

Und das war's schon :D
 
Man weiß ja nie, wer hier so alles mitliest :D

Ich hab nun die Änderungen in der Library gemacht und meinen Code auch nochmal umgebaut. Die Schleife zum Shiften brauch ich teilweise immernoch, allerdings shifte ich jetzt nur noch den Index der zu einem Kreis gehörenden Linie.

Bsp:
1. Punkt wird eingefügt: Pos (50, 50) .. zugehöriger Kreis hat den PathStep 0
2. Punkt wird eingefügt: Pos (100, 90) .. zugehöriger Kreis hat den PathStep 1 <- passt einfach so, weil es der letzte im Path ist
3. Punkt wird eingefügt: Pos (20, 80)
.. zugehöriger Kreis hat den PathStep 0, also wird der Kreis von (1.) auf PathStep 1 und der Kreis von (2.) auf PathStep 2 gesetzt.

Um das shiften effizienter zu gestalten, durchlaufe ich die ArrayList mit den Kreisen rückwärts. Warum? Sobald ich einen Kreis finde, dessen x-Position kleiner ist, als die, die ich einfügen will, kann ich die Schleife abbrechen. Das spart im Best-Case (n-1) Durchläuft. Im Worst-Case ist alles beim Alten (n Durchläufe) und im Average-Case brauche ich (n/2) Durchläufe. Also kann ich grob von einer Verdopplung der Performance sprechen.
Den gleichen 'Trick' habe ich dann auch beim Löschen von Punkten eingebaut :)



P.S. liebe begabte Mitleser, fühlt euch nicht beleidigt. Ich schreibe extra so ausführlich, damit es wirklich jeder versteht! ... denn auch die kleinen Kniffe beim Programmieren sollte man immer im Hinterkopf behalte ;)
 
ich könnte, ich könnte... :D

2 Dinge:
1. Das passiert eh in ein paar Monaten, da unser Projekt quasi-OpenSource wird.
2. Ich würde es auch jetzt schon machen, aber dafür müsste ich meinen Code erst schöner machen ^^

Meinst du eine richtig generische Klasse? Oder so wie sie ist, speziell für diese Bibliothek mit diesem Anwedungsfall?



Ich stell jetzt einfach mal die relevanten Algorithmen hier rein und erklär sie und änder die Kommentare auf Deutsch. Ab geht das!


Allgemeines zum Programm:
Das Programm hat 5 (fünf) 'globale' Variablen:

1. Liste aller Kreise:
ArrayList<Circle> circleList = new ArrayList<Circle>();
Achtung: Diese Liste ist sortiert (nach x Positionen) und innerhalb des Programms sorge ich manuell für die Aufrechterhaltung der Sortierung. (Ist das schlau? Oder lieber eine Liste nehmen, die das automatisch macht?!)

2. Liste aller selektierten Kreise:
ArrayList<Circle> selectedCircleList = new ArrayList<Circle>();

3. und 4. Höhe und Breite der Zeichenfläche (siehe 5.)
int maxWidth;
int maxHeight;

5. Die Zeichenfläche:
DrawingArea canvas;


Punkt hinzufügen:
x und y sind die Koordinaten für den neuen Punkt. MultiSelection wird bei mit per Shift-Taste umgeschaltet. D.h. ist die Taste gedrückt ist multiSelection 'true', sonst 'false'.
Achtung: Für das erste und letzte Element des Pfades gelten besondere Regeln! Wir man im Screenshot (1. Post) erkennen kann, führt eine Linie vom unteren Rand zum ersten Kreis und eine zweite Linie vom letzten Kreis zum unteren Rand. Diese sind natürlich auch Teil des Pfades und müssen berücksichtigt werden! Das heißt: Wenn ich den Kreis mit Index 0 habe, bedient dieser sich der x-Position vom PathStep mit Index 1.
Wenn es einen neuen ersten Kreis gibt, muss also zusätzlich die x-Position des PathStep mit Index 0 angepasst werden, damit die Linie gerade nach oben geht.
Und wenn es einen neuen letzten Kreis gibt, muss zusätzlich die x-Position des letzten PathStep (letzter Kreis-Index + 1) angepasst werden.

Code:
public void addPoint(int x, int y, boolean multiSelection) {
    // Hier wird geschaut, ob der neue Punkt im Panel liegt, wenn nicht gibt's eine Exception
    if (x > maxWidth || x < 0 || y > maxHeight || y < 0) {
        throw new IllegalArgumentException("Position must be inside panel!");
    }

    // Das ist der Kreis, der sich am Endpunkt der Linie befindet
    Circle circle = new Circle(x, y);

    // Mit dieser Variable wird geschaut, ob es einen anderen Punkt gibt, der eine kleinere x-Position
    // als der neue Punkt hat. Der Index des kleineren Punktes wird dann hier gespeichert. Wenn
    // kein solcher Punkt gefunden wird, bleibt der Wert auf "-1". Das heißt in jedem Fall, 
    // dass wir den neuen Punkt an die Stelle (found+1) einfügen müssen
    int found = -1;

    // Diese Schleife durchläuft alle bisherigen Kreise rückwärts und shiftet die zugehörigen
    // PathSteps um 1 nach rechts. Die Schleife wird abgebrochen, sobald wir einen Kreis gefunden
    // haben, dessen x-Position kleiner ist als die, die wir einfügen wollen
    for (int i = circleList.size() - 1; i >= 0; i--) {
        Circle c = circleList.get(i);

        if (c.getX() < x) {
            found = i + 1;
            break;
        }
        c.connectedPathStep++;
    }

    // Wenn die Schleife keine kleinere x-Koordinate gefunden hat als die neue, dann ist
    // unser neuer Punkt, der neue linkeste Wert. Also setzen wir den Beginn des kompletten Pfades
    // auf die neue x-Position
    if(found == -1) {
        this.setX(x);
        found = 0;
    }

    // Das hier tritt ein, wenn der erste Schleifen-Durchlauf zum Abbruch geführt hat:
    // Der neue Punkt hat also die größte x-Koordinate
    if(found == circleList.size()) {
        this.setStep(this.getStepCount() - 1, new LineTo(false, x, maxHeight));
    }

    // Hier wird der neue Punkt in den Path eingefügt. Und zwar an Stelle (found+1)
    this.addStep(found+1, new LineTo(false, x, y));

    // Der Kreis, der zum neuen Punkt gehört muss natürlich auch gezeichnet werden,
    // also fügen wir ihn zur Zeichenfläche (canvas) hinzu. Geben ihm einen DragAndDrop-Handler
    // und fügen ihn zur Liste der Kreise hinzu. Danach wird noch der neue PathStep dem
    // Kreis zugeordnet
    canvas.add(circle);
    new DragHandler(circle);
    circleList.add(found, circle);
    circle.connectedPathStep = found + 1;

    // Da das Tool auch multiSelection kann, wird - falls es aktiviert ist - der neue Kreis zusätzlich
    // selektiert. Falls es deaktiviert ist, wird der neue Kreis selektiert und alle vorherig selektierten
    // Kreise werden deselektiert.
    if (multiSelection) {
        select(circle);
    } else {
        setSelected(circle, false);
    }
}


Punkt mit bestimmter x-Position löschen:
Code:
public void removePoint(int x) {
    // Wir durchkämmen wieder die Liste aller Kreise von hinten nach vorn, da alle Kreise, die
    // hinter dem zu löschenden sind, ihren connectedPathStep anpassen müssen
    for (int i = circleList.size() - 1; i >= 0; i--) {
        Circle circle = circleList.get(i);

        // Wenn wir den Kreis gefunden haben, der zur gesuchten x-Position gehört ...
        if (circle.getX() == x) {
            // Wird der zugehörige PathStep gelöscht und der Kreis aus der Zeichenfläche und
            // der Liste gelöscht
            this.removeStep(circle.connectedPathStep);
            canvas.remove(circle);
            circleList.remove(i);

            // Hier kommen wieder die Zusätze für die Anpassungen, falls der gelöschte Kreis
            // der erste oder letzte war
            if (!circleList.isEmpty()) {
                // Wenn es der letzte war, holen wir uns den neuen letzten Kreis und passen den Nach-
                // folger PathStep (also die gerade Linie nach unten) an dessen x-Position an
                if (i == list.size()) {
                    circle = circleList.get(i - 1);
                    setStep(circle.connectedPathStep + 1, new LineTo(false, circle.getX(), maxHeight));

                // Wenn wir den ersten Kreis gelöscht haben, setzen den Startpunkt des gesamten Path
                // auf die x-Position des neuen ersten Kreises
                } else if (i == 0) {
                    this.setX(circleList.get(0).getX());
                }
            }

            // Wenn wir den zu löschenden Punkt gefunden haben können wir abbrechen
            break;
        }

        // Alle Kreise, die hinter dem gesuchten liegen, bekommen einen angepassten PathStep
        circle.connectedPathStep--;
    }
}


Methode, um einen bestimmten Kreis zu löschen:
Diese Methode gibt es zusätzlich zur oberen, da ich hoffe, dass sie ein wenig performanter ist :D
Code:
public void removeCircle(Circle circle) {
    // Circle ist hier deklatiert, da ich den gefundenen Kreis so einfach nach der Schleife löschen kann
    Circle c;

    // zu löschenden Kreis suchen
    for (int i = circleList.size() - 1; i >= 0; i--) {
        c = circleList.get(i);
        if (c.equals(circle)) {
            break;
        }
        c.connectedPathStep--;
    }

    // Im Prinzip das selbe, wie in der remove-Methode zuvor
    this.removeStep(circle.connectedPathStep);
    canvas.remove(circle);
    circleList.remove(circle);

    if (!circleList.isEmpty()) {
        c = circleList.get(circleList.size() - 1);
        setStep(c.connectedPathStep + 1, new LineTo(false, c.getX(), maxHeight));
        this.setX(circleList.get(0).getX());
    }
}


Neue Position für einen Kreis (und seinen zugehörigen PathStep) setzen:
Achtung: 2 Kreise dürfen nicht die gleiche x-Position haben. Deswegen sind in der Methode ein paar Dinge mehr drin.
Code:
private void setNewPosition(Circle circle, int newX, int newY) {
    // Hier wird dafür gesorgt, dass die neue Position nicht außerhalb der Zeichenfläche liegt
    if (newX > maxWidth) newX = maxWidth;
    if (newX < 0) newX = 0;
    if (newY > maxHeight) newY = maxHeight;
    if (newY < 0) newY = 0;

    // Die eigene x-Position darf höchstens so groß sein, wie die x-Position des Nächsten Punktes - 1
    if (circleList.indexOf(circle) != circleList.size() - 1
                && newX >= circleList.get(circleList.indexOf(circle) + 1).getX()) {
        newX = circleList.get(circleList.indexOf(circle) + 1).getX() - 1;
    }

    // Die eigene x-Position muss mindestens so groß sein, wie die des Vorgängers + 1
    if (circleList.indexOf(circle) != 0 && newX <= circleList.get(circleList.indexOf(circle) - 1).getX()) {
        newX = circleList.get(circleList.indexOf(circle) - 1).getX() + 1;
    }

    // Wenn der letzte Kreis (zugehöriger Pathstep ist natürlich der vorletzte) bewegt wurde,
    // muss wieder die Linie, die gerade nach unten geht angepasst werden
    if (circle.connectedPathStep == this.getStepCount() - 2) {
       this.setStep(circle.connectedPathStep + 1, new LineTo(false, newX, maxHeight));
    }

    // Wenn der erste Kreisbewegt wurde, muss die Start-Position des Pfades angepasst werden
    if (circle.connectedPathStep == 1) {
       this.setX(newX);
    }

    // Kreis an neue Position zeichnen
    circle.setX(newX);
    circle.setY(newY);

    // Zugehörigen PathStep an neue Position zeichnen
    setStep(circle.connectedPathStep, new LineTo(false, newX, newY));
}



Zu guter Letzt gibt es noch das Zeug mit der Selection:
Ein selektierter Kreis wird zum einen natürlich optisch hervorgehoben. Innerhalb des Programms 'erkennt' man einen selektierten Kreis daran, dass er in der 'selectedCircleList' steht. Beim Deselektieren wird er dort einfach gelöscht und die Optik angepasst.
Los geht's:

Kreis selektieren:
Code:
public void select(Circle circle) {
    // Das brauch ich zur Sicherheit. Im Grunde sieht man, dass nicht viel passiert:
    // Wenn der zu selektierende Kreis noch nicht selektiert ist, dann kommt er in die List und
    // wird hübsch gemacht
    if (circle != null) {
        if (!selectedCircleList.contains(circle)) {
            selectedCircleList.add(circle);
            circle.setHighlightStyle();
        }
    }
}

Kreis deselektieren:
Code:
public void unselect(Circle circle) {
    // Sonderfall für das Programm. Wenn 'null' übergeben wird, werden alle Kreise deselektiert.
    // Bei mir im Programm ist das der Fall, wenn man in das Panel - aber auf keinen Kreis - klickt
    if (circle == null) {
        // Für alle selektierten Kreise, den optischen Style auf 'normal' zurücksetzen
        for (Circle c : selectedCircleList) {
            c.setNormalStyle();
        }
        // Und die Liste der selektierten Kreise leeren
        selectedCircleList.clear();


    // Falls doch ein bestimmter Kreis deselektiert werden soll, setzen wir seinen Style auf 'normal'
    // und löschen ihn aus der Liste der selektierten Kreise
    } else {
        circle.setNormalStyle();
        selectedCircleList.remove(circle);
    }
}

Selektion 'umschalten' (wichtig für multi-select) bzw. normales Selektieren:
Hier wird das übliche single-/multi-select Verhalten der Betriebssysteme simuliert: Wenn man die multi-select-Taste gedrückt hält kann man Kreise zur Selektion hinzufügen oder entfernen. Und wenn man die Taste nicht gedrückt hält, wird der angeklickte Kreis selektiert und alle anderen (falls welche da waren) deselektiert.
Code:
public void setSelected(Circle circle, boolean multiSelection) {
    // Wenn multiSelection 'an' ist, dann wird der Status des angeklickten Kreises invertiert
    if (multiSelection) {
        if (selectedCircleList.contains(circle)) {
            unselect(circle);
        } else {
            select(circle);
        }

    // Wenn multiSelection 'aus' ist, dann wird alles deselektiert und der angeklickte Kreis selektiert
    } else {
        unselect(null);
        select(circle);
    }
}


Entfernen von selektierten Kreisen:
Durch die Vorarbeit mittels der anderen Methoden, ist diese Funktionalität sehr simpel umzusetzen: Liste der selektierten Kreise durchgehen und jeweils 'removeCircle()' aufrufen
Code:
public void removeSelected() {
    // Die if-Abfrage sollte eigentlich unnötig sein. Ich weiß gerad selbst nicht mehr warum die da ist
    if (!selectedCircleList.isEmpty()) {
        // Für jeden Kreis in der Selektions-Liste wird removeCircle(circle) aufgerufen
        for (Circle circle : selectedCircleList) {
            removeCircle(circle);
        }

        // und zum Schluss einfach die gesamte Liste der selektierten Kreise geleert
        selected.clear();
    }
}
 
Zuletzt bearbeitet:
Zurück
Oben