fiore@5: /* fiore@5: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@5: fiore@5: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@5: fiore@5: This program is free software: you can redistribute it and/or modify fiore@5: it under the terms of the GNU General Public License as published by fiore@5: the Free Software Foundation, either version 3 of the License, or fiore@5: (at your option) any later version. fiore@5: fiore@5: This program is distributed in the hope that it will be useful, fiore@5: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@5: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@5: GNU General Public License for more details. fiore@5: fiore@5: You should have received a copy of the GNU General Public License fiore@5: along with this program. If not, see . fiore@5: */ fiore@5: fiore@5: fiore@5: package uk.ac.qmul.eecs.ccmi.haptics; fiore@5: fiore@5: import java.awt.Dimension; fiore@5: import java.awt.Toolkit; fiore@5: import java.awt.geom.Line2D; fiore@5: import java.io.File; fiore@5: import java.io.IOException; fiore@5: import java.net.URL; fiore@5: import java.util.ArrayList; fiore@5: import java.util.BitSet; fiore@5: import java.util.HashMap; fiore@5: import java.util.ListIterator; fiore@5: fiore@5: import uk.ac.qmul.eecs.ccmi.utils.OsDetector; fiore@5: import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; fiore@5: import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter; fiore@5: fiore@5: class FalconHaptics extends Thread implements Haptics { fiore@5: static Haptics createInstance(HapticListenerThread listener) { fiore@6: if(OsDetector.has64BitJVM()){// no 64 native library supported yet fiore@6: return null; fiore@6: } fiore@6: fiore@5: if(OsDetector.isWindows()){ fiore@5: /* create a directory for the dll distributed with HAPI library */ fiore@5: String libDir = PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")); fiore@5: File hapiDir = new File(libDir,"HAPI"); fiore@5: hapiDir.mkdir(); fiore@5: fiore@5: /* try to load .dll's. First copy it in the home/ccmi_editor_data/lib directory */ fiore@5: String[] dlls = {"HAPI/pthreadVC2.dll","HAPI/FreeImage.dll","HAPI/freeglut.dll", fiore@5: "HAPI/H3DUtil_vc9.dll","HAPI/HAPI_vc9.dll","FalconHaptics.dll"}; fiore@5: ResourceFileWriter fileWriter = new ResourceFileWriter(); fiore@5: for(String dll : dlls){ fiore@5: URL url = OmniHaptics.class.getResource(dll); fiore@5: fileWriter.setResource(url); fiore@5: fileWriter.writeOnDisk(libDir, dll); fiore@5: String path = fileWriter.getFilePath(); fiore@5: try{ fiore@5: if(path == null) fiore@5: throw new UnsatisfiedLinkError(dll+" missing"); fiore@5: System.load( path ); fiore@5: }catch(UnsatisfiedLinkError e){ fiore@5: System.err.println(e.getMessage()); fiore@5: e.printStackTrace(); fiore@5: return null; fiore@5: } fiore@5: } fiore@5: }else{ fiore@5: return null; fiore@5: } fiore@5: fiore@5: FalconHaptics falcon = new FalconHaptics("FalconHaptics"); fiore@5: falcon.hapticListener = listener; fiore@5: /* start up the listener which immediately stops, waiting for commands */ fiore@5: if(!falcon.hapticListener.isAlive()) fiore@5: falcon.hapticListener.start(); fiore@5: /* start up the haptics thread which issues commands from the java to the c++ thread */ fiore@5: falcon.start(); fiore@5: synchronized(falcon){ fiore@5: try { fiore@5: falcon.wait(); fiore@5: }catch (InterruptedException ie) { fiore@5: throw new RuntimeException(ie); // must never happen fiore@5: } fiore@5: } fiore@5: if(falcon.initFailed) fiore@5: return null; fiore@5: else fiore@5: return falcon; fiore@5: } fiore@5: fiore@5: private FalconHaptics(String threadName){ fiore@5: super(threadName); fiore@5: fiore@5: nodes = new HashMap>(); fiore@5: edges = new HashMap>(); fiore@5: currentNodes = Empties.EMPTY_NODE_LIST; fiore@5: currentEdges = Empties.EMPTY_EDGE_LIST; fiore@5: fiore@5: attractTo = 0; fiore@5: } fiore@5: fiore@5: private native int initFalcon(int width, int height) throws IOException ; fiore@5: fiore@5: @Override fiore@5: public void addNewDiagram(String diagramName) { fiore@5: ArrayList cNodes = new ArrayList(30); fiore@5: ArrayList cEdges = new ArrayList(30); fiore@5: nodes.put(diagramName, cNodes); fiore@5: edges.put(diagramName, cEdges); fiore@5: fiore@5: synchronized(this){ fiore@5: currentNodes = cNodes; fiore@5: currentEdges = cEdges; fiore@5: collectionsChanged = true; fiore@5: } fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void switchDiagram(String diagramName) { fiore@5: if(!nodes.containsKey(diagramName)) fiore@5: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName); fiore@5: fiore@5: currentNodes = nodes.get(diagramName); fiore@5: currentEdges = edges.get(diagramName); fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void removeDiagram(String diagramNameToRemove, fiore@5: String diagramNameOfNext) { fiore@5: if(!nodes.containsKey(diagramNameToRemove)) fiore@5: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove); fiore@5: fiore@5: nodes.remove(diagramNameToRemove); fiore@5: edges.remove(diagramNameToRemove); fiore@5: fiore@5: if(diagramNameOfNext == null){ fiore@5: currentNodes = Empties.EMPTY_NODE_LIST; fiore@5: currentEdges = Empties.EMPTY_EDGE_LIST; fiore@5: }else { fiore@5: if(!nodes.containsKey(diagramNameOfNext)) fiore@5: throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameOfNext); fiore@5: currentNodes = nodes.get(diagramNameOfNext); fiore@5: currentEdges = edges.get(diagramNameOfNext); fiore@5: } fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName) { fiore@5: Node n = new Node(x,y,nodeHashCode, nodeHashCode); fiore@5: if(diagramName == null){ fiore@5: currentNodes.add(n); fiore@5: }else{ fiore@5: nodes.get(diagramName).add(n); fiore@5: } fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void removeNode(int nodeHashCode, String diagramName) { fiore@5: ListIterator itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator(); fiore@5: while(itr.hasNext()){ fiore@5: Node n = itr.next(); fiore@5: if(n.diagramId == nodeHashCode){ fiore@5: itr.remove(); fiore@5: collectionsChanged = true; fiore@5: break; fiore@5: } fiore@5: } fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void moveNode(double x, double y, int nodeHashCode, fiore@5: String diagramName) { fiore@5: ArrayList iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName); fiore@5: for(Node n : iterationList){ fiore@5: if(n.diagramId == nodeHashCode){ fiore@5: n.x = x; fiore@5: n.y = y; fiore@5: collectionsChanged = true; fiore@5: break; fiore@5: } fiore@5: } fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, fiore@5: BitSet[] adjMatrix, int nodeStart, int stipplePattern, fiore@5: Line2D attractLine, String diagramName) { fiore@5: /* find the mid point of the line of attraction */ fiore@5: double pX = Math.min(attractLine.getX1(), attractLine.getX2()); fiore@5: double pY = Math.min(attractLine.getY1(), attractLine.getY2()); fiore@5: pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; fiore@5: pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; fiore@5: fiore@5: Edge e = new Edge(edgeHashCode,edgeHashCode, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY); fiore@5: /* add the edge reference to the edges list */ fiore@5: if(diagramName == null){ fiore@5: currentEdges.add(e); fiore@5: }else{ fiore@5: edges.get(diagramName).add(e); fiore@5: } fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, fiore@5: BitSet[] adjMatrix, int nodeStart, Line2D attractLine, fiore@5: String diagramName) { fiore@5: fiore@5: for(Edge e : currentEdges){ fiore@5: if(e.diagramId == edgeHashCode){ fiore@5: e.xs = xs; fiore@5: e.ys = ys; fiore@5: e.size = xs.length; fiore@5: e.adjMatrix = adjMatrix; fiore@5: e.nodeStart = nodeStart; fiore@5: // find the mid point of the line of attraction fiore@5: double pX = Math.min(attractLine.getX1(), attractLine.getX2()); fiore@5: double pY = Math.min(attractLine.getY1(), attractLine.getY2()); fiore@5: pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; fiore@5: pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; fiore@5: e.attractPointX = pX; fiore@5: e.attractPointY = pY; fiore@5: } fiore@5: } fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void removeEdge(int edgeHashCode, String diagramName) { fiore@5: ListIterator itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator(); fiore@5: while(itr.hasNext()){ fiore@5: Edge e = itr.next(); fiore@5: if(e.diagramId == edgeHashCode){ fiore@5: itr.remove(); fiore@5: collectionsChanged = true; fiore@5: break; fiore@5: } fiore@5: } fiore@5: collectionsChanged = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void attractTo(int elementHashCode) { fiore@5: attractTo = elementHashCode; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void pickUp(int elementHashCode) { fiore@5: pickUp = true; fiore@5: } fiore@5: fiore@5: @Override fiore@5: public void setVisible(boolean visible) { fiore@5: // falcon haptics window cannot be made invisible fiore@5: } fiore@5: fiore@5: @Override fiore@5: public synchronized void dispose(){ fiore@5: shutdown = true; fiore@5: /* wait for the haptic thread to shut down */ fiore@5: try { fiore@5: wait(); fiore@5: } catch (InterruptedException e) { fiore@5: throw new RuntimeException(e); fiore@5: } fiore@5: } fiore@5: fiore@5: @Override fiore@5: public void run() { fiore@5: /* get the screen size which will be passed to init methos in order to set up a window fiore@5: * for the haptic with the same size as the swing one fiore@5: */ fiore@5: Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); fiore@5: fiore@5: int screenWidth = (int)screenSize.getWidth(); fiore@5: int screenHeight = (int)screenSize.getHeight(); fiore@5: currentNodes.size(); fiore@5: try { fiore@5: initFalcon(screenWidth * 5 / 8,screenHeight * 5 / 8); fiore@5: } catch (IOException e) { fiore@5: throw new RuntimeException();// OMNI haptic device doesn't cause any exception fiore@5: } fiore@5: } fiore@5: fiore@5: /* the diagram currently selected */ fiore@5: private ArrayList currentNodes; fiore@5: private ArrayList currentEdges; fiore@5: fiore@5: /* maps with all the diagrams in the editor */ fiore@5: private HashMap> nodes; fiore@5: private HashMap> edges; fiore@5: fiore@5: private HapticListenerThread hapticListener; fiore@5: private boolean initFailed; fiore@5: private boolean collectionsChanged; fiore@5: private boolean pickUp; fiore@5: private int attractTo; fiore@5: private boolean shutdown; fiore@5: }