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: }