/* ** This file is part of Filius, a network construction and simulation software. ** ** Originally created at the University of Siegen, Institute "Didactics of ** Informatics and E-Learning" by a students' project group: ** members (2006-2007): ** André Asschoff, Johannes Bade, Carsten Dittich, Thomas Gerding, ** Nadja Haßler, Ernst Johannes Klebert, Michell Weyer ** supervisors: ** Stefan Freischlad (maintainer until 2009), Peer Stechert ** Project is maintained since 2010 by Christian Eibl <filius@c.fameibl.de> ** and Stefan Freischlad ** Filius is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 2 of the License, or ** (at your option) version 3. ** ** Filius is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied ** warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ** PURPOSE. See the GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with Filius. If not, see <http://www.gnu.org/licenses/>. */ package filius.gui.netzwerksicht; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.MouseEvent; import java.awt.geom.QuadCurve2D; import java.util.ListIterator; import java.util.Observable; import java.util.Observer; import javax.swing.DebugGraphics; import filius.Main; import filius.gui.GUIContainer; import filius.hardware.NetzwerkInterface; import filius.hardware.Port; import filius.hardware.knoten.InternetKnoten; /** * * @author Johannes Bade */ public class JCablePanel extends javax.swing.JPanel implements Observer { /** * */ private static final long serialVersionUID = 1L; private GUIKnotenItem ziel1, ziel2; private GUIKabelItem kabelItem; private boolean kurven = true; private QuadCurve2D currCurve=null; private Color kabelFarbe = new Color(64, 64, 64); private final Color farbeStandard = new Color(64, 64, 64); private final Color farbeBlinken = new Color(0, 255, 64); public JCablePanel() { super(); this.setOpaque(false); } public void updateBounds() { Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (JCablePanel), updateBounds()"); int x1, x2, y1, y2, t1; // Main.debug.println("DEBUG updateBounds, ziel1: (" // + ziel1.getImageLabel().getX() // + "/" // + ziel1.getImageLabel().getY() // + ") [W=" // + ziel1.getImageLabel().getWidth() // + "; H=" // + ziel1.getImageLabel().getHeight() // + "]"); // Main.debug.println("DEBUG updateBounds, ziel2: (" // + ziel2.getImageLabel().getX() // + "/" // + ziel2.getImageLabel().getY() // + ") [W=" // + ziel2.getImageLabel().getWidth() // + "; H=" // + ziel2.getImageLabel().getHeight() // + "]"); // Theoretisch korrekte Positionen x1 = (int) (ziel1.getImageLabel().getX()); x2 = (int) (ziel2.getImageLabel().getX()); y1 = (int) (ziel1.getImageLabel().getY()); y2 = (int) (ziel2.getImageLabel().getY()); x1 = (int) (x1 + (0.5 * ziel1.getImageLabel().getWidth())); y1 = (int) (y1 + (0.5 * ziel1.getImageLabel().getHeight())); x2 = (int) (x2 + (0.5 * ziel2.getImageLabel().getWidth())); y2 = (int) (y2 + (0.5 * ziel2.getImageLabel().getHeight())); // Absolut korrekte Positionen (also Sidebar und Menu rausgerechnet) if (x1 > x2) { t1 = x1; x1 = x2; x2 = t1; } if (y1 > y2) { t1 = y1; y1 = y2; y2 = t1; } setBounds(x1-2, y1-2, x2 - x1 +4, y2 - y1+4); // add 2 for each direction to take care of linewidth Main.debug.println("JCablePanel ("+this.hashCode()+"), bounds: "+x1+"/"+y1+", "+x2+"/"+y2+" (W:"+(x2-x1)+", H:"+(y2-y1)+")"); } protected void paintComponent(Graphics g) { Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (JCablePanel), paintComponent("+g+")"); super.paintComponent(g); int x1, x2, y1, y2; // Theoretisch korrekte Positionen x1 = (int) (ziel1.getImageLabel().getX() + (0.5 * ziel1.getImageLabel().getWidth())); x2 = (int) (ziel2.getImageLabel().getX() + (0.5 * ziel2.getImageLabel().getWidth())); y1 = (int) (ziel1.getImageLabel().getY() + (0.5 * ziel1.getImageLabel().getHeight())); y2 = (int) (ziel2.getImageLabel().getY() + (0.5 * ziel2.getImageLabel().getHeight())); /* Einfaches Zeichnen */ g.setColor(kabelFarbe); Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(2)); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (isKurven()) { int kp1 = (x1 - this.getX() + x2 - this.getX()) / 4; if( (x1>x2 && y1>y2) || (x1<x2 && y1<y2)) kp1 = 3*kp1; // correct X value of control point for falling lines (upper left to lower right corner) int kp2 = (y1 - this.getY() + y2 - this.getY()) / 4; // Main.debug.println("\trect: ("+x1+"/"+y1+") -> ("+kp1+"/"+kp2+") -> ("+x2+"/"+y2+"), thisXY: ("+this.getX()+"/"+this.getY()+"), color: "+kabelFarbe); QuadCurve2D myCurve = new QuadCurve2D.Double(x1 - this.getX(), y1 - this.getY(), // Punkt 1 kp1, kp2, // Kontrollpunkt k x2 - this.getX(), y2 - this.getY() // Punkt 2 ); // Kurve malen g2.draw(myCurve); this.currCurve = myCurve; } else { g2.drawLine(x1 - this.getX(), y1 - this.getY(), x2 - this.getX(), y2 - this.getY()); } this.setOpaque(false); } /* * Method to examine whether the mouse was clicked close to a line representing a cable in filius. * ATTENTION: There are several workarounds necessary to cope with Java's strange boundary handling. * Thus, the actual bounds are tested prior to actually use them for determining a point to be * inside a bound of a curve or line, respectively. */ public boolean clicked(int x, int y) { Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (JCablePanel), clicked("+x+","+y+")"); if(this.getHeight()<20 || this.getWidth()<20) return true; // very slim panel --> always within collision area (incl. tolerance) int deltaX=0; int deltaY=0; int xFactor=1; // factor for adjusting the deltaX sign according to orientation of line double tmpX=0; double tmpY=0; boolean invers = false; if(isKurven()) { // test bounds placement and behaviour tmpX = this.currCurve.getCtrlX(); tmpY = this.currCurve.getCtrlY() - 2; if(!this.currCurve.contains(tmpX, tmpY)) { // unexpected behaviour: bound is considered to be above curve invers = true; } if(this.getWidth() < this.getHeight()) { deltaX=10; if(this.currCurve.getY1() > this.currCurve.getY2()) { // line is "falling" from upper left to lower left corner; --> invert deltaX to move rightwards xFactor = -1; } } else { deltaY=10; } // FIXME: still suboptimal; bounds seem to be strange concepts (probably only are of closed curve considered as "bound") QuadCurve2D topCurve = new QuadCurve2D.Double( this.currCurve.getX1() - (xFactor * deltaX), this.currCurve.getY1() + deltaY, this.currCurve.getCtrlX() - (xFactor * deltaX), this.currCurve.getCtrlY() + deltaY, this.currCurve.getX2() - (xFactor * deltaX), this.currCurve.getY2() + deltaY ); QuadCurve2D bottomCurve = new QuadCurve2D.Double( this.currCurve.getX1() + (xFactor * deltaX), this.currCurve.getY1() - deltaY, this.currCurve.getCtrlX() + (xFactor * deltaX), this.currCurve.getCtrlY() - deltaY, this.currCurve.getX2() + (xFactor * deltaX), this.currCurve.getY2() - deltaY ); // Main.debug.println("DEBUG ("+this.hashCode()+") "+getClass()+" (JCablePanel), clicked:\n\tclicked within bounds of topCurve "+ // topCurve.getBounds()+": "+ // topCurve.contains(x-this.getX(), y-this.getY())); // Main.debug.println("DEBUG ("+this.hashCode()+") "+getClass()+" (JCablePanel), clicked:\n\tclicked within bounds of bottomCurve "+ // bottomCurve.getBounds()+": "+ // bottomCurve.contains(x-this.getX(), y-this.getY())); if(!invers) { // Main.debug.println("DEBUG ("+this.hashCode()+") "+getClass()+" (JCablePanel), clicked:\n\tclicked between those bounds ["+(x-this.getX())+"/"+(y-this.getY())+"] (final result): "+ // (topCurve.contains(x-this.getX(), y-this.getY()) && // !bottomCurve.contains(x-this.getX(), y-this.getY())) // ); return (topCurve.contains(x-this.getX(), y-this.getY()) && !bottomCurve.contains(x-this.getX(), y-this.getY())); } else { // Main.debug.println("DEBUG ("+this.hashCode()+") "+getClass()+" (JCablePanel), clicked:\n\tclicked between those bounds ["+(x-this.getX())+"/"+(y-this.getY())+"] (final result, INVERSE): "+ // (topCurve.contains(x-this.getX(), y-this.getY()) && // !bottomCurve.contains(x-this.getX(), y-this.getY())) // ); return (!topCurve.contains(x-this.getX(), y-this.getY()) && bottomCurve.contains(x-this.getX(), y-this.getY())); } } else { // cables represented by simple lines // FIXME return true; } } public GUIKnotenItem getZiel1() { return ziel1; } public void setZiel1(GUIKnotenItem ziel1) { this.ziel1 = ziel1; } public GUIKnotenItem getZiel2() { return ziel2; } public void setZiel2(GUIKnotenItem ziel2) { this.ziel2 = ziel2; updateBounds(); } /** * @author Johannes Bade & Thomas Gerding * * Gibt zurück ob die Kabel als Geraden oder Kurven dargestellt werden. * * @return boolean */ public boolean isKurven() { return kurven; } /** * @author Johannes Bade & Thomas Gerding * * Gibt an ob die Kabel als Geraden oder Kurven dargestellt werden. * * * @param boolean */ public void setKurven(boolean kurven) { this.kurven = kurven; } /** * @author Johannes Bade * * Wird genutzt um Kabel blinken zu lassen :) */ public void update(Observable o, Object arg) { Main.debug.println("INVOKED ("+this.hashCode()+") "+getClass()+" (JCablePanel), update("+o+","+arg+")"); if (arg.equals(Boolean.TRUE)) { kabelFarbe = farbeBlinken; this.setLocation(this.getX() - 1, this.getY()); this.setLocation(this.getX() + 1, this.getY()); } else { kabelFarbe = farbeStandard; } updateUI(); } }