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