f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.haptics; f@0: f@0: import java.awt.AWTException; f@0: import java.awt.BasicStroke; f@0: import java.awt.BorderLayout; f@0: import java.awt.Color; f@0: import java.awt.Graphics; f@0: import java.awt.Graphics2D; f@0: import java.awt.Robot; f@0: import java.awt.Stroke; f@0: import java.awt.event.InputEvent; f@0: import java.awt.event.KeyEvent; f@0: import java.awt.event.KeyListener; f@0: import java.awt.event.MouseEvent; f@0: import java.awt.event.MouseListener; f@0: import java.awt.event.MouseMotionListener; f@0: import java.awt.event.WindowEvent; f@0: import java.awt.event.WindowFocusListener; f@0: import java.awt.geom.Line2D; f@0: import java.awt.geom.Point2D; f@0: import java.io.IOException; f@0: import java.util.BitSet; f@0: f@0: import javax.swing.JFrame; f@0: import javax.swing.JPanel; f@0: f@0: import uk.ac.qmul.eecs.ccmi.gui.DiagramPanel; f@0: import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp; f@0: import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; f@0: f@0: @SuppressWarnings("serial") f@0: class MouseHaptics extends JPanel implements Haptics, KeyListener, MouseListener, MouseMotionListener { f@0: static Haptics createInstance(HapticListener listener){ f@0: MouseHaptics instance = new MouseHaptics(listener); f@0: try{ f@0: instance.initMouseHaptics(); f@0: return instance; f@0: }catch(IOException ioe){ f@0: return null; f@0: } f@0: } f@0: f@0: private MouseHaptics(HapticListener listener){ f@0: this.listener = listener; f@0: solidStroke = new BasicStroke(EDGE_THICKNESS); f@0: dottedStroke = new BasicStroke(EDGE_THICKNESS, f@0: BasicStroke.CAP_ROUND, f@0: BasicStroke.JOIN_ROUND, f@0: 0.0f, f@0: new float[]{1.0f,30.0f}, f@0: 0.0f); f@0: dashedStroke = new BasicStroke(EDGE_THICKNESS, f@0: BasicStroke.CAP_ROUND, f@0: BasicStroke.JOIN_ROUND, f@0: 0.0f, f@0: new float[]{80.0f,50.0f}, f@0: 0.0f); f@0: } f@0: f@0: @Override f@0: public void paintComponent(Graphics g){ f@0: super.paintComponent(g); f@0: f@0: Graphics2D g2 = (Graphics2D)g; f@0: f@0: Stroke oldStroke = g2.getStroke(); f@0: // draw edges f@0: g2.setColor(EDGE_COLOR); f@0: for(Edge e : listSupport.getCurrentEdges()){ f@0: switch(e.stipplePattern){ f@0: case Edge.SOLID_LINE : f@0: g2.setStroke(solidStroke);break; f@0: case Edge.DOTTED_LINE : f@0: g2.setStroke(dottedStroke);break; f@0: case Edge.DASHED_LINE : f@0: g2.setStroke(dashedStroke);break; f@0: } f@0: for(int i=0; i< e.adjMatrix.length; i++){ f@0: BitSet adj = e.adjMatrix[i]; f@0: for (int j = adj.nextSetBit(0); j >= 0; j = adj.nextSetBit(j+1)) { f@0: g2.drawLine((int)e.xs[i], (int)e.ys[i], (int)e.xs[j], (int)e.ys[j]); f@0: } f@0: } f@0: } f@0: f@0: g2.setStroke(oldStroke); f@0: // draw nodes f@0: g2.setColor(NODE_COLOR); f@0: for(Node n : listSupport.getCurrentNodes()){ f@0: g2.fillOval((int)(n.x - NODE_DIAMETER/2), (int)(n.y - NODE_DIAMETER/2), NODE_DIAMETER, NODE_DIAMETER); f@0: } f@0: } f@0: f@0: @Override f@0: public void run() {} f@0: f@0: void initMouseHaptics() throws IOException { f@0: hapticFrame = new JFrame("Haptic Frame"); f@0: try { f@0: robot = new Robot(); f@0: } catch (AWTException e) { f@0: throw new IOException(e); f@0: } f@0: /* this is necessary to make the window screen size. it doens't work with the default layout */ f@0: setLayout(new BorderLayout()); f@0: f@0: setBackground(Color.black); f@0: f@0: listSupport = new HapticListSupport(); f@0: f@0: hapticFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); f@0: hapticFrame.addWindowFocusListener(new WindowFocusListener(){ f@0: @Override f@0: public void windowGainedFocus(WindowEvent arg0) { f@0: NarratorFactory.getInstance().speak("Haptic window Focused"); f@0: } f@0: f@0: @Override f@0: public void windowLostFocus(WindowEvent arg0) {} f@0: }); f@0: hapticFrame.setContentPane(this); f@0: hapticFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); f@0: hapticFrame.setUndecorated(true); f@0: hapticFrame.addKeyListener(this); f@0: addMouseMotionListener(this); f@0: addMouseListener(this); f@0: hapticFrame.pack(); f@0: } f@0: f@0: @Override f@0: public void addNewDiagram(String diagramName) { f@0: listSupport.addNewDiagram(diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void switchDiagram(String diagramName) { f@0: listSupport.switchDiagram(diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void removeDiagram(String diagramNameToRemove, f@0: String diagramNameOfNext) { f@0: listSupport.removeDiagram(diagramNameToRemove, diagramNameOfNext); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void addNode(double x, double y, int nodeHashCode, String diagramName) { f@0: Node n = new Node(x,y,nodeHashCode, 0); f@0: listSupport.addNode(n,diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void removeNode(int nodeHashCode, String diagramName) { f@0: listSupport.removeNode(nodeHashCode, diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void moveNode(double x, double y, int nodeHashCode, f@0: String diagramName) { f@0: Node n = listSupport.getNode(nodeHashCode, diagramName); f@0: n.x = x; f@0: n.y = y; f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void addEdge(int edgeHashCode, double[] xs, double[] ys, f@0: BitSet[] adjMatrix, int nodeStart, int stipplePattern, f@0: Line2D attractLine, String diagramName) { f@0: // find the mid point of the line of attraction f@0: double pX = Math.min(attractLine.getX1(), attractLine.getX2()); f@0: double pY = Math.min(attractLine.getY1(), attractLine.getY2()); f@0: pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; f@0: pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; f@0: Edge e = new Edge(edgeHashCode,0, xs, ys, f@0: adjMatrix, nodeStart, stipplePattern, f@0: pX, pY); f@0: listSupport.addEdge(e, diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void updateEdge(int edgeHashCode, double[] xs, double[] ys, f@0: BitSet[] adjMatrix, int nodeStart, Line2D attractLine, f@0: String diagramName) { f@0: Edge e = listSupport.getEdge(edgeHashCode, diagramName); f@0: e.xs = xs; f@0: e.ys = ys; f@0: e.adjMatrix = adjMatrix; f@0: e.nodeStart = nodeStart; f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void removeEdge(int edgeHashCode, String diagramName) { f@0: listSupport.removeEdge(edgeHashCode, diagramName); f@0: repaint(); f@0: } f@0: f@0: @Override f@0: public void attractTo(int elementHashCode) { f@0: f@0: } f@0: f@0: @Override f@0: public void pickUp(int elementHashCode) { f@0: // needed to change the status of the haptic device. not needed here f@0: } f@0: f@0: @Override f@0: public boolean isAlive() { f@0: return false; f@0: } f@0: f@0: @Override f@0: public void setVisible(boolean visible){ f@0: hapticFrame.setVisible(visible); f@0: } f@0: f@0: @Override f@0: public void dispose() { f@0: // no resources to free up f@0: } f@0: f@0: @Override f@0: public void keyPressed(KeyEvent evt) { f@0: DiagramPanel panel = DiagramEditorApp.getFrame().getActiveTab(); f@0: if(panel == null) f@0: DiagramEditorApp.getFrame().editorTabbedPane.dispatchEvent(evt); f@0: else f@0: panel.getTree().dispatchEvent(evt); f@0: } f@0: f@0: @Override f@0: public void keyReleased(KeyEvent evt) { f@0: keyPressed(evt); f@0: } f@0: f@0: @Override f@0: public void keyTyped(KeyEvent evt) { f@0: keyPressed(evt); f@0: } f@0: f@0: @Override f@0: public void mouseDragged(MouseEvent evt) { f@0: /* priority to nodes: check if the mouse pointer is inside a circle centred on * f@0: * the node and with radius equal to NODE_RADIUS. If the mouse pointer is within * f@0: * the radius of two or more nodes then the closest is picked up. The command is * f@0: * executed once when the node is touched. In order have it executed again the * f@0: * used must get away from it and hover above it again */ f@0: Point2D p = evt.getPoint(); f@0: if(elementPickedUp){ f@0: draggedDistance += evt.getPoint().distance(lastDragPoint); f@0: if( draggedDistance > CHAIN_SOUND_INTERVAL){ f@0: listener.executeCommand(HapticListenerCommand.PLAY_SOUND, 3, 0,0,0,0); f@0: draggedDistance = 0; f@0: } f@0: lastDragPoint = evt.getPoint(); f@0: } f@0: f@0: Node hoveredNode = null; f@0: for(Node n : listSupport.getCurrentNodes()){ f@0: double distance = p.distance(n.x, n.y); f@0: if( distance < NODE_HOVER_DIST){ f@0: if(hoveredNode == null || distance < p.distance(hoveredNode.x,hoveredNode.y) ){ f@0: hoveredNode = n; f@0: } f@0: } f@0: } f@0: if(hoveredNode != null && hoveredNode != lastTouchedNode){ f@0: listener.executeCommand(HapticListenerCommand.PLAY_ELEMENT_SPEECH, hoveredNode.diagramId, 0, 0, 0, 0); f@0: lastTouchedNode = hoveredNode; f@0: lastTouchedEdge = null; f@0: return; f@0: } f@0: lastTouchedNode = hoveredNode; f@0: /* if hovering inside a node neither send the command nor take edges into account */ f@0: if(hoveredNode != null) f@0: return; f@0: f@0: /* if no node is being touched, check the edges out. */ f@0: Edge hoveredEdge = null; f@0: Line2D line = new Line2D.Double(); f@0: /* look at all edges */ f@0: for(Edge e : listSupport.getCurrentEdges()){ f@0: /* look at all edge's lines */ f@0: for(int i=0; i< e.adjMatrix.length; i++){ f@0: BitSet adj = e.adjMatrix[i]; f@0: for (int j = adj.nextSetBit(0); j >= 0; j = adj.nextSetBit(j+1)) { f@0: line.setLine(e.xs[i], e.ys[i], e.xs[j], e.ys[j]); f@0: if(lastTouchedEdge != e && line.ptSegDist(p)= 0; j = adj.nextSetBit(j+1)) { f@0: line.setLine(e.xs[i], e.ys[i], e.xs[j], e.ys[j]); f@0: if(/*lastTouchedEdge != e && */line.ptSegDist(p)