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@0
|
30 import uk.ac.qmul.eecs.ccmi.haptics.HapticListenerCommand;
|
fiore@3
|
31 import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp;
|
fiore@3
|
32 import uk.ac.qmul.eecs.ccmi.network.Command;
|
fiore@3
|
33 import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
|
fiore@3
|
34 import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
|
fiore@0
|
35 import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
|
fiore@0
|
36 import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
|
fiore@0
|
37 import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
|
fiore@0
|
38
|
fiore@0
|
39 /**
|
fiore@0
|
40 *
|
fiore@0
|
41 * An instance of HapticListener for the diagram editor. By this class visual diagrams
|
fiore@0
|
42 * can be manipulated by an haptic device.
|
fiore@0
|
43 *
|
fiore@0
|
44 */
|
fiore@0
|
45 public class HapticKindle extends HapticListener {
|
fiore@0
|
46
|
fiore@0
|
47 public HapticKindle(){
|
fiore@0
|
48 super();
|
fiore@0
|
49 unselectRunnable = new Runnable(){
|
fiore@0
|
50 @Override
|
fiore@0
|
51 public void run(){
|
fiore@3
|
52 EditorFrame frame = DiagramEditorApp.getFrame();
|
fiore@3
|
53 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
54 return;
|
fiore@0
|
55 frame.selectHapticHighligh(null);
|
fiore@0
|
56 }
|
fiore@0
|
57 };
|
fiore@0
|
58 }
|
fiore@0
|
59
|
fiore@3
|
60 /**
|
fiore@3
|
61 * Implementation of the {@code executeCommand} method. All the commands that involve the
|
fiore@3
|
62 * diagram model, are executed in the Event Dispatching Thread through {@code SwingUtilities} invoke
|
fiore@3
|
63 * methods. This prevents race conditions on the model and on diagram elements.
|
fiore@3
|
64 *
|
fiore@3
|
65 * @see HapticListener#executeCommand(HapticListenerCommand, int, double, double, double, double)
|
fiore@3
|
66 */
|
fiore@0
|
67 @Override
|
fiore@3
|
68 public void executeCommand(HapticListenerCommand cmd, final int ID, final double x, final double y, final double startX, final double startY) {
|
fiore@3
|
69 final EditorFrame frame = DiagramEditorApp.getFrame();
|
fiore@0
|
70 switch(cmd){
|
fiore@3
|
71 case PLAY_ELEMENT_SOUND :
|
fiore@3
|
72 SwingUtilities.invokeLater(new Runnable(){
|
fiore@3
|
73 public void run(){
|
fiore@3
|
74 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
75 return;
|
fiore@3
|
76 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
77 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
78 /* can be null if the tab has been switched or closed in the meantime */
|
fiore@3
|
79 if(de == null)
|
fiore@3
|
80 return;
|
fiore@3
|
81 SoundFactory.getInstance().play(de.getSound());
|
fiore@3
|
82 }
|
fiore@3
|
83 });
|
fiore@0
|
84 break;
|
fiore@0
|
85 case PLAY_ELEMENT_SPEECH :
|
fiore@3
|
86 SwingUtilities.invokeLater(new Runnable(){
|
fiore@3
|
87 public void run(){
|
fiore@3
|
88 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
89 return;
|
fiore@3
|
90 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
91 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
92 if(de == null)
|
fiore@3
|
93 return;
|
fiore@3
|
94 SoundFactory.getInstance().play(de.getSound());
|
fiore@3
|
95 NarratorFactory.getInstance().speak(de.getName());
|
fiore@3
|
96 iLog("touch",((de instanceof Node) ? "node " : "edge ")+de.getName());
|
fiore@3
|
97 }
|
fiore@3
|
98 });
|
fiore@0
|
99 break;
|
fiore@0
|
100 case SELECT :
|
fiore@0
|
101 SwingUtilities.invokeLater(new Runnable(){
|
fiore@0
|
102 public void run(){
|
fiore@3
|
103 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
104 return;
|
fiore@3
|
105 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
106 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
107 if(de == null)
|
fiore@3
|
108 return;
|
fiore@3
|
109 frame.selectHapticHighligh(de);
|
fiore@0
|
110 }
|
fiore@0
|
111 });
|
fiore@0
|
112 break;
|
fiore@0
|
113 case UNSELECT :
|
fiore@0
|
114 SwingUtilities.invokeLater(unselectRunnable);
|
fiore@0
|
115 break;
|
fiore@3
|
116 case MOVE : {
|
fiore@3
|
117 /* when this block is executed we already have the lock *
|
fiore@3
|
118 * on the element from the PICK_UP command execution */
|
fiore@0
|
119 try {
|
fiore@0
|
120 SwingUtilities.invokeAndWait(new Runnable(){
|
fiore@0
|
121 @Override
|
fiore@0
|
122 public void run(){
|
fiore@3
|
123 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
124 return;
|
fiore@3
|
125 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
126 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
127 if(de == null)
|
fiore@3
|
128 return;
|
fiore@3
|
129 DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
|
fiore@3
|
130 if(de instanceof Node){
|
fiore@3
|
131 Node n = (Node)de;
|
fiore@0
|
132 Rectangle2D bounds = n.getBounds();
|
fiore@0
|
133 Point2D.Double p = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY());
|
fiore@0
|
134 double dx = x - p.getX();
|
fiore@0
|
135 double dy = y - p.getY();
|
fiore@3
|
136 n.getMonitor().lock();
|
fiore@3
|
137 modelUpdater.translate(n, p, dx, dy,DiagramEventSource.HAPT);
|
fiore@3
|
138 modelUpdater.stopMove(n,DiagramEventSource.HAPT);
|
fiore@3
|
139 n.getMonitor().unlock();
|
fiore@0
|
140
|
fiore@0
|
141 StringBuilder builder = new StringBuilder();
|
fiore@0
|
142 builder.append(DiagramElement.toLogString(n)).append(" ").append(p.getX())
|
fiore@0
|
143 .append(' ').append(p.getY());
|
fiore@0
|
144 iLog("move node start",builder.toString());
|
fiore@0
|
145 builder = new StringBuilder();
|
fiore@0
|
146 builder.append(DiagramElement.toLogString(n)).append(' ')
|
fiore@0
|
147 .append(x).append(' ').append(y);
|
fiore@0
|
148 iLog("move node end",builder.toString());
|
fiore@3
|
149 }else{
|
fiore@3
|
150 Edge e = (Edge)de;
|
fiore@3
|
151 modelUpdater.startMove(e, new Point2D.Double(startX,startY),DiagramEventSource.HAPT);
|
fiore@3
|
152 Point2D p = new Point2D.Double(x,y);
|
fiore@3
|
153 e.getMonitor().lock();
|
fiore@3
|
154 modelUpdater.bend(e, p,DiagramEventSource.HAPT);
|
fiore@3
|
155 modelUpdater.stopMove(e,DiagramEventSource.HAPT);
|
fiore@3
|
156 e.getMonitor().unlock();
|
fiore@0
|
157
|
fiore@0
|
158 StringBuilder builder = new StringBuilder();
|
fiore@0
|
159 builder.append(DiagramElement.toLogString(e)).append(' ').append(startX)
|
fiore@0
|
160 .append(' ').append(startY);
|
fiore@0
|
161 iLog("bend edge start",builder.toString());
|
fiore@0
|
162 builder = new StringBuilder();
|
fiore@0
|
163 builder.append(DiagramElement.toLogString(e)).append(' ')
|
fiore@0
|
164 .append(x).append(' ').append(y);
|
fiore@0
|
165 iLog("bend edge end",builder.toString());
|
fiore@0
|
166 }
|
fiore@3
|
167 modelUpdater.yieldLock(de,
|
fiore@3
|
168 Lock.MOVE,
|
fiore@3
|
169 new DiagramEventActionSource(
|
fiore@3
|
170 DiagramEventSource.HAPT,
|
fiore@3
|
171 de instanceof Node ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE,
|
fiore@3
|
172 de.getId(),
|
fiore@3
|
173 de.getName()
|
fiore@3
|
174 ));
|
fiore@3
|
175 } // run()
|
fiore@0
|
176 });
|
fiore@0
|
177 } catch (Exception e) {
|
fiore@0
|
178 throw new RuntimeException(e);
|
fiore@0
|
179 }
|
fiore@0
|
180 SoundFactory.getInstance().play(SoundEvent.HOOK_OFF);
|
fiore@3
|
181 }
|
fiore@0
|
182 break;
|
fiore@3
|
183 case INFO :
|
fiore@3
|
184 SwingUtilities.invokeLater(new Runnable(){
|
fiore@3
|
185 public void run(){
|
fiore@3
|
186 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
187 return;
|
fiore@3
|
188 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
189 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
190 if(de == null)
|
fiore@3
|
191 return;
|
fiore@3
|
192 SoundFactory.getInstance().stop();
|
fiore@3
|
193 NarratorFactory.getInstance().speak(de.detailedSpokenText());
|
fiore@3
|
194 iLog("request detailed info",((de instanceof Node) ? "node " : "edge ")+de.getName());
|
fiore@3
|
195 }
|
fiore@3
|
196 });
|
fiore@0
|
197 break;
|
fiore@0
|
198 case PLAY_SOUND :
|
fiore@0
|
199 switch(HapticListenerCommand.Sound.fromInt(ID) ){
|
fiore@0
|
200 case MAGNET_OFF :
|
fiore@0
|
201 SoundFactory.getInstance().play(SoundEvent.MAGNET_OFF);
|
fiore@0
|
202 iLog("sticky mode off","");
|
fiore@0
|
203 break;
|
fiore@0
|
204 case MAGNET_ON :
|
fiore@0
|
205 SoundFactory.getInstance().play(SoundEvent.MAGNET_ON);
|
fiore@0
|
206 iLog("sticky mode on","");
|
fiore@0
|
207 break;
|
fiore@0
|
208 case DRAG : SoundFactory.getInstance().play(SoundEvent.DRAG);
|
fiore@0
|
209 break;
|
fiore@0
|
210 }
|
fiore@0
|
211 break;
|
fiore@3
|
212 case PICK_UP :
|
fiore@3
|
213 try {
|
fiore@3
|
214 SwingUtilities.invokeAndWait(new Runnable (){
|
fiore@3
|
215 @Override
|
fiore@3
|
216 public void run(){
|
fiore@3
|
217 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
218 return;
|
fiore@3
|
219 CollectionModel<Node,Edge> collectionModel = frame.getActiveTab().getDiagram().getCollectionModel();
|
fiore@3
|
220 DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements());
|
fiore@3
|
221 if(de == null)
|
fiore@3
|
222 return;
|
fiore@3
|
223 DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater();
|
fiore@3
|
224 if(!modelUpdater.getLock(de,
|
fiore@3
|
225 Lock.MOVE,
|
fiore@3
|
226 new DiagramEventActionSource(DiagramEventSource.HAPT, de instanceof Edge ? Command.Name.TRANSLATE_EDGE : Command.Name.TRANSLATE_NODE ,de.getId(),de.getName()))){
|
fiore@3
|
227 iLog("Could not get lock on element for motion", DiagramElement.toLogString(de));
|
fiore@3
|
228 NarratorFactory.getInstance().speak("Object is being moved by another user");
|
fiore@3
|
229 return;
|
fiore@3
|
230 }
|
fiore@3
|
231 frame.hPickUp(de);
|
fiore@3
|
232 SoundFactory.getInstance().play(SoundEvent.HOOK_ON);
|
fiore@3
|
233 iLog("hook on","");
|
fiore@3
|
234 }
|
fiore@3
|
235 });
|
fiore@3
|
236 }catch(Exception e){
|
fiore@3
|
237 e.printStackTrace();
|
fiore@3
|
238 throw new RuntimeException();
|
fiore@3
|
239 }
|
fiore@3
|
240 break;
|
fiore@0
|
241 case ERROR :
|
fiore@0
|
242 /* no synchronization necessary as the XMLManager looks after it*/
|
fiore@3
|
243 SwingUtilities.invokeLater(new Runnable(){
|
fiore@3
|
244 @Override
|
fiore@3
|
245 public void run(){
|
fiore@3
|
246 if((frame == null)||(frame.getActiveTab() == null))
|
fiore@3
|
247 return;
|
fiore@3
|
248 frame.backupOpenDiagrams();
|
fiore@3
|
249 }
|
fiore@3
|
250 });
|
fiore@3
|
251 NarratorFactory.getInstance().speak(ResourceBundle.getBundle(EditorFrame.class.getName()).getString("speech.haptic_device_crashed"));
|
fiore@0
|
252 break;
|
fiore@0
|
253 }
|
fiore@0
|
254 }
|
fiore@0
|
255
|
fiore@0
|
256 private void iLog(String action, String args){
|
fiore@0
|
257 InteractionLog.log(INTERACTION_LOG_SOURCE,action,args);
|
fiore@0
|
258 }
|
fiore@0
|
259
|
fiore@0
|
260 private Runnable unselectRunnable;
|
fiore@0
|
261 private static String INTERACTION_LOG_SOURCE = "HAPTIC";
|
fiore@3
|
262 //private EditorFrame frame;
|
fiore@0
|
263 }
|