annotate java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java @ 8:ea7885bd9bff tip

fixed bug : render solid line as dotted/dashed when moving the stylus from dotted/dashed to solid
author ccmi-guest
date Thu, 03 Jul 2014 16:12:20 +0100
parents d66dd5880081
children
rev   line source
fiore@0 1 /*
fiore@0 2 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool
fiore@0 3
fiore@0 4 Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)
fiore@0 5
fiore@0 6 This program is free software: you can redistribute it and/or modify
fiore@0 7 it under the terms of the GNU General Public License as published by
fiore@0 8 the Free Software Foundation, either version 3 of the License, or
fiore@0 9 (at your option) any later version.
fiore@0 10
fiore@0 11 This program is distributed in the hope that it will be useful,
fiore@0 12 but WITHOUT ANY WARRANTY; without even the implied warranty of
fiore@0 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
fiore@0 14 GNU General Public License for more details.
fiore@0 15
fiore@0 16 You should have received a copy of the GNU General Public License
fiore@0 17 along with this program. If not, see <http://www.gnu.org/licenses/>.
fiore@0 18 */
fiore@0 19 package uk.ac.qmul.eecs.ccmi.gui;
fiore@0 20
fiore@0 21 import java.awt.geom.Point2D;
fiore@0 22 import java.awt.geom.Rectangle2D;
fiore@3 23 import java.util.ResourceBundle;
fiore@0 24
fiore@0 25 import javax.swing.SwingUtilities;
fiore@0 26
fiore@0 27 import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel;
fiore@0 28 import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
fiore@0 29 import uk.ac.qmul.eecs.ccmi.haptics.HapticListener;
fiore@5 30 import uk.ac.qmul.eecs.ccmi.haptics.HapticListenerThread;
fiore@0 31 import uk.ac.qmul.eecs.ccmi.haptics.HapticListenerCommand;
fiore@3 32 import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp;
fiore@3 33 import uk.ac.qmul.eecs.ccmi.network.Command;
fiore@3 34 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
fiore@3 35 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
fiore@0 36 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
fiore@0 37 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
fiore@0 38 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
fiore@0 39
fiore@0 40 /**
fiore@0 41 *
fiore@0 42 * An instance of HapticListener for the diagram editor. By this class visual diagrams
fiore@5 43 * can be manipulated by an haptic device. This class extends the {@code Thread} class,
fiore@5 44 * and can therefore be run on a separate thread listening to the haptic commands coming from the thread
fiore@5 45 * managing the haptic device. The commands affecting the Swing components will
fiore@5 46 * be queued for execution on the code Event Dispatching Thread event queue.
fiore@0 47 *
fiore@0 48 */
fiore@5 49 public class HapticKindle extends HapticListenerThread {
fiore@0 50
fiore@0 51 public HapticKindle(){
fiore@0 52 super();
fiore@5 53 cmdImpl = new CommandImplementation();
fiore@5 54 /* unselect always ends up to the same instruction. Therefore don't create a new runnable *
fiore@5 55 * each time the command is issued, but rather keep and reuse always the same class */
fiore@0 56 unselectRunnable = new Runnable(){
fiore@0 57 @Override
fiore@0 58 public void run(){
fiore@5 59 cmdImpl.executeCommand(HapticListenerCommand.UNSELECT, 0, 0, 0, 0, 0);
fiore@0 60 }
fiore@0 61 };
fiore@0 62 }
fiore@0 63
fiore@3 64 /**
fiore@3 65 * Implementation of the {@code executeCommand} method. All the commands that involve the
fiore@3 66 * diagram model, are executed in the Event Dispatching Thread through {@code SwingUtilities} invoke
fiore@3 67 * methods. This prevents race conditions on the model and on diagram elements.
fiore@3 68 *
fiore@5 69 * @see HapticListenerThread#executeCommand(HapticListenerCommand, int, double, double, double, double)
fiore@3 70 */
fiore@0 71 @Override
fiore@5 72 public void executeCommand(final HapticListenerCommand cmd, final int ID, final double x, final double y, final double startX, final double startY) {
fiore@0 73 switch(cmd){
fiore@3 74 case PLAY_ELEMENT_SOUND :
fiore@5 75 case PLAY_ELEMENT_SPEECH :
fiore@5 76 case SELECT :
fiore@5 77 case INFO :
fiore@5 78 case ERROR :
fiore@3 79 SwingUtilities.invokeLater(new Runnable(){
fiore@3 80 public void run(){
fiore@5 81 cmdImpl.executeCommand(cmd, ID, x, y, startX, startY);
fiore@0 82 }
fiore@0 83 });
fiore@0 84 break;
fiore@0 85 case UNSELECT :
fiore@0 86 SwingUtilities.invokeLater(unselectRunnable);
fiore@0 87 break;
fiore@5 88 case PICK_UP :
fiore@3 89 case MOVE : {
fiore@3 90 /* when this block is executed we already have the lock *
fiore@3 91 * on the element from the PICK_UP command execution */
fiore@0 92 try {
fiore@0 93 SwingUtilities.invokeAndWait(new Runnable(){
fiore@0 94 @Override
fiore@0 95 public void run(){
fiore@5 96 cmdImpl.executeCommand(cmd, ID, x, y, startX, startY);
fiore@3 97 } // run()
fiore@0 98 });
fiore@0 99 } catch (Exception e) {
fiore@0 100 throw new RuntimeException(e);
fiore@0 101 }
fiore@3 102 }
fiore@0 103 break;
fiore@5 104
fiore@5 105 case PLAY_SOUND :
fiore@5 106 cmdImpl.executeCommand(cmd, ID, x, y, startX, startY); // not in the Event Dispatching Thread
fiore@5 107 break;
fiore@5 108 }
fiore@5 109
fiore@5 110 }
fiore@5 111
fiore@5 112 /**
fiore@5 113 * Returns the delegate inner implementation of the {@code HapticListener} commands.
fiore@5 114 * When called directly from the returned {@code HapticListener} the commands won't be executed on a separate thread.
fiore@5 115 *
fiore@5 116 * @return the {@code HapticListener} command implementation
fiore@5 117 */
fiore@5 118 @Override
fiore@5 119 public HapticListener getNonRunnableListener(){
fiore@5 120 return cmdImpl;
fiore@5 121 }
fiore@5 122
fiore@5 123
fiore@5 124 private Runnable unselectRunnable;
fiore@5 125 private CommandImplementation cmdImpl;
fiore@5 126 private static String INTERACTION_LOG_SOURCE = "HAPTIC";
fiore@5 127
fiore@5 128 /* An inner class with the implementation of all the commands. HapticKindle runs *
fiore@5 129 * on its own thread and delegates the real commands implementation to this class */
fiore@5 130 private static class CommandImplementation implements HapticListener{
fiore@5 131 @Override
fiore@5 132 public void executeCommand(HapticListenerCommand cmd, int ID, double x,
fiore@5 133 double y, double startX, double startY) {
fiore@5 134 final EditorFrame frame = DiagramEditorApp.getFrame();
fiore@5 135 switch(cmd){
fiore@5 136 case PLAY_ELEMENT_SOUND : {
fiore@5 137 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 138 return;
fiore@5 139 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 140 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 141 /* can be null if the tab has been switched or closed in the meantime */
fiore@5 142 if(de == null)
fiore@5 143 return;
fiore@5 144 SoundFactory.getInstance().play(de.getSound());
fiore@5 145 }break;
fiore@5 146 case PLAY_ELEMENT_SPEECH : {
fiore@5 147 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 148 return;
fiore@5 149 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 150 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 151 if(de == null)
fiore@5 152 return;
fiore@5 153 SoundFactory.getInstance().play(de.getSound());
fiore@5 154 NarratorFactory.getInstance().speak(de.getName());
fiore@5 155 iLog("touch",((de instanceof Node) ? "node " : "edge ")+de.getName());
fiore@5 156 }break;
fiore@5 157 case SELECT : {
fiore@5 158 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 159 return;
fiore@5 160 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 161 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 162 if(de == null)
fiore@5 163 return;
fiore@5 164 frame.selectHapticHighligh(de);
fiore@5 165 }break;
fiore@5 166 case UNSELECT : {
fiore@5 167 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 168 return;
fiore@5 169 frame.selectHapticHighligh(null);
fiore@5 170 }break;
fiore@5 171 case MOVE : {
fiore@5 172 /* when this block is executed we already have the lock *
fiore@5 173 * on the element from the PICK_UP command execution */
fiore@5 174 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 175 return;
fiore@5 176 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 177 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 178 if(de == null)
fiore@5 179 return;
fiore@5 180 DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
fiore@5 181 if(de instanceof Node){
fiore@5 182 Node n = (Node)de;
fiore@5 183 Rectangle2D bounds = n.getBounds();
fiore@5 184 Point2D.Double p = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
fiore@5 185 double dx = x - p.getX();
fiore@5 186 double dy = y - p.getY();
fiore@5 187 n.getMonitor().lock();
fiore@5 188 modelUpdater.translate(n, p, dx, dy,DiagramEventSource.HAPT);
fiore@5 189 modelUpdater.stopMove(n,DiagramEventSource.HAPT);
fiore@5 190 n.getMonitor().unlock();
fiore@5 191
fiore@5 192 StringBuilder builder = new StringBuilder();
fiore@5 193 builder.append(DiagramElement.toLogString(n)).append(" ").append(p.getX())
fiore@5 194 .append(' ').append(p.getY());
fiore@5 195 iLog("move node start",builder.toString());
fiore@5 196 builder = new StringBuilder();
fiore@5 197 builder.append(DiagramElement.toLogString(n)).append(' ')
fiore@5 198 .append(x).append(' ').append(y);
fiore@5 199 iLog("move node end",builder.toString());
fiore@5 200 }else{
fiore@5 201 Edge e = (Edge)de;
fiore@5 202 modelUpdater.startMove(e, new Point2D.Double(startX,startY),DiagramEventSource.HAPT);
fiore@5 203 Point2D p = new Point2D.Double(x,y);
fiore@5 204 e.getMonitor().lock();
fiore@5 205 modelUpdater.bend(e, p,DiagramEventSource.HAPT);
fiore@5 206 modelUpdater.stopMove(e,DiagramEventSource.HAPT);
fiore@5 207 e.getMonitor().unlock();
fiore@5 208
fiore@5 209 StringBuilder builder = new StringBuilder();
fiore@5 210 builder.append(DiagramElement.toLogString(e)).append(' ').append(startX)
fiore@5 211 .append(' ').append(startY);
fiore@5 212 iLog("bend edge start",builder.toString());
fiore@5 213 builder = new StringBuilder();
fiore@5 214 builder.append(DiagramElement.toLogString(e)).append(' ')
fiore@5 215 .append(x).append(' ').append(y);
fiore@5 216 iLog("bend edge end",builder.toString());
fiore@3 217 }
fiore@5 218 modelUpdater.yieldLock(de,
fiore@5 219 Lock.MOVE,
fiore@5 220 new DiagramEventActionSource(
fiore@5 221 DiagramEventSource.HAPT,
fiore@5 222 de instanceof Node ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
fiore@5 223 de.getId(),
fiore@5 224 de.getName()
fiore@5 225 ));
fiore@5 226 SoundFactory.getInstance().play(SoundEvent.HOOK_OFF);
fiore@5 227 }break;
fiore@5 228 case INFO : {
fiore@5 229 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 230 return;
fiore@5 231 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 232 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 233 if(de == null)
fiore@5 234 return;
fiore@5 235 SoundFactory.getInstance().stop();
fiore@5 236 NarratorFactory.getInstance().speak(de.detailedSpokenText());
fiore@5 237 iLog("request detailed info",((de instanceof Node) ? "node " : "edge ")+de.getName());
fiore@5 238 }break;
fiore@5 239 case PLAY_SOUND : {
fiore@5 240 switch(HapticListenerCommand.Sound.fromInt(ID) ){
fiore@5 241 case MAGNET_OFF :
fiore@5 242 SoundFactory.getInstance().play(SoundEvent.MAGNET_OFF);
fiore@5 243 iLog("sticky mode off","");
fiore@5 244 break;
fiore@5 245 case MAGNET_ON :
fiore@5 246 SoundFactory.getInstance().play(SoundEvent.MAGNET_ON);
fiore@5 247 iLog("sticky mode on","");
fiore@5 248 break;
fiore@5 249 case DRAG : SoundFactory.getInstance().play(SoundEvent.DRAG);
fiore@5 250 break;
fiore@5 251 }
fiore@5 252 }break;
fiore@5 253 case PICK_UP :{
fiore@5 254 if((frame == null)||(frame.getActiveTab() == null))
fiore@5 255 return;
fiore@5 256 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
fiore@5 257 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
fiore@5 258 if(de == null)
fiore@5 259 return;
fiore@5 260 DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
fiore@5 261 if(!modelUpdater.getLock(de,
fiore@5 262 Lock.MOVE,
fiore@5 263 new DiagramEventActionSource(DiagramEventSource.HAPT, de instanceof Edge ? Command.Name.TRANSLATE_EDGE : Command.Name.TRANSLATE_NODE ,de.getId(),de.getName()))){
fiore@5 264 iLog("Could not get lock on element for motion", DiagramElement.toLogString(de));
fiore@5 265 NarratorFactory.getInstance().speak("Object is being moved by another user");
fiore@5 266 return;
fiore@5 267 }
fiore@5 268 frame.hPickUp(de);
fiore@5 269 SoundFactory.getInstance().play(SoundEvent.HOOK_ON);
fiore@5 270 iLog("hook on","");
fiore@5 271 }break;
fiore@5 272 case ERROR : {
fiore@3 273 if((frame == null)||(frame.getActiveTab() == null))
fiore@3 274 return;
fiore@5 275 frame.backupOpenDiagrams();
fiore@5 276 NarratorFactory.getInstance().speak(ResourceBundle.getBundle(EditorFrame.class.getName()).getString("speech.haptic_device_crashed"));
fiore@5 277 }break;
fiore@3 278 }
fiore@5 279 }
fiore@5 280
fiore@5 281 private void iLog(String action, String args){
fiore@5 282 InteractionLog.log(INTERACTION_LOG_SOURCE,action,args);
fiore@0 283 }
fiore@0 284 }
fiore@0 285
fiore@0 286 }