fiore@0: /* fiore@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@0: fiore@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@0: fiore@0: This program is free software: you can redistribute it and/or modify fiore@0: it under the terms of the GNU General Public License as published by fiore@0: the Free Software Foundation, either version 3 of the License, or fiore@0: (at your option) any later version. fiore@0: fiore@0: This program is distributed in the hope that it will be useful, fiore@0: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@0: GNU General Public License for more details. fiore@0: fiore@0: You should have received a copy of the GNU General Public License fiore@0: along with this program. If not, see . fiore@0: */ fiore@0: package uk.ac.qmul.eecs.ccmi.gui; fiore@0: fiore@0: import java.awt.geom.Point2D; fiore@0: import java.awt.geom.Rectangle2D; fiore@3: import java.util.ResourceBundle; fiore@0: fiore@0: import javax.swing.SwingUtilities; fiore@0: fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.CollectionModel; fiore@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; fiore@0: import uk.ac.qmul.eecs.ccmi.haptics.HapticListener; fiore@0: import uk.ac.qmul.eecs.ccmi.haptics.HapticListenerCommand; fiore@3: import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp; fiore@3: import uk.ac.qmul.eecs.ccmi.network.Command; fiore@3: import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; fiore@3: import uk.ac.qmul.eecs.ccmi.sound.SoundEvent; fiore@0: import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; fiore@0: import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; fiore@0: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; fiore@0: fiore@0: /** fiore@0: * fiore@0: * An instance of HapticListener for the diagram editor. By this class visual diagrams fiore@0: * can be manipulated by an haptic device. fiore@0: * fiore@0: */ fiore@0: public class HapticKindle extends HapticListener { fiore@0: fiore@0: public HapticKindle(){ fiore@0: super(); fiore@0: unselectRunnable = new Runnable(){ fiore@0: @Override fiore@0: public void run(){ fiore@3: EditorFrame frame = DiagramEditorApp.getFrame(); fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@0: frame.selectHapticHighligh(null); fiore@0: } fiore@0: }; fiore@0: } fiore@0: fiore@3: /** fiore@3: * Implementation of the {@code executeCommand} method. All the commands that involve the fiore@3: * diagram model, are executed in the Event Dispatching Thread through {@code SwingUtilities} invoke fiore@3: * methods. This prevents race conditions on the model and on diagram elements. fiore@3: * fiore@3: * @see HapticListener#executeCommand(HapticListenerCommand, int, double, double, double, double) fiore@3: */ fiore@0: @Override fiore@3: public void executeCommand(HapticListenerCommand cmd, final int ID, final double x, final double y, final double startX, final double startY) { fiore@3: final EditorFrame frame = DiagramEditorApp.getFrame(); fiore@0: switch(cmd){ fiore@3: case PLAY_ELEMENT_SOUND : fiore@3: SwingUtilities.invokeLater(new Runnable(){ fiore@3: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: /* can be null if the tab has been switched or closed in the meantime */ fiore@3: if(de == null) fiore@3: return; fiore@3: SoundFactory.getInstance().play(de.getSound()); fiore@3: } fiore@3: }); fiore@0: break; fiore@0: case PLAY_ELEMENT_SPEECH : fiore@3: SwingUtilities.invokeLater(new Runnable(){ fiore@3: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: if(de == null) fiore@3: return; fiore@3: SoundFactory.getInstance().play(de.getSound()); fiore@3: NarratorFactory.getInstance().speak(de.getName()); fiore@3: iLog("touch",((de instanceof Node) ? "node " : "edge ")+de.getName()); fiore@3: } fiore@3: }); fiore@0: break; fiore@0: case SELECT : fiore@0: SwingUtilities.invokeLater(new Runnable(){ fiore@0: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: if(de == null) fiore@3: return; fiore@3: frame.selectHapticHighligh(de); fiore@0: } fiore@0: }); fiore@0: break; fiore@0: case UNSELECT : fiore@0: SwingUtilities.invokeLater(unselectRunnable); fiore@0: break; fiore@3: case MOVE : { fiore@3: /* when this block is executed we already have the lock * fiore@3: * on the element from the PICK_UP command execution */ fiore@0: try { fiore@0: SwingUtilities.invokeAndWait(new Runnable(){ fiore@0: @Override fiore@0: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: if(de == null) fiore@3: return; fiore@3: DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater(); fiore@3: if(de instanceof Node){ fiore@3: Node n = (Node)de; fiore@0: Rectangle2D bounds = n.getBounds(); fiore@0: Point2D.Double p = new Point2D.Double(bounds.getCenterX(),bounds.getCenterY()); fiore@0: double dx = x - p.getX(); fiore@0: double dy = y - p.getY(); fiore@3: n.getMonitor().lock(); fiore@3: modelUpdater.translate(n, p, dx, dy,DiagramEventSource.HAPT); fiore@3: modelUpdater.stopMove(n,DiagramEventSource.HAPT); fiore@3: n.getMonitor().unlock(); fiore@0: fiore@0: StringBuilder builder = new StringBuilder(); fiore@0: builder.append(DiagramElement.toLogString(n)).append(" ").append(p.getX()) fiore@0: .append(' ').append(p.getY()); fiore@0: iLog("move node start",builder.toString()); fiore@0: builder = new StringBuilder(); fiore@0: builder.append(DiagramElement.toLogString(n)).append(' ') fiore@0: .append(x).append(' ').append(y); fiore@0: iLog("move node end",builder.toString()); fiore@3: }else{ fiore@3: Edge e = (Edge)de; fiore@3: modelUpdater.startMove(e, new Point2D.Double(startX,startY),DiagramEventSource.HAPT); fiore@3: Point2D p = new Point2D.Double(x,y); fiore@3: e.getMonitor().lock(); fiore@3: modelUpdater.bend(e, p,DiagramEventSource.HAPT); fiore@3: modelUpdater.stopMove(e,DiagramEventSource.HAPT); fiore@3: e.getMonitor().unlock(); fiore@0: fiore@0: StringBuilder builder = new StringBuilder(); fiore@0: builder.append(DiagramElement.toLogString(e)).append(' ').append(startX) fiore@0: .append(' ').append(startY); fiore@0: iLog("bend edge start",builder.toString()); fiore@0: builder = new StringBuilder(); fiore@0: builder.append(DiagramElement.toLogString(e)).append(' ') fiore@0: .append(x).append(' ').append(y); fiore@0: iLog("bend edge end",builder.toString()); fiore@0: } fiore@3: modelUpdater.yieldLock(de, fiore@3: Lock.MOVE, fiore@3: new DiagramEventActionSource( fiore@3: DiagramEventSource.HAPT, fiore@3: de instanceof Node ? Command.Name.STOP_NODE_MOVE : Command.Name.STOP_EDGE_MOVE, fiore@3: de.getId(), fiore@3: de.getName() fiore@3: )); fiore@3: } // run() fiore@0: }); fiore@0: } catch (Exception e) { fiore@0: throw new RuntimeException(e); fiore@0: } fiore@0: SoundFactory.getInstance().play(SoundEvent.HOOK_OFF); fiore@3: } fiore@0: break; fiore@3: case INFO : fiore@3: SwingUtilities.invokeLater(new Runnable(){ fiore@3: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: if(de == null) fiore@3: return; fiore@3: SoundFactory.getInstance().stop(); fiore@3: NarratorFactory.getInstance().speak(de.detailedSpokenText()); fiore@3: iLog("request detailed info",((de instanceof Node) ? "node " : "edge ")+de.getName()); fiore@3: } fiore@3: }); fiore@0: break; fiore@0: case PLAY_SOUND : fiore@0: switch(HapticListenerCommand.Sound.fromInt(ID) ){ fiore@0: case MAGNET_OFF : fiore@0: SoundFactory.getInstance().play(SoundEvent.MAGNET_OFF); fiore@0: iLog("sticky mode off",""); fiore@0: break; fiore@0: case MAGNET_ON : fiore@0: SoundFactory.getInstance().play(SoundEvent.MAGNET_ON); fiore@0: iLog("sticky mode on",""); fiore@0: break; fiore@0: case DRAG : SoundFactory.getInstance().play(SoundEvent.DRAG); fiore@0: break; fiore@0: } fiore@0: break; fiore@3: case PICK_UP : fiore@3: try { fiore@3: SwingUtilities.invokeAndWait(new Runnable (){ fiore@3: @Override fiore@3: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: CollectionModel collectionModel = frame.getActiveTab().getDiagram().getCollectionModel(); fiore@3: DiagramElement de = Finder.findElementByHashcode(ID, collectionModel.getElements()); fiore@3: if(de == null) fiore@3: return; fiore@3: DiagramModelUpdater modelUpdater = frame.getActiveTab().getDiagram().getModelUpdater(); fiore@3: if(!modelUpdater.getLock(de, fiore@3: Lock.MOVE, fiore@3: new DiagramEventActionSource(DiagramEventSource.HAPT, de instanceof Edge ? Command.Name.TRANSLATE_EDGE : Command.Name.TRANSLATE_NODE ,de.getId(),de.getName()))){ fiore@3: iLog("Could not get lock on element for motion", DiagramElement.toLogString(de)); fiore@3: NarratorFactory.getInstance().speak("Object is being moved by another user"); fiore@3: return; fiore@3: } fiore@3: frame.hPickUp(de); fiore@3: SoundFactory.getInstance().play(SoundEvent.HOOK_ON); fiore@3: iLog("hook on",""); fiore@3: } fiore@3: }); fiore@3: }catch(Exception e){ fiore@3: e.printStackTrace(); fiore@3: throw new RuntimeException(); fiore@3: } fiore@3: break; fiore@0: case ERROR : fiore@0: /* no synchronization necessary as the XMLManager looks after it*/ fiore@3: SwingUtilities.invokeLater(new Runnable(){ fiore@3: @Override fiore@3: public void run(){ fiore@3: if((frame == null)||(frame.getActiveTab() == null)) fiore@3: return; fiore@3: frame.backupOpenDiagrams(); fiore@3: } fiore@3: }); fiore@3: NarratorFactory.getInstance().speak(ResourceBundle.getBundle(EditorFrame.class.getName()).getString("speech.haptic_device_crashed")); fiore@0: break; fiore@0: } fiore@0: } fiore@0: fiore@0: private void iLog(String action, String args){ fiore@0: InteractionLog.log(INTERACTION_LOG_SOURCE,action,args); fiore@0: } fiore@0: fiore@0: private Runnable unselectRunnable; fiore@0: private static String INTERACTION_LOG_SOURCE = "HAPTIC"; fiore@3: //private EditorFrame frame; fiore@0: }