/******************************************************************************* * Copyright (c) 2012-present Jakub Kováč, Jozef Brandýs, Katarína Kotrlová, * Pavol Lukča, Ladislav Pápay, Viktor Tomkovič, Tatiana Tóthová * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package algvis.core; import algvis.core.history.HashtableStoreSupport; import algvis.core.visual.VisualElement; import algvis.core.visual.ZDepth; import algvis.ui.Fonts; import algvis.ui.view.View; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.util.Hashtable; /** * The Class Node. This is a basic element of the visualization. Nodes can be * drawn, they can move, change color, become marked/unmarked, or point in some * direction. Nodes are by default drawn as circles with their key in the * middle. */ public class Node extends VisualElement { public DataStructure D; protected int key; /** * x, y - node position tox, toy - the position, where the node is heading * steps - the number of steps to reach the destination */ public volatile int x; public volatile int y; public int tox; public int toy; protected int steps; /** the state of a node - either ALIVE, DOWN, LEFT, or RIGHT. */ public int state = ALIVE; private NodeColor color = NodeColor.NORMAL; public boolean marked = false; protected Node dir = null; private int arrow = Node.NOARROW; // NOARROW or angle (0=E, 45=SE, 90=S, // 135=SW, 180=W) private boolean arc = false; public static final int STEPS = 10; public static final int RADIUS = 10; /** * the key values are generally integers from 1 to 999 (inclusive) special * values of a key are: INF (infinity) or -INF (-infinity), these are drawn * nicely as the numeral 8 sideways NOKEY (drawn as an empty circle), and * NULL (not drawn at all) */ public static final int INF = 99999, NOKEY = -1, NULL = 100000; /** * a node can be in several different states: INVISIBLE (default starting * state, not drawn), ALIVE (visible), DOWN, LEFT, and RIGHT (the node moves * down, or diagonally left or right until it gets out of the screen, and * then turns INVISIBLE) */ public static final int INVISIBLE = -1; public static final int ALIVE = 0; public static final int DOWN = 2; public static final int LEFT = 3; public static final int RIGHT = 4; public static final int OUT = 5; public static final int UP = 6; public static final int NOARROW = -10000; public static final int DIRARROW = -10001; public static final int TOARROW = -10002; public static final int UPY = -7 * Node.RADIUS; protected Node(DataStructure D, int key, int x, int y) { this(D, key, x, y, ZDepth.NODE); } protected Node(DataStructure D, int key, int x, int y, int zDepth) { super(zDepth); this.D = D; this.setKey(key); this.x = tox = x; this.y = toy = y; steps = 0; } protected Node(DataStructure D, int key, int zDepth) { this(D, key, 0, UPY, zDepth); } public Node(Node v) { this(v.D, v.getKey(), v.x, v.y); } public void setState(int s) { state = s; } public NodeColor getColor() { return color; } public void setColor(NodeColor color) { fgColor(color.fgColor); bgColor(color.bgColor); this.color = color; } protected void fgColor(Color fg) { if (fg != color.fgColor) { color = new NodeColor(fg, color.bgColor); } } protected void bgColor(Color bg) { if (bg != color.bgColor) { color = new NodeColor(color.fgColor, bg); } } protected Color getFgColor() { return color.fgColor; } public Color getBgColor() { return color.bgColor; } /** * Set background color depending on the key (the higher the key, the darker * the color). */ protected void bgKeyColor() { bgColor(new Color(255, 255 - getKey() / 20, 0)); } public void mark() { marked = true; } public void unmark() { marked = false; } /** * Draw an arrow pointing above the node w. * * @param w */ public void pointAbove(Node w) { dir = w; arrow = Node.DIRARROW; } /** * Draw an arrow pointing to the node w. * * @param w */ public void pointTo(Node w) { dir = w; arrow = Node.TOARROW; } /** * Point in direction angle. The angle in degrees should be a nonnegative * integer 0 = RIGHT, then clockwise: 90 = DOWN, 180 = LEFT * * @param angle */ public void pointInDir(int angle) { dir = null; arrow = angle; } /** * Stop drawing an arrow. */ public void noArrow() { dir = null; arrow = Node.NOARROW; } /** * Draw an arc pointing to node w. Assumption: w is above this node. * * @param w */ public void setArc(Node w) { dir = w; arc = true; } /** * Stop drawing an arc. */ public void noArc() { arc = false; } /** * Draw bg. * * @param v * view */ protected void drawBg(View v) { v.setColor(getBgColor()); v.fillCircle(x, y, Node.RADIUS); v.setColor(Color.BLACK); // fgcolor); v.drawCircle(x, y, Node.RADIUS); if (marked) { v.drawCircle(x, y, Node.RADIUS + 2); } } /** * Convert the key into a string (INF is converted to "8" sideways). */ @Override public String toString() { if (getKey() == INF) { return "\u221e"; } else if (getKey() == -INF) { return "-\u221e"; } else { return "" + getKey(); } } protected void drawKey(View v) { v.setColor(getFgColor()); if (getKey() != NOKEY) { v.drawString(toString(), x, y, Fonts.NORMAL); } } protected void drawArrow(View v) { if (arrow == Node.NOARROW || (arrow < 0 && dir == null)) { return; } double dx, dy; if (arrow < 0) { dx = dir.x - x; if (arrow == DIRARROW) { dy = dir.y - DataStructure.minsepy - y; } else if (arrow == TOARROW) { dy = dir.y - y; } else { // vypindaj return; } final double d = Math.sqrt(dx * dx + dy * dy); dx /= d; dy /= d; } else { dx = Math.cos(arrow * Math.PI / 180); dy = Math.sin(arrow * Math.PI / 180); } double x1, y1, x2, y2; x1 = x + 1.5 * Node.RADIUS * dx; y1 = y + 1.5 * Node.RADIUS * dy; if (arrow == TOARROW) { x2 = dir.x - 1.5 * Node.RADIUS * dx; y2 = dir.y - 1.5 * Node.RADIUS * dy; } else { x2 = x1 + 2 * Node.RADIUS * dx; y2 = y1 + 2 * Node.RADIUS * dy; } v.setColor(Color.BLACK); v.drawArrow((int) x1, (int) y1, (int) x2, (int) y2); } // Assumption: dir (the node we are pointing to) is above this node protected void drawArc(View v) { if (!arc || dir == null) { return; } final int x = dir.x, y = this.y - DataStructure.minsepy + Node.RADIUS, a = Math .abs(this.x - dir.x), b = Math.abs(this.y - dir.y); v.setColor(Color.BLACK); if (this.x > dir.x) { v.drawArcArrow(x - a, y - b, 2 * a, 2 * b, 0, 90); } else { v.drawArcArrow(x - a, y - b, 2 * a, 2 * b, 180, 90); } } @Override public void draw(View v) { if (state == Node.INVISIBLE || getKey() == NULL || state == Node.OUT) { return; } drawBg(v); drawKey(v); drawArrow(v); drawArc(v); } /** * Is the given point inside the node? (Used mainly to decide whether a user * clicked at the node.) */ protected boolean inside(int x, int y) { return (this.x - x) * (this.x - x) + (this.y - y) * (this.y - y) <= Node.RADIUS * Node.RADIUS; } /** * Set new coordinates, where the node should go. */ public void goTo(int tox, int toy) { this.tox = tox; this.toy = toy; this.steps = STEPS; } /** * Go to the same position where node v is going. */ public void goTo(Node v) { goTo(v.tox, v.toy); } // public void goAbove (int tox, int toy) { goTo (tox, toy - 2*D.RADIUS - // D.yspan); } /** * Go above node v (or more precisely: above the position where v is going). */ public void goAbove(Node v) { goTo(v.tox, v.toy - DataStructure.minsepy); } // public void goNextTo (int tox, int toy) { goTo (tox + 2*D.RADIUS + // D.xspan, toy); } /** * Go next to node v (precisely to the right of where v is going). */ public void goNextTo(Node v) { goTo(v.tox + DataStructure.minsepx, v.toy); } /** * Go to the root position. */ public void goToRoot() { goTo(DataStructure.rootx, DataStructure.rooty); } /** * Go above the root position. */ public void goAboveRoot() { final int toy = DataStructure.rooty - DataStructure.minsepy; goTo(DataStructure.rootx, toy); } /** * Go downwards out of the screen. */ public void goDown() { setState(DOWN); } /** * Go left downwards out of the screen. */ public void goLeft() { setState(LEFT); } /** * Go right downwards out of the screen. */ public void goRight() { setState(RIGHT); } /** * Make one step towards the destination (tox, toy). In the special states * DOWN, LEFT, or RIGHT, go downwards off the screen. */ @Override public void move() { switch (state) { case Node.ALIVE: case Node.INVISIBLE: if (steps > 0) { x += (tox - x) / steps; y += (toy - y) / steps; --steps; } break; case Node.DOWN: case Node.LEFT: case Node.RIGHT: y += 20; if (state == Node.LEFT) { x -= 20; } else if (state == Node.RIGHT) { x += 20; } // robi problem, ked rychlo dozadu a potom rychlo dopredu if (!D.panel.screen.V.inside(x, y - Node.RADIUS)) { state = OUT; } break; case Node.UP: y -= 20; break; } } @Override public Rectangle2D getBoundingBox() { final int r = RADIUS + 1; return new Rectangle2D.Double(x - r, y - r, 2 * r, 2 * r); } @Override public void endAnimation() { if (state == ALIVE || state == INVISIBLE) { steps = 0; x = tox; y = toy; } else if (state == DOWN || state == LEFT || state == RIGHT) { while (D.panel.screen.V.inside(x, y - Node.RADIUS)) { y += 20; if (state == Node.LEFT) { x -= 20; } else if (state == Node.RIGHT) { x += 20; } } state = OUT; } } @Override public boolean isAnimationDone() { return ((steps == 0 && state == ALIVE) || state == INVISIBLE || state == OUT); } public int getKey() { return key; } public void setKey(int key) { this.key = key; } @Override public void storeState(Hashtable<Object, Object> state) { super.storeState(state); HashtableStoreSupport.store(state, hash + "key", key); HashtableStoreSupport.store(state, hash + "state", this.state); HashtableStoreSupport.store(state, hash + "tox", tox); HashtableStoreSupport.store(state, hash + "toy", toy); HashtableStoreSupport.store(state, hash + "color", color); HashtableStoreSupport.store(state, hash + "marked", marked); HashtableStoreSupport.store(state, hash + "dir", dir); HashtableStoreSupport.store(state, hash + "arrow", arrow); HashtableStoreSupport.store(state, hash + "arc", arc); } @Override public void restoreState(Hashtable<?, ?> state) { super.restoreState(state); final Object key = state.get(hash + "key"); if (key != null) { this.key = (Integer) HashtableStoreSupport.restore(key); } Object stat = state.get(hash + "state"); if (stat != null) { stat = HashtableStoreSupport.restore(stat); // tu nechcem mat invisible (inak spravit) if ((this.state == OUT || this.state == DOWN || this.state == LEFT || this.state == RIGHT || this.state == UP) && stat.equals(ALIVE)) { goTo(tox, toy); } this.state = (Integer) stat; } boolean isMoved = false; Object tox = state.get(hash + "tox"); Object toy = state.get(hash + "toy"); if (tox != null) { isMoved = true; } else { tox = this.tox; } if (toy != null) { isMoved = true; } else { toy = this.toy; } if (isMoved) { goTo((Integer) tox, (Integer) toy); } final Object color = state.get(hash + "color"); if (color != null) { this.color = (NodeColor) HashtableStoreSupport.restore(color); } final Object marked = state.get(hash + "marked"); if (marked != null) { this.marked = (Boolean) HashtableStoreSupport.restore(marked); } final Object dir = state.get(hash + "dir"); if (dir != null) { this.dir = (Node) HashtableStoreSupport.restore(dir); } final Object arrow = state.get(hash + "arrow"); if (arrow != null) { this.arrow = (Integer) HashtableStoreSupport.restore(arrow); } final Object arc = state.get(hash + "arc"); if (arc != null) { this.arc = (Boolean) HashtableStoreSupport.restore(arc); } } }