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: f@0: package uk.ac.qmul.eecs.ccmi.haptics; f@0: f@0: import java.awt.Dimension; f@0: import java.awt.Toolkit; f@0: import java.awt.geom.Line2D; f@0: import java.io.File; f@0: import java.io.IOException; f@0: import java.net.URL; f@0: import java.util.ArrayList; f@0: import java.util.BitSet; f@0: import java.util.HashMap; f@0: import java.util.ListIterator; f@0: f@0: import uk.ac.qmul.eecs.ccmi.utils.OsDetector; f@0: import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; f@0: import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter; f@0: f@0: class FalconHaptics extends Thread implements Haptics { f@0: static Haptics createInstance(HapticListenerThread listener) { f@0: if(OsDetector.has64BitJVM()){// no 64 native library supported yet f@0: return null; f@0: } f@0: f@0: if(OsDetector.isWindows()){ f@0: /* create a directory for the dll distributed with HAPI library */ f@0: String libDir = PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")); f@0: File hapiDir = new File(libDir,"HAPI"); f@0: hapiDir.mkdir(); f@0: f@0: /* try to load .dll's. First copy it in the home/ccmi_editor_data/lib directory */ f@0: String[] dlls = {"HAPI/pthreadVC2.dll","HAPI/FreeImage.dll","HAPI/freeglut.dll", f@0: "HAPI/H3DUtil_vc9.dll","HAPI/HAPI_vc9.dll","FalconHaptics.dll"}; f@0: ResourceFileWriter fileWriter = new ResourceFileWriter(); f@0: for(String dll : dlls){ f@0: URL url = OmniHaptics.class.getResource(dll); f@0: fileWriter.setResource(url); f@0: fileWriter.writeOnDisk(libDir, dll); f@0: String path = fileWriter.getFilePath(); f@0: try{ f@0: if(path == null) f@0: throw new UnsatisfiedLinkError(dll+" missing"); f@0: System.load( path ); f@0: }catch(UnsatisfiedLinkError e){ f@0: System.err.println(e.getMessage()); f@0: e.printStackTrace(); f@0: return null; f@0: } f@0: } f@0: }else{ f@0: return null; f@0: } f@0: f@0: FalconHaptics falcon = new FalconHaptics("FalconHaptics"); f@0: falcon.hapticListener = listener; f@0: /* start up the listener which immediately stops, waiting for commands */ f@0: if(!falcon.hapticListener.isAlive()) f@0: falcon.hapticListener.start(); f@0: /* start up the haptics thread which issues commands from the java to the c++ thread */ f@0: falcon.start(); f@0: synchronized(falcon){ f@0: try { f@0: falcon.wait(); f@0: }catch (InterruptedException ie) { f@0: throw new RuntimeException(ie); // must never happen f@0: } f@0: } f@0: if(falcon.initFailed) f@0: return null; f@0: else f@0: return falcon; f@0: } f@0: f@0: private FalconHaptics(String threadName){ f@0: super(threadName); f@0: f@0: nodes = new HashMap>(); f@0: edges = new HashMap>(); f@0: currentNodes = Empties.EMPTY_NODE_LIST; f@0: currentEdges = Empties.EMPTY_EDGE_LIST; f@0: f@0: attractTo = 0; f@0: } f@0: f@0: private native int initFalcon(int width, int height) throws IOException ; f@0: f@0: @Override f@0: public void addNewDiagram(String diagramName) { f@0: ArrayList cNodes = new ArrayList(30); f@0: ArrayList cEdges = new ArrayList(30); f@0: nodes.put(diagramName, cNodes); f@0: edges.put(diagramName, cEdges); f@0: f@0: synchronized(this){ f@0: currentNodes = cNodes; f@0: currentEdges = cEdges; f@0: collectionsChanged = true; f@0: } f@0: } f@0: f@0: @Override f@0: public synchronized void switchDiagram(String diagramName) { f@0: if(!nodes.containsKey(diagramName)) f@0: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName); f@0: f@0: currentNodes = nodes.get(diagramName); f@0: currentEdges = edges.get(diagramName); f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void removeDiagram(String diagramNameToRemove, f@0: String diagramNameOfNext) { f@0: if(!nodes.containsKey(diagramNameToRemove)) f@0: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove); f@0: f@0: nodes.remove(diagramNameToRemove); f@0: edges.remove(diagramNameToRemove); f@0: f@0: if(diagramNameOfNext == null){ f@0: currentNodes = Empties.EMPTY_NODE_LIST; f@0: currentEdges = Empties.EMPTY_EDGE_LIST; f@0: }else { f@0: if(!nodes.containsKey(diagramNameOfNext)) f@0: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameOfNext); f@0: currentNodes = nodes.get(diagramNameOfNext); f@0: currentEdges = edges.get(diagramNameOfNext); f@0: } f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName) { f@0: Node n = new Node(x,y,nodeHashCode, nodeHashCode); f@0: if(diagramName == null){ f@0: currentNodes.add(n); f@0: }else{ f@0: nodes.get(diagramName).add(n); f@0: } f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void removeNode(int nodeHashCode, String diagramName) { f@0: ListIterator itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator(); f@0: while(itr.hasNext()){ f@0: Node n = itr.next(); f@0: if(n.diagramId == nodeHashCode){ f@0: itr.remove(); f@0: collectionsChanged = true; f@0: break; f@0: } f@0: } f@0: } f@0: f@0: @Override f@0: public synchronized void moveNode(double x, double y, int nodeHashCode, f@0: String diagramName) { f@0: ArrayList iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName); f@0: for(Node n : iterationList){ f@0: if(n.diagramId == nodeHashCode){ f@0: n.x = x; f@0: n.y = y; f@0: collectionsChanged = true; f@0: break; f@0: } f@0: } f@0: } f@0: f@0: @Override f@0: public synchronized 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: f@0: Edge e = new Edge(edgeHashCode,edgeHashCode, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY); f@0: /* add the edge reference to the edges list */ f@0: if(diagramName == null){ f@0: currentEdges.add(e); f@0: }else{ f@0: edges.get(diagramName).add(e); f@0: } f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, f@0: BitSet[] adjMatrix, int nodeStart, Line2D attractLine, f@0: String diagramName) { f@0: f@0: for(Edge e : currentEdges){ f@0: if(e.diagramId == edgeHashCode){ f@0: e.xs = xs; f@0: e.ys = ys; f@0: e.size = xs.length; f@0: e.adjMatrix = adjMatrix; f@0: e.nodeStart = nodeStart; 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: e.attractPointX = pX; f@0: e.attractPointY = pY; f@0: } f@0: } f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void removeEdge(int edgeHashCode, String diagramName) { f@0: ListIterator itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator(); f@0: while(itr.hasNext()){ f@0: Edge e = itr.next(); f@0: if(e.diagramId == edgeHashCode){ f@0: itr.remove(); f@0: collectionsChanged = true; f@0: break; f@0: } f@0: } f@0: collectionsChanged = true; f@0: } f@0: f@0: @Override f@0: public synchronized void attractTo(int elementHashCode) { f@0: attractTo = elementHashCode; f@0: } f@0: f@0: @Override f@0: public synchronized void pickUp(int elementHashCode) { f@0: pickUp = true; f@0: } f@0: f@0: @Override f@0: public void setVisible(boolean visible) { f@0: // falcon haptics window cannot be made invisible f@0: } f@0: f@0: @Override f@0: public synchronized void dispose(){ f@0: shutdown = true; f@0: /* wait for the haptic thread to shut down */ f@0: try { f@0: wait(); f@0: } catch (InterruptedException e) { f@0: throw new RuntimeException(e); f@0: } f@0: } f@0: f@0: @Override f@0: public void run() { f@0: /* get the screen size which will be passed to init methos in order to set up a window f@0: * for the haptic with the same size as the swing one f@0: */ f@0: Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); f@0: f@0: int screenWidth = (int)screenSize.getWidth(); f@0: int screenHeight = (int)screenSize.getHeight(); f@0: currentNodes.size(); f@0: try { f@0: initFalcon(screenWidth * 5 / 8,screenHeight * 5 / 8); f@0: } catch (IOException e) { f@0: throw new RuntimeException();// OMNI haptic device doesn't cause any exception f@0: } f@0: } f@0: f@0: /* the diagram currently selected */ f@0: private ArrayList currentNodes; f@0: private ArrayList currentEdges; f@0: f@0: /* maps with all the diagrams in the editor */ f@0: private HashMap> nodes; f@0: private HashMap> edges; f@0: f@0: private HapticListenerThread hapticListener; f@0: private boolean initFailed; f@0: private boolean collectionsChanged; f@0: private boolean pickUp; f@0: private int attractTo; f@0: private boolean shutdown; f@0: }