annotate java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java @ 6:1c5af356bb99

added 64 bit native narrator allow hapitic native dll to load only on 32 bit JVM refactored DiagramEditorApp for better inheritance fixed Java 7 bug: NullPointerException when typing minor bug fixes added splashscreen
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Mon, 17 Dec 2012 18:39:40 +0000
parents d66dd5880081
children
rev   line source
fiore@3 1 /*
fiore@3 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
fiore@3 3
fiore@3 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@3 5
fiore@3 6 This program is free software: you can redistribute it and/or modify
fiore@3 7 it under the terms of the GNU General Public License as published by
fiore@3 8 the Free Software Foundation, either version 3 of the License, or
fiore@3 9 (at your option) any later version.
fiore@3 10
fiore@3 11 This program is distributed in the hope that it will be useful,
fiore@3 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@3 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@3 14 GNU General Public License for more details.
fiore@3 15
fiore@3 16 You should have received a copy of the GNU General Public License
fiore@3 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@3 18 */
fiore@3 19
fiore@3 20 package uk.ac.qmul.eecs.ccmi.haptics;
fiore@3 21
fiore@3 22 import java.awt.Dimension;
fiore@3 23 import java.awt.Toolkit;
fiore@3 24 import java.awt.geom.Line2D;
fiore@3 25 import java.io.IOException;
fiore@3 26 import java.net.URL;
fiore@3 27 import java.util.ArrayList;
fiore@3 28 import java.util.BitSet;
fiore@3 29 import java.util.HashMap;
fiore@3 30 import java.util.ListIterator;
fiore@3 31
fiore@3 32 import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter;
fiore@3 33 import uk.ac.qmul.eecs.ccmi.utils.OsDetector;
fiore@3 34 import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;
fiore@3 35
fiore@3 36 /*
fiore@3 37 *
fiore@3 38 * The implementation of the Haptics interface which uses SensableŽ
fiore@3 39 * PHANTOM OmniŽ haptic device.
fiore@3 40 *
fiore@3 41 */
fiore@3 42 class OmniHaptics extends Thread implements Haptics {
fiore@3 43
fiore@5 44 static Haptics createInstance(HapticListenerThread listener) {
fiore@3 45 if(listener == null)
fiore@3 46 throw new IllegalArgumentException("listener cannot be null");
fiore@3 47
fiore@6 48 if(OsDetector.has64BitJVM()){
fiore@6 49 return null;// no 64 native library supported yet
fiore@6 50 }
fiore@6 51
fiore@3 52 if(OsDetector.isWindows()){
fiore@3 53 /* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory */
fiore@5 54 URL url = OmniHaptics.class.getResource("OmniHaptics.dll");
fiore@3 55 ResourceFileWriter fileWriter = new ResourceFileWriter(url);
fiore@5 56 fileWriter.writeOnDisk(
fiore@3 57 PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")),
fiore@5 58 "OmniHaptics.dll");
fiore@3 59 String path = fileWriter.getFilePath();
fiore@3 60 try{
fiore@5 61 if(path == null)
fiore@5 62 throw new UnsatisfiedLinkError("OmniHaptics.dll missing");
fiore@3 63 System.load( path );
fiore@3 64 }catch(UnsatisfiedLinkError e){
fiore@5 65 e.printStackTrace();
fiore@3 66 return null;
fiore@3 67 }
fiore@3 68 }else{
fiore@3 69 return null;
fiore@3 70 }
fiore@3 71
fiore@3 72 OmniHaptics omniHaptics = new OmniHaptics("Haptics");
fiore@3 73 omniHaptics.hapticListener = listener;
fiore@3 74 /* start up the listener which immediately stops, waiting for commands */
fiore@3 75 if(!omniHaptics.hapticListener.isAlive())
fiore@3 76 omniHaptics.hapticListener.start();
fiore@3 77 /* start up the haptics thread which issues commands from the java to the c++ thread */
fiore@3 78 omniHaptics.start();
fiore@3 79 /* wait for the haptics thread (now running native code) to initialize (need to know if initialization is successful) */
fiore@3 80 synchronized(omniHaptics){
fiore@3 81 try {
fiore@3 82 omniHaptics.wait();
fiore@3 83 }catch (InterruptedException ie) {
fiore@3 84 throw new RuntimeException(ie); // must never happen
fiore@3 85 }
fiore@3 86 }
fiore@3 87 if(omniHaptics.hapticInitFailed){
fiore@5 88 /* the initialization has failed, the haptic thread is about to die */
fiore@5 89 /* don't kill the listener as initialization will be tried on other devices */
fiore@5 90 // while(!omniHaptics.hapticListener.isInterrupted()){
fiore@5 91 // omniHaptics.hapticListener.interrupt();
fiore@5 92 // }
fiore@5 93 // omniHaptics.hapticListener = null; //leave the listener to the GC
fiore@3 94 return null;
fiore@3 95 }else{
fiore@3 96 return omniHaptics;
fiore@3 97 }
fiore@3 98 }
fiore@3 99
fiore@3 100 private OmniHaptics(String threadName){
fiore@3 101 super(threadName);
fiore@3 102 /* get the screen size which will be passed to init methos in order to set up a window
fiore@3 103 * for the haptic with the same size as the swing one
fiore@3 104 */
fiore@3 105 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
fiore@3 106
fiore@3 107 int screenWidth = (int)screenSize.getWidth();
fiore@3 108 int screenHeight = (int)screenSize.getHeight();
fiore@3 109
fiore@3 110 width = screenWidth * 5 / 8;
fiore@3 111 height = screenHeight * 5 / 8;
fiore@3 112
fiore@3 113 newHapticId = false;
fiore@3 114 dumpHapticId = false;
fiore@3 115 shutdown = false;
fiore@3 116 hapticInitFailed = false;
fiore@3 117 nodes = new HashMap<String,ArrayList<Node>>();
fiore@3 118 edges = new HashMap<String,ArrayList<Edge>>();
fiore@5 119 currentNodes = Empties.EMPTY_NODE_LIST;
fiore@5 120 currentEdges = Empties.EMPTY_EDGE_LIST;
fiore@3 121 nodesMaps = new HashMap<String,HashMap<Integer,Node>>();
fiore@3 122 edgesMaps = new HashMap<String,HashMap<Integer,Edge>>();
fiore@5 123 currentNodesMap = Empties.EMPTY_NODE_MAP;
fiore@5 124 currentEdgesMap = Empties.EMPTY_EDGE_MAP;
fiore@3 125 }
fiore@3 126
fiore@3 127
fiore@5 128 public native int initOmni(int width, int height) throws IOException;
fiore@3 129
fiore@3 130 @Override
fiore@5 131 public void addNewDiagram(String diagramName){
fiore@3 132 ArrayList<Node> cNodes = new ArrayList<Node>(30);
fiore@3 133 ArrayList<Edge> cEdges = new ArrayList<Edge>(30);
fiore@5 134 nodes.put(diagramName, cNodes);
fiore@5 135 edges.put(diagramName, cEdges);
fiore@3 136
fiore@3 137 HashMap<Integer,Node> cNodesMap = new HashMap<Integer,Node>();
fiore@3 138 HashMap<Integer,Edge> cEdgesMap = new HashMap<Integer,Edge>();
fiore@5 139 nodesMaps.put(diagramName, cNodesMap);
fiore@5 140 edgesMaps.put(diagramName, cEdgesMap);
fiore@3 141
fiore@5 142 synchronized(this){
fiore@5 143 currentNodes = cNodes;
fiore@5 144 currentEdges = cEdges;
fiore@5 145 currentNodesMap = cNodesMap;
fiore@5 146 currentEdgesMap = cEdgesMap;
fiore@3 147 }
fiore@3 148 }
fiore@3 149
fiore@3 150 @Override
fiore@3 151 public synchronized void switchDiagram(String diagramName){
fiore@3 152 // check nodes only, as the edges and nodes maps are strongly coupled
fiore@3 153 if(!nodes.containsKey(diagramName))
fiore@5 154 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName);
fiore@3 155
fiore@3 156 currentNodes = nodes.get(diagramName);
fiore@3 157 currentEdges = edges.get(diagramName);
fiore@3 158 currentNodesMap = nodesMaps.get(diagramName);
fiore@3 159 currentEdgesMap = edgesMaps.get(diagramName);
fiore@3 160 }
fiore@3 161
fiore@3 162 @Override
fiore@3 163 public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext){
fiore@3 164 if(!nodes.containsKey(diagramNameToRemove))
fiore@5 165 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove);
fiore@3 166
fiore@3 167 nodes.remove(diagramNameToRemove);
fiore@3 168 edges.remove(diagramNameToRemove);
fiore@3 169 nodesMaps.remove(diagramNameToRemove);
fiore@3 170 edgesMaps.remove(diagramNameToRemove);
fiore@3 171 if(diagramNameNext == null){
fiore@5 172 currentNodes = Empties.EMPTY_NODE_LIST;
fiore@5 173 currentEdges = Empties.EMPTY_EDGE_LIST;
fiore@5 174 currentNodesMap = Empties.EMPTY_NODE_MAP;
fiore@5 175 currentEdgesMap = Empties.EMPTY_EDGE_MAP;
fiore@3 176 }else{
fiore@3 177 if(!nodes.containsKey(diagramNameNext))
fiore@5 178 throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameNext);
fiore@3 179 currentNodes = nodes.get(diagramNameNext);
fiore@3 180 currentEdges = edges.get(diagramNameNext);
fiore@3 181 currentNodesMap = nodesMaps.get(diagramNameNext);
fiore@3 182 currentEdgesMap = edgesMaps.get(diagramNameNext);
fiore@3 183 }
fiore@3 184 }
fiore@3 185
fiore@3 186 @Override
fiore@3 187 public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName){
fiore@3 188 newHapticId = true;
fiore@3 189 // waits for an identifier from the openGL thread
fiore@3 190 try{
fiore@3 191 wait();
fiore@3 192 }catch(InterruptedException ie){
fiore@3 193 wasInterrupted();
fiore@3 194 }
fiore@3 195
fiore@3 196 Node n = new Node(x,y,nodeHashCode, currentHapticId);
fiore@3 197 if(diagramName == null){
fiore@3 198 currentNodes.add(n);
fiore@3 199 currentNodesMap.put(currentHapticId, n);
fiore@3 200 }else{
fiore@3 201 nodes.get(diagramName).add(n);
fiore@3 202 nodesMaps.get(diagramName).put(currentHapticId, n);
fiore@3 203 }
fiore@3 204 }
fiore@3 205
fiore@3 206 @Override
fiore@3 207 public synchronized void removeNode(int nodeHashCode, String diagramName){
fiore@3 208 ListIterator<Node> itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator();
fiore@3 209 boolean found = false;
fiore@3 210 int hID = -1;
fiore@3 211 while(itr.hasNext()){
fiore@3 212 Node n = itr.next();
fiore@3 213 if(n.diagramId == nodeHashCode){
fiore@3 214 hID = n.hapticId;
fiore@3 215 itr.remove();
fiore@3 216 found = true;
fiore@3 217 break;
fiore@3 218 }
fiore@3 219 }
fiore@3 220 assert(found);
fiore@3 221
fiore@3 222 /* remove the node from the map as well */
fiore@3 223 if(diagramName == null)
fiore@3 224 currentNodesMap.remove(hID);
fiore@3 225 else
fiore@3 226 nodesMaps.get(diagramName).remove(hID);
fiore@3 227
fiore@3 228 /* set the flag to ask the haptic thread to free the id of the node that's been deleted */
fiore@3 229 dumpHapticId = true;
fiore@3 230 /* share the id to free with the other thread */
fiore@3 231 currentHapticId = hID;
fiore@3 232 try{
fiore@3 233 wait();
fiore@3 234 }catch(InterruptedException ie){
fiore@3 235 wasInterrupted();
fiore@3 236 }
fiore@3 237 }
fiore@3 238
fiore@3 239 @Override
fiore@3 240 public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName){
fiore@3 241 ArrayList<Node> iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName);
fiore@3 242 for(Node n : iterationList){
fiore@3 243 if(n.diagramId == nodeHashCode){
fiore@3 244 n.x = x;
fiore@5 245 n.y = y;
fiore@3 246 break;
fiore@3 247 }
fiore@3 248 }
fiore@3 249 }
fiore@3 250
fiore@3 251 @Override
fiore@3 252 public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName){
fiore@3 253 // flag the openGL thread the fact we need an identifier
fiore@3 254 newHapticId = true;
fiore@3 255 // waits for an identifier from the openGL thread
fiore@3 256 try{
fiore@3 257 wait();
fiore@3 258 }catch(InterruptedException ie){
fiore@3 259 wasInterrupted();
fiore@3 260 }
fiore@3 261
fiore@3 262 // find the mid point of the line of attraction
fiore@3 263 double pX = Math.min(attractLine.getX1(), attractLine.getX2());
fiore@3 264 double pY = Math.min(attractLine.getY1(), attractLine.getY2());
fiore@3 265 pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2;
fiore@3 266 pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2;
fiore@3 267
fiore@3 268 Edge e = new Edge(edgeHashCode,currentHapticId, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY);
fiore@3 269 if(diagramName == null){
fiore@3 270 /* add the edge reference to the Haptic edges list */
fiore@3 271 currentEdges.add(e);
fiore@5 272 /* add the edge reference to the Haptic edges map */
fiore@3 273 currentEdgesMap.put(currentHapticId, e);
fiore@3 274 }else{
fiore@3 275 /* add the edge reference to the Haptic edges list */
fiore@3 276 edges.get(diagramName).add(e);
fiore@5 277 /* add the edge reference to the Haptic edges map */
fiore@3 278 edgesMaps.get(diagramName).put(currentHapticId, e);
fiore@3 279 }
fiore@3 280 }
fiore@3 281
fiore@3 282 @Override
fiore@3 283 public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName){
fiore@3 284 assert(xs.length == ys.length);
fiore@3 285
fiore@3 286 for(Edge e : currentEdges){
fiore@3 287 if(e.diagramId == edgeHashCode){
fiore@3 288 e.xs = xs;
fiore@3 289 e.ys = ys;
fiore@3 290 e.size = xs.length;
fiore@3 291 e.adjMatrix = adjMatrix;
fiore@3 292 e.nodeStart = nodeStart;
fiore@3 293 // find the mid point of the line of attraction
fiore@3 294 double pX = Math.min(attractLine.getX1(), attractLine.getX2());
fiore@3 295 double pY = Math.min(attractLine.getY1(), attractLine.getY2());
fiore@3 296 pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2;
fiore@3 297 pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2;
fiore@3 298 e.attractPointX = pX;
fiore@3 299 e.attractPointY = pY;
fiore@3 300 }
fiore@3 301 }
fiore@3 302 }
fiore@3 303
fiore@3 304 @Override
fiore@3 305 public synchronized void removeEdge(int edgeHashCode, String diagramName){
fiore@3 306 ListIterator<Edge> itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator();
fiore@3 307 boolean found = false;
fiore@3 308 int hID = -1;
fiore@3 309 while(itr.hasNext()){
fiore@3 310 Edge e = itr.next();
fiore@3 311 if(e.diagramId == edgeHashCode){
fiore@3 312 hID = e.hapticId;
fiore@3 313 itr.remove();
fiore@3 314 found = true;
fiore@3 315 break;
fiore@3 316 }
fiore@3 317 }
fiore@3 318 assert(found);
fiore@3 319
fiore@3 320 /* remove the edge from the map as well */
fiore@3 321 if(diagramName == null)
fiore@3 322 currentEdgesMap.remove(hID);
fiore@3 323 else
fiore@3 324 edgesMaps.get(diagramName).remove(hID);
fiore@3 325 /* set the flag to ask the haptic thread to free the id of the node that's been deleted */
fiore@3 326 dumpHapticId = true;
fiore@3 327 /* share the id to free with the other thread */
fiore@3 328 currentHapticId = hID;
fiore@3 329 try{
fiore@3 330 wait();
fiore@3 331 }catch(InterruptedException ie){
fiore@3 332 wasInterrupted();
fiore@3 333 }
fiore@3 334 }
fiore@3 335
fiore@3 336 @Override
fiore@3 337 public synchronized void attractTo(int elementHashCode){
fiore@3 338 attractToHapticId = findElementHapticID(elementHashCode);
fiore@3 339 attractTo = true;
fiore@3 340 }
fiore@3 341
fiore@3 342 @Override
fiore@3 343 public synchronized void pickUp(int elementHashCode){
fiore@3 344 pickUpHapticId = findElementHapticID(elementHashCode);
fiore@3 345 pickUp = true;
fiore@3 346 }
fiore@3 347
fiore@3 348 private int findElementHapticID(int elementHashCode){
fiore@3 349 int hID = -1;
fiore@3 350 boolean found = false;
fiore@3 351 for(Node n : currentNodes){
fiore@3 352 if(n.diagramId == elementHashCode){
fiore@3 353 hID = n.hapticId;
fiore@3 354 found = true;
fiore@3 355 break;
fiore@3 356 }
fiore@3 357 }
fiore@3 358
fiore@3 359 if(!found)
fiore@3 360 for(Edge e : currentEdges){
fiore@3 361 if(e.diagramId == elementHashCode){
fiore@3 362 hID = e.hapticId;
fiore@3 363 found = true;
fiore@3 364 break;
fiore@3 365 }
fiore@3 366 }
fiore@3 367 assert(found);
fiore@3 368 return hID;
fiore@3 369 }
fiore@3 370
fiore@3 371 @Override
fiore@5 372 public void setVisible(boolean visible){
fiore@5 373 // not implemented but required by Haptic interface
fiore@5 374 }
fiore@5 375
fiore@5 376 @Override
fiore@3 377 public synchronized void dispose(){
fiore@3 378 shutdown = true;
fiore@3 379 /* wait for the haptic thread to shut down */
fiore@3 380 try {
fiore@3 381 wait();
fiore@3 382 } catch (InterruptedException e) {
fiore@3 383 wasInterrupted();
fiore@3 384 }
fiore@3 385 }
fiore@3 386
fiore@3 387 @Override
fiore@3 388 public void run() {
fiore@3 389 try {
fiore@5 390 initOmni(width,height);
fiore@3 391 } catch (IOException e) {
fiore@3 392 throw new RuntimeException();// OMNI haptic device doesn't cause any exception
fiore@3 393 }
fiore@3 394 }
fiore@3 395
fiore@3 396 private void wasInterrupted(){
fiore@3 397 throw new UnsupportedOperationException("Haptics thread interrupted and no catch block implemented");
fiore@3 398 }
fiore@3 399
fiore@3 400 private Node getNodeFromID(int hID){
fiore@3 401 return currentNodesMap.get(hID);
fiore@3 402 }
fiore@3 403
fiore@3 404 private Edge getEdgeFromID(int hID){
fiore@3 405 return currentEdgesMap.get(hID);
fiore@3 406 }
fiore@3 407
fiore@3 408 /* the diagram currently selected */
fiore@3 409 private ArrayList<Node> currentNodes;
fiore@3 410 private ArrayList<Edge> currentEdges;
fiore@3 411 private HashMap<Integer,Node> currentNodesMap;
fiore@3 412 private HashMap<Integer,Edge> currentEdgesMap;
fiore@3 413
fiore@3 414 /* maps with all the diagrams in the editor */
fiore@3 415 private HashMap<String,ArrayList<Node>> nodes;
fiore@3 416 private HashMap<String,ArrayList<Edge>> edges;
fiore@3 417 private HashMap<String,HashMap<Integer,Node>> nodesMaps;
fiore@3 418 private HashMap<String,HashMap<Integer,Edge>> edgesMaps;
fiore@3 419 private int width;
fiore@3 420 private int height;
fiore@3 421 private int attractToHapticId;
fiore@3 422 private int pickUpHapticId;
fiore@3 423 /* flag for synchronization with the haptic thread*/
fiore@3 424 private boolean newHapticId;
fiore@3 425 private boolean dumpHapticId;
fiore@3 426 private boolean shutdown;
fiore@3 427 boolean hapticInitFailed;
fiore@3 428 private boolean attractTo;
fiore@3 429 private boolean pickUp;
fiore@3 430 /* currentHapticId is used to share haptic ids between the threads */
fiore@3 431 private int currentHapticId;
fiore@5 432 private HapticListenerThread hapticListener;
fiore@3 433
fiore@3 434 }