//****************************************************************************** // Applet: GraphEdit.java // Java-Projekt bei Prof. Ring / Universitat-GH Siegen im WS 1996/97 // // Aufgabenstellung: Gruppe 4: Graphen-Editor // ------------------------------------------ // Entwickeln Sie ein Java-Applet, das es ermoeglicht, // - Knoten eines Graphen einzugeben, // - einzelne Knoten zu verschieben, // - einen oder mehrere Knoten zu markieren, // - die markierten Knoten zu loeschen, // - zwischen zwei markieren Knoten eine Kante einzufuegen oder zu loeschen. // Beim Verschieben eines Knotens sollen alle mit diesem verbundenen Kanten // sichbar mitbewegt werden. // //****************************************************************************** import java.applet.*; import java.awt.*; import java.util.*; // Die Klasse "Knoten" repraesentiert einen Graphen-Knoten. // Die Member-Variablen sind public; ich verzichte auf private Daten und ent- // sprechende getSonstwas()-Methoden, weil die Knotenposition nicht unter den // Datenschutz faellt und nicht von nationalem Interesse ist. class Knoten { public int x; // Koordinate des Knotens (x, y) public int y; Rectangle r; // Umgebendes Rechteck fuer graphische Eingabe von Kanten boolean marked; // Zustandsvariable: Knoten ist markiert boolean pointed; // Zustandsvariable: Maus befindet sich genau ueber Knoten boolean touched; // Zustandsvariable: Maus befindet sich neben Knoten boolean dragged; // Zustandsvariable: Knoten wird gerade bewegt Vector kantlist; // dynamisches Array zur Speicherung der Kanten // einziger Konstruktor public Knoten(int x, int y, int rx, int ry, int rw, int rh) { this.x = x; this.y = y; this.marked = false; this.pointed = false; this.touched = false; this.dragged = false; r = new Rectangle(); this.r.x = rx; this.r.y = ry; this.r.width = rw; this.r.height = rh; kantlist = new Vector(); } } // Klasse Knoten // Die Klasse "Kante" dienst zur Kapselung eines int-Wertes in eine von // der Klasse "Object" abgeleitete Klasse. Integerwerte koennen sonst nicht // zu einem dynamischen Array zugefuegt werden. class Kante { int idxToKnoten; // idxToKnoten enthaelt den Index eines Knotens in // der global gespeicherten Liste aller Knoten public Kante() { this.idxToKnoten = -1; } public Kante(int idxToKnoten) { this.idxToKnoten = idxToKnoten; } } // KIasse Kante //============================================================================== // Eigentliche Klasse GraphEdit //============================================================================== public class GraphEdit extends Applet { private Vector knotlist; // dyn. Liste von Objekten: Knoten int radius; // Radius eines Knotens in pt int rectWidth; // Breite des Umgebungs-Rechtecks eines Knotens Point lastClick; // Speicherung der Mauspos. bei mouseDown() boolean moreInfo; // Detailanzeige an/aus Point anfKante, endKante; // Anfangs- und Endpunkt beim Kantenzeichnen int idxAnfKnoten; // Ausgangsknoten beim Kantenzeichnen boolean dragKante; // Zustandsvariable, die angibt, ob gerade eine // neue Kante mit der Maus gezeichnet wird // Es folgen die Member fuer graphische Darstellung und Bedienung Dimension offDimension; // Variablen zur Image offImage; // Realisierung des Graphics offGraphics; // Double-Buffering Button b1, b2, b3, b4, b5, b6; // Alle Buttons, Checkbox b7; // Checkboxen, TextField strKnoten, strKanten; // und Textfelder Panel p; // kommen ins Panel p Font myFont; // Schriftart int maxX, maxY, minX, minY; // Ausdehnung des Zeichenbereichs Rectangle clipRec; // Clipping-Rechteck fuer den Zeichenbereich int btnWidth; // Nach Einfuegen der Buttons muss deren Platz- // bedarf gespeichert werden // Konstruktor der Klasse GraphEdit //-------------------------------------------------------------------------- public GraphEdit() { knotlist = new Vector(); radius = 10; // Radius sollte durch 2 teilbar sein! rectWidth = 26; // Breite sollte groesser radius und durch // 2 teilb. sein lastClick = new Point(0, 0); moreInfo = false; anfKante = new Point(0,0); endKante = new Point(0,0); idxAnfKnoten = 0; dragKante = false; // zunaechst wird nicht ge-"dragged" clipRec = new Rectangle(); btnWidth = 0; } // Die getAppletInfo()-Methode gibt einen String mit Informationen // ueber das Applet zurueck. //-------------------------------------------------------------------------- public String getAppletInfo() { return "Name: GraphEdit\r\n" + "Author: Olaf Schröder\r\n" + "Uni-GH Siegen, Jan. 1997"; } // Die init()-Methode dient hier zur Init. des Layouts, des Fonts etc. //-------------------------------------------------------------------------- public void init() { // Ich will aber "Helvetica" (Probleme mit MS-Inet-Explorer) myFont = new Font("Helvetica", Font.PLAIN, 10); // Alle Buttons und Checkboxen erstellen b1 = new Button("Alles löschen"); b2 = new Button("Alles markieren"); b3 = new Button("Mark. aufheben"); b4 = new Button("Kante(n) setzen"); b5 = new Button("Kante(n) löschen"); b6 = new Button("Knoten löschen"); b7 = new Checkbox("Details"); // Zwei Textfelder zur Anzeige der Knoten- und Kantenanzahl strKnoten = new TextField("Anzahl Knoten: 0"); strKnoten.setEditable(false); strKanten = new TextField("Anzahl Kanten: 0"); strKanten.setEditable(false); // neuen Panel erstellen und Layout setzen... p = new Panel(); p.setLayout(new GridLayout(0, 1, 0, 2)); // ...und alle obigen Bedienelemente hinzu + einem Label p.add(b1); p.add(b2); p.add(b3); p.add(b4); p.add(b5); p.add(b6); p.add(strKnoten); p.add(strKanten); p.add(b7); p.add(new Label("Olaf Schröder, 1/1997", Label.CENTER)); // Panel dann rechts im Applet einfuegen this.setLayout(new FlowLayout(FlowLayout.RIGHT, 3,3)); this.add(p); // Containerlayout ueberarbeiten... validate(); // ...und aus den Daten benoetigte Variablen errechnen Dimension d = new Dimension(p.size()); btnWidth = d.width; d = this.size(); maxX = d.width-btnWidth-11-3; maxY = d.height-1-3; minX = 3; minY = 3; } // Methode init() // Die Methode destroy() wird noch nicht genutzt. // Ich vertraue auf das Garbage-Collection von Java... //------------------------------------------------------------------------- public void destroy() { } // GraphEdit unterstuetzt Double-Buffering, wie es im Java-Tutorial im // Kapitel "Creating a User Interface / Working with Graphics" beschrieben // ist. Hierzu muss die update()-Methode ueberschrieben werden und alle // Ausgaben zunaechst in ein "Bild" gemacht werden, bevor das "Bild" dann // "auf einmal" ausgegeben wird. // Die paint()-Methode wird benoetigt, weil sie aufgerufen wird, wenn der // Appletbereich ungueltig wird (z.B. durch Scrollen der HTML-Seite). //-------------------------------------------------------------------------- public void paint(Graphics g) { update(g); } public void update(Graphics g) { Dimension d = size(); int k; String knotstring = new String(); Kante tkant; // neuen Graphics-Context erstellen, falls kein gueltiger existiert if ( (offGraphics == null) || (d.width != offDimension.width) || (d.height != offDimension.height) ) { offDimension = d; offImage = createImage(d.width, d.height); offGraphics = offImage.getGraphics(); } // Letztes "Bild" leoschen und 3D-Rechteck zeichnen offGraphics.setColor(getBackground()); offGraphics.fillRect(0, 0, d.width, d.height); offGraphics.draw3DRect(0, 0, d.width-btnWidth-11, d.height - 1, true); offGraphics.draw3DRect(2, 2, d.width-btnWidth-15, d.height - 5, false); offGraphics.setColor(Color.black); // Clipping auf Graphen-Zeichenbereich Rechteck setzen offGraphics.clipRect(4, 4, d.width-btnWidth-18, d.height-8); // falls gerade eine Kante eingefuegt wird, zeichnen! if ((dragKante) && (anfKante.x!=0) && (anfKante.y!=0)) offGraphics.drawLine(anfKante.x, anfKante.y, endKante.x, endKante.y); Knoten laufknot, zielknot; int kantAnz = 0; // Erst mal die Kanten aller Knoten zeichnen, gleichzeitig Anzahl merken for (int i=0; i "; for (k=0; kmaxX) || (y>maxY) ) return true; if (x maxX-radius) { r.x = x-halfr; r.width = halfr+maxX-x; } if (y > maxY-radius) { r.y = y-halfr; r.height = halfr+maxY-y; } // neuen Knoten einfuegen knot = new Knoten(x, y, r.x, r.y, r.width, r.height); knotlist.addElement(knot); // alles neu zeichnen und Schluss. repaint(); return true; } // Methode mouseDown() // Die mouseUp()-Methode wird aufgerufen, wenn die Taste losgelassen wird. //-------------------------------------------------------------------------- public boolean mouseUp(Event evt, int x, int y) { Knoten knot, laufknot; // Laufvariablen fuer for-Schleifen Knoten anfknot; // Anfangsknoten beim Kantenzeichnen boolean addKante = true; // Hilfsvariable beim Kantenzeichnen lastClick.x = 0; // Die in mouseDown() gespeicherte Mausposition wird lastClick.y = 0; // wird wieder "auf Null" gesetzt. // Schleife, um herauszufinden, ob Maus auf einen Knoten zeigt for (int i=0; i=maxX) knot.x = maxX; if (y>=maxY) knot.y = maxY; int halfr = rectWidth / 2; // haeufig gebrauchter Wert Rectangle r = new Rectangle(x-halfr, y-halfr, 2*halfr, 2*halfr); // liegt der Kreis am Appletrand? Dann Umgebungsrechteck anpassen if (knot.x < radius) { r.x = 0; r.width = halfr + knot.x; } if (knot.y < radius) { r.y = 0; r.height = halfr + knot.y; } if (knot.x > maxX - radius) { r.x = knot.x - halfr; r.width = halfr + maxX - knot.x; } if (knot.y > maxY - radius) { r.y = knot.y - halfr; r.height = halfr + maxY - knot.y; } knot.r = r; knot.dragged = true; repaint(); return true; } } return true; } // Methode mouseDrag() // Die Methode mouseMove() veraendert in Abhaengigkeit der Mausposition den // Zustand und die Darstellung der Knoten //-------------------------------------------------------------------------- public boolean mouseMove(Event evt, int x, int y) { boolean foundOne = false; // nur _ein_ Knoten darf "touched" sein boolean redraw = false; // zur Vermeidung von unnoetigem Zeichnen Knoten knot; // "Lauf"-Knoten in for-Schleife Rectangle ir = new Rectangle(); // inneres Rectangle zum Markieren int halfr = rectWidth/2; // haeufig benoetigter Wert int iradius = (int)radius-2; // innerer Radius zum markieren showStatus("Applet läuft..."); // Dies ist ein deutsches Applet :-) // Schleife, in der der Zustand der Knoten in Abhaengigkeit // der Mausposition ermittelt und gesetzt wird for (int i=0; i Knoten markierbar if ( ir.inside(x,y) ) { knot.pointed = true; // Knoten mit Mausdruck markierbar knot.touched = false; // Knoten kann nicht touched sein. redraw = true; // auf jeden Fall repaint() aufrufen continue; // Knoten kann nicht mehr "touched" sein } else { if (knot.pointed) redraw = true; // falls sich Zustand geaendert knot.pointed = false; // hat -> repaint() } // dann, ob Maus innerhalb des Umgebungs-Rechteck liegt -> Kanten ziehen if ( knot.r.inside(x,y) && !foundOne) { knot.touched = true; // Kanten auf Mausdruck ziehbar knot.pointed = false; // Knoten kann nicht "pointed" sein. redraw = true; // auf jeden Fall repaint() aufrufen foundOne = true; // nur von einem Knoten kann gleichzeitig... continue; // ...eine Kante gezogen werden! } else { if (knot.touched) redraw = true; // falls sich Zustand geaendert knot.touched = false; // hat -> repaint() } } // Falls sich was getan hat, neu zeichnen if (redraw) repaint(); return true; } // Methode mouseMove() // Die Methoden mouseEnter() und mouseExit() werden zur Zeit noch nicht // unterstuetzt. Vielleicht ergibt sich noch eine sinnvolle Funktion. //-------------------------------------------------------------------------- public boolean mouseEnter(Event evt, int x, int y) { return true; } public boolean mouseExit(Event evt, int x, int y) { return true; } // Ueberschreiben von getBackground() zum Aendern der Hintergrundfarbe //-------------------------------------------------------------------------- public Color getBackground() { return Color.lightGray; } // In der action()-Methode wird auf alle Button-Events reagiert //-------------------------------------------------------------------------- public boolean action(Event event, Object arg) { Knoten knot, tknot; Kante tkant; int i, k, l, delidx; boolean addKante, weitere, gefunden; // Button "Zuruecksetzen" if (event.target == b1) { // alles dem Garbage-Collektor ueberlassen: knotlist = new Vector(); } else // Button "Alles markieren" if (event.target == b2) { // knotlist durchgehen, und alle Knoten auf marked=true setzen for (i=0; i= 0) { // aus dem tknot kann die Kante zu knot entfernt werden tknot.kantlist.removeElementAt(delidx); // jetzt noch den Eintrag in knot suchen, der auf den aktuellen // tknot verweist... (doppelte Kanten) for (l=0; l delidx) tkant.idxToKnoten -= 1; } } // In den kantlist-en ist das Fehlen des Knotens mit Index delidx // vermerkt. Der Knoten kann nun endgueltig geloescht werden. knotlist.removeElementAt(delidx); } } else // Checkbox "Details" // Bei Aenderung der Checkbox "Details" wird die globale Variable moreInfo // gesetzt und zusaetzliche Informationen zu den Knoten angezeigt. if (event.target == b7) { moreInfo = b7.getState(); } // Alles neu zeichnen und Methode action() beenden. repaint(); return true; } // Methode action() } // Klasse GraphEdit