import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import java.awt.image.*; import javax.swing.*; import javax.swing.event.*; import java.util.*; /** * Die Klasse stellt ein Fenster mit einer Malfläche zur Verfügung, * auf der Objekte der Klassen Rechteck, Kreis und Dreieck sowie Turtle dargestellt * werden können. * Die Zeichenfläche wird beim ersten Anlegen eines Zeichenobjekts automatisch * nach dem Muster Singleton angelegt. * * @author Albert Wiedemann * @version 1.0 */ class Zeichenfenster { /** Interface für die Aktionsausführung. */ interface AktionsEmpfaenger { /** Methode wird vom Taktgeber aufgerufen. */ void Ausführen(); void Taste (char taste); void SonderTaste (int taste); void Geklickt (int x, int y, int anzahl); } /** Aufzählung der erzeugbaren Objektarten. */ static enum SymbolArt {kreis, dreieck, rechteck, turtle, figur, text;}; /** Einziges Objekt der Zeichenfläche. */ private static Zeichenfenster zeichenfläche = null; /** Fenster für die Zeichenfläche. */ private JFrame fenster; /** Die eigentliche Darstellungskomponente. */ private JComponent malfläche; /** Stop-Knopf für den Taktgeber. */ private JButton stop; /** Start-Knopf für den Taktgeber. */ private JButton start; /** Einsteller für die Taktrate*/ private JSlider slider; /** Feld aller zu zeichnenden Objekte. */ private ArrayList alleSymbole; /** Feld aller zu zeichnenden Objekte. */ private ArrayList aktionsEmpfänger; /** Timerobjekt für die zentrale Zeitverwaltung */ private javax.swing.Timer timer; /** * Legt das Fenster und die Malfläche an */ private Zeichenfenster () { alleSymbole = new ArrayList(); aktionsEmpfänger = new ArrayList(); fenster = new JFrame("Zeichenfenster"); fenster.setLocation(50, 50); fenster.setSize(800, 600); fenster.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); //Close-Button kann nicht versteckt oder abgestellt werden. malfläche = new JComponent() { public void paint (Graphics g) { g.setColor(new Color (230, 230, 230)); g.fillRect(0, 0, getWidth() - 1, getHeight() - 1); synchronized (malfläche) { for (GrafikSymbol s: alleSymbole) { if (s.sichtbar) { s.Zeichnen(g); } } } } }; malfläche.setOpaque(true); malfläche.addMouseListener(new MouseAdapter () { /** * Gibt den Ort eines Mouseclicks an die eigentliche Aktionsmethode weiter. * @param e das zugrestellte Ereignis */ public void mousePressed(MouseEvent e) { malfläche.requestFocus(); ArrayList empfänger = new ArrayList(aktionsEmpfänger); for (AktionsEmpfaenger em: empfänger) { em.Geklickt(e.getX(), e.getY(), e.getClickCount()); } } } ); malfläche.addKeyListener(new KeyAdapter () { /** * Gibt die Taste an die eigentliche Aktionsmethode weiter. * @param e das zugestellte Ereignis */ public void keyPressed(KeyEvent e) { ArrayList empfänger = new ArrayList(aktionsEmpfänger); if ((int) e.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { switch (e.getKeyCode()) { case KeyEvent.VK_ENTER: for (AktionsEmpfaenger em: empfänger) { em.Taste((char) KeyEvent.VK_ENTER); } break; default: for (AktionsEmpfaenger em: empfänger) { em.SonderTaste(e.getKeyCode()); } } } else { for (AktionsEmpfaenger em: empfänger) { em.Taste(e.getKeyChar()); } } } } ); malfläche.addComponentListener(new ComponentAdapter() { /** * Setzt die Hintegrundbilder aller Turtle auf die neue Größe. */ public void componentResized​(ComponentEvent e) { for (GrafikSymbol s: alleSymbole) { if (s instanceof TurtleIntern) { ((TurtleIntern) s).NeueGrößeSetzen(); } } } } ); fenster.add(malfläche, BorderLayout.CENTER); JPanel panel = new JPanel(); panel.setMinimumSize(new Dimension(200, 60)); panel.setSize(200,60); panel.setVisible(true); panel.setLayout(new GridLayout(1, 2)); JPanel panel2 = new JPanel(); panel2.setMinimumSize(new Dimension(100, 60)); panel2.setSize(100,60); panel2.setVisible(true); panel2.setLayout(new GridLayout(1, 1)); stop = new JButton(); start = new JButton(); start.setLocation(10, 10); start.setSize(80, 30); start.setText("Start"); start.setVisible(true); start.addActionListener(new ActionListener () { public void actionPerformed (ActionEvent evt) { TaktgeberStartenIntern(); malfläche.requestFocus(); } } ); panel2.add(start); stop.setLocation(100, 10); stop.setSize(80, 30); stop.setText("Stop"); stop.setVisible(true); stop.setEnabled(false); stop.addActionListener(new ActionListener () { public void actionPerformed (ActionEvent evt) { TaktgeberStoppenIntern(); malfläche.requestFocus(); } } ); panel2.add(stop); panel.add(panel2); slider = new JSlider(0, 1000, 100); slider.setLocation(190, 10); slider.setSize(160, 40); slider.setMinimumSize(new Dimension(160, 40)); slider.setPreferredSize(new Dimension(160, 40)); slider.setMajorTickSpacing(100); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setValue(1000); slider.addChangeListener(new ChangeListener() { public void stateChanged​(ChangeEvent e) { timer.setDelay(slider.getValue()); malfläche.requestFocus(); } } ); panel.add(slider); fenster.add(panel, BorderLayout.SOUTH); fenster.setVisible(true); malfläche.requestFocus(); timer = new javax.swing.Timer (1000, new ActionListener () { /** * Vom Timer aufgerufen. * Erzeugt den nächsten Taktimpuls * @param evt der Timerevent */ public void actionPerformed (ActionEvent evt) { ArrayList empfänger = new ArrayList(aktionsEmpfänger); for (AktionsEmpfaenger e: empfänger) { e.Ausführen(); } } } ); } /** * Meldet die aktuelle Breite der Malfläche. * @returns Breite der Malfläche */ static int MalflächenBreiteGeben() { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } return zeichenfläche.malfläche.getWidth(); } /** * Meldet die aktuelle Höhe der Malfläche. * @returns Höhe der Malfläche */ static int MalflächenHöheGeben() { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } return zeichenfläche.malfläche.getHeight(); } /** * Trägt einen neuen Aktionsempfänger ein. * @param neu der neue Aktionsempfänger */ static void AktionsEmpfängerEintragen(AktionsEmpfaenger neu) { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } zeichenfläche.aktionsEmpfänger.add(neu); } /** * Löscht einen Aktionsempfänger aus der Liste. * @param alt der zu löschende Aktionsempfänger */ static void AktionsEmpfängerEntfernen(AktionsEmpfaenger alt) { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } zeichenfläche.aktionsEmpfänger.remove(alt); } /** * Erzeugt ein neues darzustelledes Symbol. * Die möglichen Symbole sind im Aufzählungstyp SymbolArt beschrieben. * @param art Art des zu erzeugenden Symbols. * @return Referenz auf das Delegate-Objekt. */ static GrafikSymbol SymbolErzeugen (SymbolArt art) { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } return zeichenfläche.SymbolAnlegen(art); } /** * Startet den Taktgeber. */ static void TaktgeberStarten () { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } zeichenfläche.TaktgeberStartenIntern(); } /** * Stoppt den Taktgeber. */ static void TaktgeberStoppen () { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } zeichenfläche.TaktgeberStoppenIntern(); } /** * Ablaufgeschwindigkeit des Zeitgebers einstellen. * * @param dauer: Angabe in Millisekunden */ static void TaktdauerSetzen (int dauer) { if (zeichenfläche == null) { zeichenfläche = new Zeichenfenster(); } zeichenfläche.slider.setValue(dauer < 0 ? 0 : (dauer > 1000 ? 1000: dauer)); } /** * Erzeugt das neue Symbol tatsächlich. * @param art Art des zu erzeugenden Symbols. * @return Referenz auf das Delegate-Objekt. */ private GrafikSymbol SymbolAnlegen (SymbolArt art) { GrafikSymbol neu = null; switch (art) { case rechteck: neu = new RechteckIntern(); break; case kreis: neu = new EllipseIntern(); break; case dreieck: neu = new DreieckIntern(); break; case turtle: neu = new TurtleIntern(); break; case figur: neu = new FigurIntern(); break; case text: neu = new TextIntern(); break; } synchronized (zeichenfläche.malfläche) { zeichenfläche.alleSymbole.add(neu); } malfläche.repaint(); return neu; } /** * Startet den Taktgeber. */ private void TaktgeberStartenIntern() { start.setEnabled(false); stop.setEnabled(true); timer.start(); } /** * Stoppt den Taktgeber. */ private void TaktgeberStoppenIntern() { start.setEnabled(true); stop.setEnabled(false); timer.stop(); } /** * Oberklasse für alle verfügbaren Grafiksymbole. * Alle Grafiksymbole werden über ihr umgebendes Rechteck beschrieben. */ abstract class GrafikSymbol { /** x-Position der linken oberen Ecke. */ protected int x; /** y-Position der linken oberen Ecke. */ protected int y; /** Breite des umgebenden Rechtecks. */ protected int b; /** Höhe des umgebenden Rechtecks. */ protected int h; /** Farbe des Symbols. */ protected Color c; /** Sichtbarkeit des Symbols. */ protected boolean sichtbar; /** Drehwinkel (mathematisch positiver Drehsinn) des Symbols. */ protected int winkel; /** Die Form des Grafiksymbols. */ protected Area form; /** Farbe Hellgelb. */ protected final Color hellgelb = new Color(255,255,128); /** Farbe Hellgrün. */ protected final Color hellgrün = new Color(128,255,128); /** Farbe Orange. */ protected final Color orange = new Color(255,128,0); /** Farbe Braun. */ protected final Color braun = new Color(128,64,0); /** * Der Konstruktor erzeugt ein rotes Symbol in der linken oberen Ecke des Fensters. */ GrafikSymbol() { x = 10; y = 10; b = 100; h = 100; c = Color.RED; sichtbar = true; winkel = 0; FormErzeugen(); } /** * Normiert den Winkel auf Werte im Bereich [0; 360[ * @param winkel der Eingabewinkel * @return der normierte Winkel */ int WinkelNormieren(int winkel) { while (winkel < 0) { winkel += 360; } return winkel % 360; } /** * Setzt die Position (der linken oberen Ecke) des Objekts. * @param x x-Position der linken oberen Ecke * @param y y-Position der linken oberen Ecke */ void PositionSetzen (int x, int y) { this.x = x; this.y = y; FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Setzt die Größe des Objekts. * @param breite (neue) Breite des Objekts * @param höhe (neue) Höhe des Objekts */ void GrößeSetzen (int breite, int höhe) { b = breite; h = höhe; FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Bestimmt die RGB-Farbe für den gegeben String. * @param farbe die Farbe als String * @return die Farbe als RGB-Farbe */ Color FarbeCodieren (String farbe) { farbe = farbe.toLowerCase(); switch (farbe) { case "weiß": case "weiss": return Color.WHITE; case "rot": return Color.RED; case "grün": case "gruen": return Color.GREEN; case "blau": return Color.BLUE; case "gelb": return Color.YELLOW; case "magenta": return Color.MAGENTA; case "cyan": return Color.CYAN; case "hellgelb": return hellgelb; case "hellgrün": case "hellgruen": return hellgrün; case "orange": return orange; case "braun": return braun; case "grau": return Color.GRAY; case "schwarz": return Color.BLACK; default: return Color.BLACK; } } /** * Setzt die Farbe des Objekts. * @param farbe (neue) Farbe des Objekts */ void FarbeSetzen (String farbe) { FarbeSetzen(FarbeCodieren(farbe)); } /** * Setzt die Farbe des Objekts. * @param c (neue) Farbe des Objekts */ void FarbeSetzen (Color c) { this.c = c; zeichenfläche.malfläche.repaint(); } /** * Setzt die Sichtbarkeit des Objekts. * @param sichtbar (neue) Sichtbarkeit des Objekts */ void SichtbarkeitSetzen (boolean sichtbar) { this.sichtbar = sichtbar; zeichenfläche.malfläche.repaint(); } /** * Setzt den Drehwinkel des Objekts. * @param winkel der (neue) Drehwinkel des Objekts */ void WinkelSetzen (int winkel) { this.winkel = WinkelNormieren(winkel); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Entfernt das Objekt aus dem Zeichenfenster. */ void Entfernen () { synchronized (zeichenfläche.malfläche) { zeichenfläche.alleSymbole.remove(this); zeichenfläche.malfläche.repaint(); } } /** * Bringt das Objekt eine Ebene nach vorn. */ void NachVornBringen () { synchronized (zeichenfläche.malfläche) { int index = zeichenfläche.alleSymbole.indexOf(this); if (index < zeichenfläche.alleSymbole.size() - 1) { zeichenfläche.alleSymbole.set(index, zeichenfläche.alleSymbole.get(index + 1)); zeichenfläche.alleSymbole.set(index + 1, this); zeichenfläche.malfläche.repaint(); } } } /** * Bringt das Objekt in die vorderste Ebene. */ void GanzNachVornBringen () { synchronized (zeichenfläche.malfläche) { int index = zeichenfläche.alleSymbole.indexOf(this); if (index < zeichenfläche.alleSymbole.size() - 1) { zeichenfläche.alleSymbole.remove(index); zeichenfläche.alleSymbole.add(this); zeichenfläche.malfläche.repaint(); } } } /** * Bringt das Objekt eine Ebene nach hinten. */ void NachHintenBringen () { synchronized (zeichenfläche.malfläche) { int index = zeichenfläche.alleSymbole.indexOf(this); if (index > 0) { zeichenfläche.alleSymbole.set(index, zeichenfläche.alleSymbole.get(index - 1)); zeichenfläche.alleSymbole.set(index - 1, this); zeichenfläche.malfläche.repaint(); } } } /** * Bringt das Objekt in die hinterste Ebene. */ void GanzNachHintenBringen () { synchronized (zeichenfläche.malfläche) { int index = zeichenfläche.alleSymbole.indexOf(this); if (index > 0) { zeichenfläche.alleSymbole.remove(index); zeichenfläche.alleSymbole.add(0, this); zeichenfläche.malfläche.repaint(); } } } /** * Testet, ob der angegebene Punkt innerhalb der Figur ist. * @param x x-Koordinate des zu testenden Punktes * @param y y-Koordinate des zu testenden Punktes * @return wahr, wenn der Punkt innerhalb der Figur ist */ boolean IstInnerhalb (int x, int y) { return form.contains(x, y); } /** * Testet, ob die beiden Figuren überlappen. * @param wen die andere Form * @return wahr, wenn die beiden Formen überlappen. */ boolean Schneidet (Area wen) { Area area = new Area(form); area.intersect (wen); return !area.isEmpty(); } /** * Zeichnet das Objekt * @param g das Grafikobjekt zum Zeichnen */ void Zeichnen(Graphics g) { g.setColor(c); ((Graphics2D) g).fill(form); } /** * Berechnet den Drehwinkel gemäß den Konventionen des Graphik-Frameworks. * Für Java: Winkel in Radians, positive Drehrichtng im Uhrzeiger. * @param winkel: Der Winkel in Grad, mathematischer Drehsinn * @return Winkel für Graphik-Framework */ double DrehwinkelGeben (int winkel) { return - Math.PI * winkel / 180.0; } /** * Erstellt die Form des Objekts. */ abstract void FormErzeugen(); } /** * Objekte dieser Klasse verwalten ein Rechteck. */ private class RechteckIntern extends GrafikSymbol { /** * Erstellt die Form des Rechtecks. */ @Override void FormErzeugen() { AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (winkel), this.x + b / 2, this.y + h / 2); form = new Area(new Path2D.Double (new Rectangle2D.Double(this.x, this.y, b, h), a)); } } /** * Objekte dieser Klasse verwalten eine Ellipse. */ private class EllipseIntern extends GrafikSymbol { /** * Erstellt die Form der Ellipse. */ @Override void FormErzeugen() { AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (winkel), this.x + b / 2, this.y + h / 2); form = new Area(new Path2D.Double (new Ellipse2D.Double(this.x, this.y, b, h), a)); } } /** * Objekte dieser Klasse verwalten ein Dreieck. */ private class DreieckIntern extends GrafikSymbol { /** * Erstellt die Form des Dreiecks. */ @Override void FormErzeugen() { Polygon rand = new Polygon (new int [] {x + b / 2, x + b, x, x + b / 2}, new int [] {y, y + h, y + h, y}, 4); AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (winkel), this.x + b / 2, this.y + h / 2); form = new Area(new Path2D.Double (rand, a)); } } /** * Objekte dieser Klasse verwalten einen Text. */ class TextIntern extends GrafikSymbol { /** Der aktuelle Text. */ private String text; /** Die aktuelle Textgröße. */ float size; /** * Belegt text und size mit Defaultwerten. */ TextIntern () { super(); text = "Text"; size = 12; c = Color.black; } /** * Erstellt die Form des Textes. * Dummy, legt ein leeres Area an. */ @Override void FormErzeugen() { form = new Area(); } /** * Testet, ob der angegebene Punkt innerhalb der Figur ist. * @param x x-Koordinate des zu testenden Punktes * @param y y-Koordinate des zu testenden Punktes * @return falsch */ @Override boolean IstInnerhalb (int x, int y) { return false; } /** * Setzt den aktuellen Text. * @param t der neue Text */ void TextSetzen (String t) { text = t; zeichenfläche.malfläche.repaint(); } /** * Setzt die Größe des Textes. */ void TextGrößeSetzen (int größe) { size = größe; zeichenfläche.malfläche.repaint(); } /** * Vergrößert den Text. */ void TextVergrößern() { if (size <= 10) { size += 1; } else if (size <= 40) { size += 2; } else { size += 4; } zeichenfläche.malfläche.repaint(); } /** * Verkleinert den Text. */ void TextVerkleinern() { if (size <= 10) { size -= 1; } else if (size <= 40) { size -= 2; } else { size -= 4; } if (size < 1) { size = 1; } zeichenfläche.malfläche.repaint(); } /** * Zeichnet das Objekt als Dreieck in der gegebenen Farbe. * @param g das Grafikobjekt zum Zeichnen */ @Override void Zeichnen(Graphics g) { g.setColor(c); Font f = g.getFont(); Font f2 = f.deriveFont(size); g.setFont(f2); if (winkel == 0) { g.drawString(text, x, y); } else { Graphics2D g2 = (Graphics2D) g; AffineTransform alt = g2.getTransform(); //g2.rotate(DrehwinkelGeben (winkel), x + b / 2, y + h / 2); //g2.rotate(DrehwinkelGeben (winkel), x + text.length() * size / 4, y + size / 2); Rectangle2D bounds = f2.getStringBounds(text, g2.getFontRenderContext()); g2.rotate(DrehwinkelGeben (winkel), x + bounds.getWidth() / 2, y - bounds.getHeight() / 2); g.drawString(text, x, y); g2.setTransform(alt); } g.setFont(f); } } /** * Oberklasse für alle Elemente einer Figur (Figur, Turtle). */ private abstract class FigurenElement { double xe; double ye; double breite; double höhe; Color c; /** * Zeichnet das Figurenelement. * @param g das Grafikobjekt * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ abstract void ElementZeichnen(Graphics2D g, double größe, int x, int y); /** * Fügt den Pfadteil deiser Komponente zum umgebenden Pfad hinzu. * @param p der Gesamtpfad * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ abstract void ElementZuForm (Path2D.Double p, double größe, int x, int y); } /** * Ein rechteckiges Figurenelement. */ private class FigurenElementRechteck extends FigurenElement { /** * Der Konstruktor speichert die Rahmendaten. * @param x x-Koordinate der linken oberen Ecke des Rechtecks relativ zum Aufhängepunkt. * @param y y-Koordinate der linken oberen Ecke des Rechtecks relativ zum Aufhängepunkt. * @param breite Breite des Rechtecks * @param höhe Höhe des Rechtecks * @param c Farbe des Rechtecks */ FigurenElementRechteck (double x, double y, double breite, double höhe, Color c) { this.xe = x; this.ye = y; this.breite = breite; this.höhe = höhe; this.c = c; } /** * Zeichnet das Figurenelement. * @param g das Grafikobjekt * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZeichnen(Graphics2D g, double größe, int x, int y) { g.setColor(c); g.fill(new Rectangle2D.Double (x+größe*xe/100.0, y+größe*ye/100.0, größe*breite/100.0, größe*höhe/100.0)); } /** * Fügt den Pfadteil dieser Komponente zum umgebenden Pfad hinzu. * @param p der Gesamtpfad * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZuForm (Path2D.Double p, double größe, int x, int y) { p.append(new Rectangle2D.Double (x+größe*xe/100.0, y+größe*ye/100.0, größe*breite/100.0, größe*höhe/100.0), false); } } /** * Ein elliptisches Figurenelement. */ private class FigurenElementEllipse extends FigurenElement { /** * Der Konstruktor speichert die Rahmendaten. * @param x x-Koordinate der linken oberen Ecke des umgebenden Rechtecks relativ zum Aufhängepunkt. * @param y y-Koordinate der linken oberen Ecke des umgebenden Rechtecks relativ zum Aufhängepunkt. * @param breite Breite der Ellipse * @param höhe Höhe der Ellipse * @param c Farbe der Ellipse */ FigurenElementEllipse (double x, double y, double breite, double höhe, Color c) { this.xe = x; this.ye = y; this.breite = breite; this.höhe = höhe; this.c = c; } /** * Zeichnet das Figurenelement. * @param g das Grafikobjekt * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZeichnen(Graphics2D g, double größe, int x, int y) { g.setColor(c); g.fill(new Ellipse2D.Double (x+größe*xe/100.0, y+größe*ye/100.0, größe*breite/100.0, größe*höhe/100.0)); } /** * Fügt den Pfadteil dieser Komponente zum umgebenden Pfad hinzu. * @param p der Gesamtpfad * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZuForm (Path2D.Double p, double größe, int x, int y) { p.append(new Ellipse2D.Double (x+größe*xe/100.0, y+größe*ye/100.0, größe*breite/100.0, größe*höhe/100.0), false); } } /** * Ein Figurenelement begrenzt durch das angegebene Polygon. */ private class FigurenElementPolygon extends FigurenElement { /** Das Polygonobjekt */ private Polygon poly; /** * Der Konstruktor speichert die Rahmendaten. * @param x x-Koordinaten der Stützpunkte des Polygons relativ zum Aufhängepunkt. * @param y y-Koordinaten der Stützpunkte des Polygons relativ zum Aufhängepunkt. * @param c Farbe der Polygonfläche */ FigurenElementPolygon (int [] x, int[] y, Color c) { int anz = x.length <= y.length ? x.length : y.length; poly = new Polygon (x, y, anz); Rectangle2D bounds = poly.getBounds2D(); xe = bounds.getX(); ye = bounds.getY(); breite = bounds.getWidth(); höhe = bounds.getHeight(); this.c = c; } /** * Zeichnet das Figurenelement. * @param g das Grafikobjekt * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZeichnen(Graphics2D g, double größe, int x, int y) { g.setColor(c); AffineTransform at = new AffineTransform(größe/100.0, 0, 0, größe/100.0, x, y); g.fill(new Path2D.Double (poly, at)); } /** * Fügt den Pfadteil dieser Komponente zum umgebenden Pfad hinzu. * @param p der Gesamtpfad * @param größe die aktuelle Größe der Figur * @param x die x-Koordinate des Aufhängepunkts der Figur * @param y die y-Koordinate des Aufhängepunkts der Figur */ @Override void ElementZuForm (Path2D.Double p, double größe, int x, int y) { AffineTransform at = new AffineTransform(größe/100.0, 0, 0, größe/100.0, x, y); Path2D.Double p2 = new Path2D.Double (poly, at); p2.closePath(); p2.setWindingRule(Path2D.WIND_EVEN_ODD); p.append(p2, false); } } /** * Das Objekt dieser Klasse zeichnet den Weg der Turtle. */ class TurtleIntern extends GrafikSymbol { private class LinienElement { /** x-Koordinate des Startpunktes. */ private double xStart; /** y-Koordinate des Startpunktes. */ private double yStart; /** x-Koordinate des Endpunktes. */ private double xEnde; /** y-Koordinate des Endpunktes. */ private double yEnde; /** Farbe des LinienElements. */ private Color c; LinienElement (double xStart, double yStart, double xEnde, double yEnde, Color c) { this.xStart = xStart; this.yStart = yStart; this.xEnde = xEnde; this.yEnde = yEnde; this.c = c; } void Zeichnen (Graphics2D g) { g.setColor(c); g.draw(new Line2D.Double (xStart, yStart, xEnde, yEnde)); } } /** * Verwaltet das Hintergrundfenster für die Turtlezeichnung. */ private class HintergrundBild { /** Das aktuelle Hintergrundbild. */ private BufferedImage bild; /** Das zugehörige Zeichenobjekt. */ private Graphics2D g; /** * Der Konstruktor legt das Bild in der Größe der Zeichenfläche an. */ HintergrundBild() { bild = new BufferedImage(Zeichenfenster.MalflächenBreiteGeben(), Zeichenfenster.MalflächenBreiteGeben(), BufferedImage.TYPE_INT_ARGB); g = bild.createGraphics(); g.setColor(new Color (0, 0, 0, 0)); g.fillRect(0, 0, bild.getWidth(), bild.getHeight()); } /** * Zeichent die angegebe Linie in das Bild. * @param linie das zu zeichnende Linienelement. */ void LinieZeichnen(LinienElement linie) { linie.Zeichnen(g); } /** * Zeichnet das Bild in das angegebene Zeichenobjekt. * @param wohin Zeichenobjekt */ void BildZeichnen (Graphics2D wohin) { wohin.drawImage(bild, null, 0, 0); } } /** Genaue x-Koordinate der Turtle. */ double xD; /** Genaue y-Koordinate der Turtle. */ double yD; /** Startkoordinate der Turtle. */ private int homeX; /** Startkoordinate der Turtle. */ private int homeY; /** Startwinkel der Turtle. */ private int homeWinkel; /** Stiftposition. */ boolean stiftUnten; /** Die Sichtbarkeit des Turtle-Symbols. */ private boolean symbolSichtbar; /** Linienelemente. */ private ArrayList linien; /** Standardfigur für Turtle. */ private LinkedList standardFigur; /** Das Hintergrundbild für die Linien. */ private HintergrundBild hintergrund; /** * Legt die Turtle mit Startpunkt (100|200) in Richtung 0˚ an. */ TurtleIntern () { super (); x = 100; y = 200; xD = x; yD = y; h = 40; b = 40; homeX=x; homeY=y; homeWinkel=winkel; c = Color.black; stiftUnten = true; symbolSichtbar = true; linien = new ArrayList(); hintergrund = new HintergrundBild(); standardFigur = new LinkedList(); StandardfigurErzeugen(); FormErzeugen(); } /** * Baut die Standardfigur aus den Elementen auf. */ private void StandardfigurErzeugen() { //Kopf standardFigur.add(new FigurenElementEllipse(50, -12.5, 25, 25, Color.GREEN)); //Beine standardFigur.add(new FigurenElementEllipse(22.5, -32.5, 12.5, 17.5, Color.GREEN)); standardFigur.add(new FigurenElementEllipse(40.0, -32.5, 12.5, 17.5, Color.GREEN)); standardFigur.add(new FigurenElementEllipse(22.5, 15.0, 12.5, 17.5, Color.GREEN)); standardFigur.add(new FigurenElementEllipse(40.0, 15.0, 12.5, 17.5, Color.GREEN)); //Augen standardFigur.add(new FigurenElementRechteck(67.5, -10.0, 5.0, 7.5, c)); standardFigur.add(new FigurenElementRechteck(67.5, 2.5, 5.0, 7.5, c)); //Schwanz standardFigur.add(new FigurenElementEllipse(0, -3.75, 25, 7.5, c)); //Rumpf standardFigur.add(new FigurenElementEllipse(7.5, -23.75, 57.5, 47.5, braun)); } /** * Passt das Hintergrundbild an eine neue Größe der Zeichenfläche an. */ void NeueGrößeSetzen() { hintergrund = new HintergrundBild(); for (LinienElement l: linien) { hintergrund.LinieZeichnen(l); } } /** * Erstellt die Form der Turtle. */ @Override void FormErzeugen() { Area area = new Area(); AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (winkel), this.x, this.y); double größe = h > b ? b : h; if (standardFigur != null) { synchronized (standardFigur) { for (FigurenElement e: standardFigur) { Path2D.Double p = new Path2D.Double(); e.ElementZuForm(p, größe, x, y); area.add( new Area(new Path2D.Double (p, a))); } } } form = area; } /** * Setzt die Position (der linken oberen Ecke) des Objekts. * @param x x-Position der linken oberen Ecke * @param y y-Position der linken oberen Ecke */ @Override void PositionSetzen (int x, int y) { super.PositionSetzen (x, y); xD = x; yD = y; } /** * Setzt die Turtle wieder an ihre Ausgangsposition. */ void ZumStartpunktGehen() { x = homeX; y = homeY; xD = x; yD = y; winkel = homeWinkel; FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Bewegt die Turtle nach vorne. * @param länge Anzahl der Längeneinheiten */ void Gehen(double länge) { double neuX = xD + Math.cos(DrehwinkelGeben (winkel)) * länge; double neuY = yD + Math.sin(DrehwinkelGeben (winkel)) * länge; if (stiftUnten) { synchronized (this) { LinienElement l = new LinienElement(xD, yD, neuX, neuY, c); linien.add (l); hintergrund.LinieZeichnen (l); } } xD = neuX; yD = neuY; x =(int) Math.round(xD); y =(int) Math.round(yD); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Dreht die Turtle * @param grad Drehwinkel im Gradmass */ void Drehen(int grad) { winkel = WinkelNormieren(winkel + grad); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Versetzt Zeichenfläche und Turtle in den Ausgangszustand */ void Löschen() { linien.clear(); hintergrund = new HintergrundBild(); ZumStartpunktGehen(); } /** * Turtle wechselt in den Modus "nicht zeichnen" */ void StiftHeben() { stiftUnten = false; } /** * Turtle wechselt in den Modus "zeichnen" */ void StiftSenken() { stiftUnten = true; } /** * Schaltet die Sichtbarkeit des Turtlesymbols ein oder aus. * Erlaubte Parameterwerte: true, false * @param sichtbar (neue) Sichtbarkeit des Turtlesymbols */ void SichtbarkeitFürSymbolSetzen (boolean sichtbar) { symbolSichtbar = sichtbar; zeichenfläche.malfläche.repaint(); } /** * Testet, ob der angegebene Punkt innerhalb der Figur ist. * @param x x-Koordinate des zu testenden Punktes * @param y y-Koordinate des zu testenden Punktes * @return wahr, wenn der Punkt innerhalb der Figur ist */ /** * Testet, ob die Turtle eine (sichtbare) Figur berührt. * @return true, wenn die Turtlekoordinaten innerhalb einer Grafikfigur sind */ boolean Berührt () { for (GrafikSymbol g: zeichenfläche.alleSymbole) { if ((g != this) && g.IstInnerhalb(x, y) && g.sichtbar && (!(g instanceof TurtleIntern) || ((TurtleIntern) g).symbolSichtbar)) { return true; } } return false; } /** * Testet, ob die Turtle eine (sichtbare) Figur in der angegebenen Farbe berührt. * Bei Überlappungen * @param farbe die Farbe, die die berührte Figur haben muss. * @return true, wenn die Turtlekoordinaten innerhalb einer Grafikfigur in der angegebenen Farbe sind */ boolean Berührt (String farbe) { Color c2 = FarbeCodieren(farbe); boolean ok = false; for (GrafikSymbol g: zeichenfläche.alleSymbole) { if ((g != this) && g.IstInnerhalb(x, y) && g.sichtbar) { if (g instanceof TurtleIntern) { TurtleIntern t = (TurtleIntern) g; if (t.symbolSichtbar) { for (FigurenElement e: t.standardFigur) { Path2D.Double p = new Path2D.Double(); double größe = t.h > t.b ? t.b : t.h; e.ElementZuForm(p, größe, t.x, t.y); AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (t.winkel), t.x, t.y); p = new Path2D.Double (p, a); if (p.contains(x, y)) { ok = c2.equals(e.c); } } } } else if (g instanceof FigurIntern) { FigurIntern t = (FigurIntern) g; LinkedList figur = ((t.eigeneFigur == null) || (t.eigeneFigur.size() == 0)) ? t.standardFigur : t.eigeneFigur; for (FigurenElement e: figur) { Path2D.Double p = new Path2D.Double(); double größe = t.h > t.b ? t.b : t.h; e.ElementZuForm(p, größe, t.x, t.y); AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (t.winkel), t.x, t.y); p = new Path2D.Double (p, a); if (p.contains(x, y)) { ok = c2.equals(e.c); } } } else { ok = ok || c2.equals(g.c); } } } return ok; } /** * Testet, ob die Turtle die (sichtbare, ) angegebene Figur berührt. * @param object das Objekt, das getestet werden soll. * @return true, wenn die Turtlekoordinaten innerhalb einer Grafikfigur in der angegebenen Farbe sind */ boolean Berührt (Object object) { GrafikSymbol s = null; if (object instanceof Rechteck) { s = ((Rechteck) object).symbol; } else if (object instanceof Dreieck) { s = ((Dreieck) object).symbol; } else if (object instanceof Kreis) { s = ((Kreis) object).symbol; } else if (object instanceof Turtle) { s = ((Turtle) object).symbol; } else if (object instanceof Figur) { s = ((Figur) object).symbol; } return (s != null) && (s != this) && s.IstInnerhalb(x, y) && s.sichtbar && (!(s instanceof TurtleIntern) || ((TurtleIntern) s).symbolSichtbar); } /** * Zeichnet das Objekt als Dreieck in der gegebenen Farbe. * @param g das Grafikobjekt zum Zeichnen */ @Override void Zeichnen(Graphics g) { Graphics2D g2 = (Graphics2D) g; synchronized (this) { hintergrund.BildZeichnen(g2); } if (symbolSichtbar) { g.setColor(Color.black); double größe = h > b ? b : h; AffineTransform alt = g2.getTransform(); g2.rotate(DrehwinkelGeben (winkel), x, y); if (standardFigur != null) { synchronized (standardFigur) { for (FigurenElement e: standardFigur) { e.ElementZeichnen(g2, größe, x, y); } } } g2.setTransform(alt); } } } /** * Das Objekt dieser Klasse ist ein in der Gestalt definierbarer Akteur. */ class FigurIntern extends GrafikSymbol { /** Genaue x-Koordinate der Figur. */ double xD; /** Genaue y-Koordinate der Figur. */ double yD; /** Startkoordinate der Figur. */ private int homeX; /** Startkoordinate der Figur. */ private int homeY; /** Startwinkel der Figur. */ private int homeWinkel; /** Eigene Figur für Figur. */ private LinkedList eigeneFigur; /** Standardfigur für Figur. */ private LinkedList standardFigur; /** * Legt die Figur mit Startpunkt (100|200) in Richtung 0˚ an. */ FigurIntern () { super (); x = 100; y = 200; xD = x; yD = y; h = 40; b = 40; homeX=x; homeY=y; homeWinkel=winkel; c = Color.black; eigeneFigur = new LinkedList(); standardFigur = new LinkedList(); StandardfigurErzeugen(); FormErzeugen(); } /** * Baut die Standardfigur aus den Elementen auf. */ private void StandardfigurErzeugen() { int[] x = new int [] {-50, 50, -50}; int[] y = new int [] {-50, 0, 50}; standardFigur.add (new FigurenElementPolygon (x, y, Color.yellow)); standardFigur.add(new FigurenElementEllipse(-10, -10, 20, 20, Color.blue)); } /** * Erstellt die Form der Figur. */ @Override void FormErzeugen() { Area area = new Area(); AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (winkel), this.x, this.y); double größe = h > b ? b : h; if (standardFigur != null) { LinkedList figur = ((eigeneFigur == null) || (eigeneFigur.size() == 0)) ? standardFigur : eigeneFigur; synchronized (figur) { for (FigurenElement e: figur) { Path2D.Double p = new Path2D.Double(); e.ElementZuForm(p, größe, x, y); area.add(new Area(new Path2D.Double (p, a))); } } } form = area; } /** * Setzt die Position (der Mitte) des Objekts. * @param x x-Position der Mitte * @param y y-Position der Mitte */ @Override void PositionSetzen (int x, int y) { super.PositionSetzen (x, y); xD = x; yD = y; } /** * Setzt die Figur wieder an ihre Ausgangsposition. */ void ZumStartpunktGehen() { x = homeX; y = homeY; xD = x; yD = y; winkel = homeWinkel; FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Bewegt die Figur nach vorne. * @param länge Anzahl der Längeneinheiten */ void Gehen(double länge) { double neuX = xD + Math.cos(DrehwinkelGeben (winkel)) * länge; double neuY = yD + Math.sin(DrehwinkelGeben (winkel)) * länge; xD = neuX; yD = neuY; x =(int) Math.round(xD); y =(int) Math.round(yD); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Dreht die Figur * @param grad Drehwinkel im Gradmass */ void Drehen(int grad) { winkel = WinkelNormieren(winkel + grad); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Testet, ob die Figur eine (sichtbare) Grafik-Figur berührt. * @return true, wenn die Figurkoordinaten innerhalb einer Grafikfigur sind */ boolean Berührt () { for (GrafikSymbol g: zeichenfläche.alleSymbole) { if ((g != this) && g.Schneidet(form) && g.sichtbar && (!(g instanceof TurtleIntern) || ((TurtleIntern) g).symbolSichtbar)) { return true; } } return false; } /** * Testet, ob die Figur eine (sichtbare) Grafik-Figur in der angegebenen Farbe berührt. * Bei Überlappungen * @param farbe die Farbe, die die berührte Figur haben muss. * @return true, wenn die Figurkoordinaten innerhalb einer Grafikfigur in der angegebenen Farbe sind */ boolean Berührt (String farbe) { Color c2 = FarbeCodieren(farbe); boolean ok = false; for (GrafikSymbol g: zeichenfläche.alleSymbole) { if ((g != this) && g.Schneidet(form) && g.sichtbar) { if (g instanceof TurtleIntern) { TurtleIntern t = (TurtleIntern) g; if (t.symbolSichtbar) { Area[] areas = new Area [t.standardFigur.size()]; Color[] colors = new Color [t.standardFigur.size()]; AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (t.winkel), t.x, t.y); int pos = 0; for (FigurenElement e: t.standardFigur) { Path2D.Double p = new Path2D.Double(); double größe = t.h > t.b ? t.b : t.h; e.ElementZuForm(p, größe, t.x, t.y); p = new Path2D.Double (p, a); areas [pos] = new Area(p); colors[pos] = e.c; for (int i = pos - 1; i >= 0;i--) { areas[i].subtract(areas[pos]); } pos += 1; } for (int i = 0; i < areas.length; i++) { if (Schneidet(areas[i]) && (c2.equals(colors[i]))) { ok = true; } } } } else if (g instanceof FigurIntern) { FigurIntern t = (FigurIntern) g; LinkedList figur = ((t.eigeneFigur == null) || (t.eigeneFigur.size() == 0)) ? t.standardFigur : t.eigeneFigur; Area[] areas = new Area [figur.size()]; Color[] colors = new Color [figur.size()]; AffineTransform a = new AffineTransform(); a.rotate(DrehwinkelGeben (t.winkel), t.x, t.y); int pos = 0; for (FigurenElement e: figur) { Path2D.Double p = new Path2D.Double(); double größe = t.h > t.b ? t.b : t.h; e.ElementZuForm(p, größe, t.x, t.y); p = new Path2D.Double (p, a); areas [pos] = new Area(p); colors[pos] = e.c; for (int i = pos - 1; i >= 0;i--) { areas[i].subtract(areas[pos]); } pos += 1; } for (int i = 0; i < areas.length; i++) { if (Schneidet(areas[i]) && (c2.equals(colors[i]))) { ok = true; } } } else { ok = ok || c2.equals(g.c); } } } return ok; } /** * Testet, ob die Figur die (sichtbare, ) angegebene Grafik-Figur berührt. * @param object das Objekt, das getestet werden soll. * @return true, wenn die Turtlekoordinaten innerhalb einer Grafikfigur in der angegebenen Farbe sind */ boolean Berührt (Object object) { GrafikSymbol s = null; if (object instanceof Rechteck) { s = ((Rechteck) object).symbol; } else if (object instanceof Dreieck) { s = ((Dreieck) object).symbol; } else if (object instanceof Kreis) { s = ((Kreis) object).symbol; } else if (object instanceof Turtle) { s = ((Turtle) object).symbol; } else if (object instanceof Figur) { s = ((Figur) object).symbol; } return (s != null) && (s != this) && s.Schneidet(form) && s.sichtbar && (!(s instanceof TurtleIntern) || ((TurtleIntern) s).symbolSichtbar); } /** * Erzeugt ein neues, rechteckiges Element einer eigenen Darstellung der Figur. * Alle Werte beziehen sich auf eine Figur der Größe 100 und den Koordinaten (0|0) in der Mitte des Quadrats * @param x x-Wert der linken oberen Ecke des Rechtecks * @param y y-Wert der linken oberen Ecke des Rechtecks * @param breite Breite des Rechtecks * @param höhe Höhe des Rechtecks * @param farbe (Füll)Farbe des Rechtecks */ void FigurteilFestlegenRechteck (int x, int y, int breite, int höhe, String farbe) { synchronized (eigeneFigur) { eigeneFigur.add(new FigurenElementRechteck(x, y, breite, höhe, FarbeCodieren(farbe))); } FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Erzeugt ein neues, elliptisches Element einer eigenen Darstellung der Figur. * Alle Werte beziehen sich auf eine Figur der Größe 100 und den Koordinaten (0|0) in der Mitte des Quadrats * @param x x-Wert der linken oberen Ecke des umgebenden Rechtecks der Ellipse * @param y y-Wert der linken oberen Ecke des umgebenden Rechtecks der Ellipse * @param breite Breite des umgebenden Rechtecks der Ellipse * @param höhe Höhe des umgebenden Rechtecks der Ellipse * @param farbe (Füll)Farbe der Ellipse */ void FigurteilFestlegenEllipse (int x, int y, int breite, int höhe, String farbe) { synchronized (eigeneFigur) { eigeneFigur.add(new FigurenElementEllipse(x, y, breite, höhe, FarbeCodieren(farbe))); } FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Erzeugt ein neues, dreieckiges Element einer eigenen Darstellung der Figur. * Alle Werte beziehen sich auf eine Figur der Größe 100 und den Koordinaten (0|0) in der Mitte des Quadrats * @param x1 x-Wert des ersten Punkts des Dreiecks * @param y1 y-Wert des ersten Punkts des Dreiecks * @param x2 x-Wert des zweiten Punkts des Dreiecks * @param y2 y-Wert des zweiten Punkts des Dreiecks * @param x3 x-Wert des dritten Punkts des Dreiecks * @param y3 y-Wert des dritten Punkts des Dreiecks * @param farbe (Füll)Farbe der Ellipse */ void FigurteilFestlegenDreieck (int x1, int y1, int x2, int y2, int x3, int y3, String farbe) { synchronized (eigeneFigur) { int[] x = new int [] {x1, x2, x3}; int[] y = new int [] {y1, y2, y3}; eigeneFigur.add(new FigurenElementPolygon(x, y, FarbeCodieren(farbe))); } FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Löscht die Vereinbarung für die eigene Darstellung Figur. * Die Figur wird wieder durch die Originalfigur dargestellt. */ void EigeneFigurLöschen() { eigeneFigur.clear(); FormErzeugen(); zeichenfläche.malfläche.repaint(); } /** * Zeichnet das Objekt als Dreieck in der gegebenen Farbe. * @param g das Grafikobjekt zum Zeichnen */ @Override void Zeichnen(Graphics g) { Graphics2D g2 = (Graphics2D) g; // Outline g.setColor(Color.black); Stroke stAlt = g2.getStroke(); g2.setStroke(new BasicStroke(3.0f)); g2.draw(form); g2.setStroke(stAlt); // Füllung double größe = h > b ? b : h; AffineTransform alt = g2.getTransform(); g2.rotate(DrehwinkelGeben (winkel), x, y); if (standardFigur != null) { LinkedList figur = ((eigeneFigur == null) || (eigeneFigur.size() == 0)) ? standardFigur : eigeneFigur; synchronized (figur) { for (FigurenElement e: figur) { e.ElementZeichnen(g2, größe, x, y); } } } g2.setTransform(alt); } } }