001    // GraphLab Project: http://graphlab.sharif.edu
002    // Copyright (C) 2008 Mathematical Science Department of Sharif University of Technology
003    // Distributed under the terms of the GNU General Public License (GPL): http://www.gnu.org/licenses/
004    package graphlab.graph.graph;
005    
006    import graphlab.graph.event.EdgeEvent;
007    import graphlab.graph.event.GraphControlListener;
008    import graphlab.graph.event.GraphEvent;
009    import graphlab.graph.event.VertexEvent;
010    import graphlab.library.util.Pair;
011    import graphlab.platform.core.BlackBoard;
012    
013    import javax.swing.*;
014    import java.awt.event.MouseEvent;
015    import java.awt.event.MouseListener;
016    import java.awt.event.MouseMotionListener;
017    import java.awt.event.MouseWheelListener;
018    import java.awt.geom.Line2D;
019    import java.util.Iterator;
020    
021    /**
022     * @author Azin Azadi, roozbeh ebrahimi, Ali Ershadi
023     */
024    public class GraphControl implements MouseListener, MouseWheelListener, MouseMotionListener {
025        private GraphModel g;
026        private JPanel gv;
027        BlackBoard blackboard;
028        private GraphControlListener listener;
029        private VertexModel lastVertexPressed = null;
030        private EdgeModel lastEdgePressed = null;
031        GraphPoint p = new GraphPoint();
032    //    boolean edgesCurved;
033    
034        public static final int EDGE_CURVE_CPNTROL_BOX_DIAMETER = 10;
035        /**
036         * just a short cut to EDGE_CURVE_CPNTROL_BOX_DIAMETER
037         */
038        private static final int cd = EDGE_CURVE_CPNTROL_BOX_DIAMETER;
039        /**
040         * determines the min x and y of graph bounds
041         */
042        double minx = 0;
043        double miny = 0;
044    
045        public void setListener(GraphControlListener l) {
046            this.listener = l;
047        }
048    
049        public GraphControl(GraphModel g, JPanel gv, BlackBoard bb) {
050            this.g = g;
051            this.gv = gv;
052            blackboard = bb;
053            gv.addMouseListener(this);
054            gv.addMouseMotionListener(this);
055            gv.addMouseWheelListener(this);
056        }
057    
058    //todo: single click != double click
059    
060        public void mouseClicked(MouseEvent mouseEvent) {
061            Pair<VertexModel, Double> p = mindistv(g, mousePos(mouseEvent));
062            VertexModel v = (VertexModel) p.first;
063            if (v != null && isPointOnVertex(g, v, mousePos(mouseEvent))) {
064                if (mouseEvent.getClickCount() > 1)
065                    sendEventToBlackBoard(VertexEvent.doubleClicked(v, mousePos(mouseEvent, v), mouseEvent.getButton(), mouseEvent.getModifiersEx()));
066                else
067                    sendEventToBlackBoard(VertexEvent.clicked(v, mousePos(mouseEvent, v), mouseEvent.getButton(), mouseEvent.getModifiersEx()));
068                return;
069            }
070            Pair<EdgeModel, Double> pp;
071            pp = mindiste(g, mousePos(mouseEvent));
072            EdgeModel e = (EdgeModel) pp.first;
073            double dist = (Double) pp.second;
074            if (g.isEdgesCurved()) {
075                if (pp.second <= (EDGE_CURVE_CPNTROL_BOX_DIAMETER)) {
076                    sendEventToBlackBoard(EdgeEvent.clicked(e, mousePos(mouseEvent, e), mouseEvent.getButton()));
077                    return;
078                }
079            } else if (Math.sqrt(dist) < 4) {
080    //                if (doubleClicked)
081    //                    sendEventToBlackBoard(EdgeEvent.mouseDoubleClicked(e, mousePos(mouseEvent), mouseEvent.getButton()));
082    //                else
083    //                System.err.println("&^&^&^&^&^&^&");
084                sendEventToBlackBoard(EdgeEvent.clicked(e, mousePos(mouseEvent, e), mouseEvent.getButton()));
085                return;
086            }
087    
088    
089            sendEventToBlackBoard(GraphEvent.mouseClicked(g, mousePos(mouseEvent), mouseEvent.getButton(), mouseEvent.getModifiersEx()));
090        }
091    
092    
093        /**
094         * 0 1 2 3 4
095         * 0 1 2 3 4
096         *
097         * @param mouseEvent
098         * @param e
099         * @return
100         */
101        private GraphPoint mousePos(MouseEvent mouseEvent, EdgeModel e) {
102            return new GraphPoint(mousePos(mouseEvent).x - e.source.getLocation().x, mousePos(mouseEvent).y - e.source.getLocation().y);
103        }
104    
105        private GraphPoint mousePos(MouseEvent mouseEvent, VertexModel v) {
106            return new GraphPoint(mousePos(mouseEvent).x - v.getLocation().x, mousePos(mouseEvent).y - v.getLocation().y);
107        }
108    
109        /**
110         * the zoom factor of graph, It will be synced on calling mousePos(mouseEvent)
111         */
112        double zf;
113    
114        private GraphPoint mousePos(MouseEvent mouseEvent) {
115            zf = g.getZoomFactor();
116            GraphPoint p = new GraphPoint(mouseEvent.getX() / zf, mouseEvent.getY() / zf);
117            //the graphics moves automatically to solve the swing JScrollPane negative positions problem, Im not sure
118    
119            if (minx < 0)
120                p.x += minx / zf;
121            if (miny < 0)
122                p.y += miny / zf;
123            return p;
124        }
125    
126        public void mouseEntered(java.awt.event.MouseEvent mouseEvent) {
127            sendEventToBlackBoard(GraphEvent.mouseEntered(g, mousePos(mouseEvent), mouseEvent.getButton(), mouseEvent.getModifiersEx()));
128        }
129    
130        public void mouseExited(java.awt.event.MouseEvent mouseEvent) {
131            sendEventToBlackBoard(GraphEvent.mouseExited(g, mousePos(mouseEvent), mouseEvent.getButton(), mouseEvent.getModifiersEx()));
132        }
133    
134        public void mousePressed(java.awt.event.MouseEvent mouseEvent) {
135    
136            lastVertexPressed = null;
137    
138            gv.requestFocusInWindow();
139            GraphPoint mousePos = mousePos(mouseEvent);
140            Pair p = mindistv(g, mousePos);
141            VertexModel v = (VertexModel) p.first;
142            int mbuton = mouseEvent.getModifiersEx();
143            if (v != null && isPointOnVertex(g, v, mousePos)) {
144                sendEventToBlackBoard(VertexEvent.draggingStarted(v, mousePos(mouseEvent, v), mouseEvent.getButton(), mbuton));
145                if (lastVertexPressed != null) System.err.println("last = " + lastVertexPressed.getLabel());
146                lastVertexPressed = v;
147                return;
148            }
149            lastVertexPressed = null;
150            if (g.isEdgesCurved()) {
151                Pair<EdgeModel, Double> pair = mindiste(g, mousePos);
152                if (pair.first != null) {
153                    if (pair.second <= EDGE_CURVE_CPNTROL_BOX_DIAMETER) {
154                        lastEdgePressed = pair.first;
155                        sendEventToBlackBoard(EdgeEvent.draggingStarted(pair.first, mousePos(mouseEvent, lastEdgePressed), mbuton));
156                        return;
157                    }
158                }
159            }
160    
161            sendEventToBlackBoard(GraphEvent.mouseDraggingStarted(g, mousePos, mouseEvent.getButton(), mbuton));
162        }
163    
164    
165        public void mouseReleased(MouseEvent mouseEvent) {
166            Pair p = mindistv(g, mousePos(mouseEvent));
167            VertexModel v = (VertexModel) p.first;
168            int mouseButton = mouseEvent.getModifiersEx();
169            if (v != null && isPointOnVertex(g, v, mousePos(mouseEvent)) && lastVertexPressed != null) {
170                sendEventToBlackBoard(VertexEvent.dropped(v, mousePos(mouseEvent, v), mouseEvent.getButton(), mouseButton));
171                lastVertexPressed = null;
172            } else if (lastVertexPressed != null) {
173                sendEventToBlackBoard(VertexEvent.released(lastVertexPressed, mousePos(mouseEvent, lastVertexPressed), mouseEvent.getButton(), mouseButton));
174                lastVertexPressed = null;
175            } else if (lastEdgePressed != null) {
176                sendEventToBlackBoard(EdgeEvent.released(lastEdgePressed, mousePos(mouseEvent, lastEdgePressed), mouseButton));
177                lastEdgePressed = null;
178            } else
179                sendEventToBlackBoard(GraphEvent.mouseDropped(g, mousePos(mouseEvent), mouseEvent.getButton(), mouseButton));
180        }
181    
182        public void mouseWheelMoved(java.awt.event.MouseWheelEvent mouseWheelEvent) {
183            sendEventToBlackBoard(GraphEvent.mouseWheelMoved(g, mousePos(mouseWheelEvent), mouseWheelEvent.getWheelRotation(), mouseWheelEvent.getModifiersEx()));
184        }
185    
186        public void mouseDragged(MouseEvent mouseEvent) {
187            int modex = mouseEvent.getModifiersEx();
188            if (lastVertexPressed != null) {
189                sendEventToBlackBoard(VertexEvent.dragging(lastVertexPressed, mousePos(mouseEvent, lastVertexPressed), mouseEvent.getButton(), modex));
190            } else if (lastEdgePressed != null) {
191                sendEventToBlackBoard(EdgeEvent.dragging(lastEdgePressed, mousePos(mouseEvent, lastEdgePressed), modex));
192            } else
193                sendEventToBlackBoard(GraphEvent.dragging(g, mousePos(mouseEvent), mouseEvent.getButton(), modex));
194    
195        }
196    
197        public void mouseMoved(MouseEvent e) {
198            sendEventToBlackBoard(GraphEvent.mouseMoved(g, mousePos(e), e.getButton(), e.getModifiersEx()));
199        }
200    
201        private void sendEventToBlackBoard(GraphEvent value) {
202            if (listener != null)
203                listener.ActionPerformed(value);
204        }
205    
206        private void sendEventToBlackBoard(VertexEvent value) {
207            if (listener != null)
208                listener.ActionPerformed(value);
209        }
210    
211        private void sendEventToBlackBoard(EdgeEvent value) {
212            if (listener != null)
213                listener.ActionPerformed(value);
214        }
215    
216        /**
217         * @return the minimum distanse edge and its distance to the given GraphPoint,
218         *         If edges are curved the distance will be calculated to Curve Control Points
219         */
220        public static Pair<EdgeModel, Double> mindiste(GraphModel g, GraphPoint p) {
221            double min = 100000;
222            EdgeModel mine = null;
223            Iterator<EdgeModel> ei = g.lightEdgeIterator();
224            if (g.isEdgesCurved()) {
225                for (; ei.hasNext();) {
226                    EdgeModel e = ei.next();
227                    GraphPoint cnp = e.getCurveControlPoint();
228                    GraphPoint s = e.source.getLocation();
229                    GraphPoint t = e.target.getLocation();
230                    GraphPoint cp = new GraphPoint((s.x + t.x) / 2.0 + cnp.x, (s.y + t.y) / 2.0 + cnp.y);
231                    double dist = GraphPoint.distance(cp.x, cp.y, p.x, p.y);
232                    if (min > dist) {
233                        min = dist;
234                        mine = e;
235                    }
236                }
237                if (min < EDGE_CURVE_CPNTROL_BOX_DIAMETER) {
238                    min = 0;
239                }
240            } else {
241                for (; ei.hasNext();) {
242                    EdgeModel e = ei.next();
243                    if (!isInBounds(e, p))
244                        continue;
245                    GraphPoint sloc = e.source.getLocation();
246                    GraphPoint tloc = e.target.getLocation();
247                    Line2D.Double l = new Line2D.Double(sloc.x, sloc.y, tloc.x, e.target.getLocation().y);
248                    double dist = l.ptLineDistSq(p);
249                    if (min > dist) {
250                        min = dist;
251                        mine = e;
252                    }
253                }
254            }
255            return new Pair(mine, min);
256        }
257    
258        private static boolean isInBounds(EdgeModel e, GraphPoint p) {
259            GraphPoint l1 = e.source.getLocation();
260            GraphPoint l2 = e.target.getLocation();
261            return Math.min(l1.x, l2.x) <= p.x + 5 && Math.max(l1.x, l2.x) >= p.x - 5 && Math.min(l1.y, l2.y) <= p.y + 5 && Math.max(l1.y, l2.y) >= p.y - 5;
262        }
263    
264        /**
265         * @return the minimum distance vertex to the given location, and its distanse square(^2).
266         */
267        public static Pair<VertexModel, Double> mindistv(GraphModel g, GraphPoint p) {
268            double min = 100000;
269            VertexModel minv = null;
270            for (VertexModel v : g) {
271                double dist = Math.pow(v.getLocation().x - p.x, 2) + Math.pow(v.getLocation().y - p.y, 2);
272                if (min > dist) {
273                    min = dist;
274                    minv = v;
275                }
276            }
277            return new Pair(minv, min);
278        }
279    
280        /**
281         * @return True if the given point in on the given vertex
282         */
283        public static boolean isPointOnVertex(GraphModel g, VertexModel v, GraphPoint p) {
284            double zf = g.getZoomFactor();
285            GraphPoint l = v.getLocation();
286            GraphPoint s = v.getSize();
287            double sx = s.x / zf;
288            double sy = s.y / zf;
289            return l.x - sx / 2 <= p.x && l.x + sx / 2 >= p.x && l.y - sy / 2 <= p.y && l.y + sy / 2 >= p.y;
290        }
291    
292    }