Mercurial > hg > ccmieditor
changeset 5:d66dd5880081
Added support for Falcon Haptic device and Tablet/Mouse as haptic device
line wrap: on
line diff
--- a/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTree.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/checkboxtree/CheckBoxTree.java Tue Jul 10 22:39:37 2012 +0100 @@ -114,7 +114,7 @@ /** * Returns a reference to the properties holding which nodes of the tree are currently checked. - * @return the properties or {@value null} if the constructor with no properties was used. + * @return the properties or {@code null} if the constructor with no properties was used. */ public SetProperties getProperties(){ return properties;
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/CollectionListener.java Tue Jul 10 22:39:37 2012 +0100 @@ -57,7 +57,7 @@ * a {@code PropertyChangeArgs} object to retrieve the property from the node. {@code getOldValue()} will return in this * case a String concatenation of all the modifier that were set before the change for this property. * <li>{@code arrowHead} : when the arrow head of an edge is changed. - * <li>{@code endLabel} : + * <li>{@code endLabel} : when the label of an edge is changed. * </ul> * @param e an object representing the change event */
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramEdge.java Tue Jul 10 22:39:37 2012 +0100 @@ -68,6 +68,7 @@ * * @param n the node,at whose end the label is located * @param label the label + * @param source the source of the action that triggered this method */ public void setEndLabel(DiagramNode n, String label, Object source){ if(label == null) @@ -80,7 +81,9 @@ * Returns the end label related to a node. On a graphical representation of the diagram * the label would be put in proximity of the node. * - * @param n the node,at whose end the label is located + * @param n the node, at whose end the label is located + * + * @return the label at the specified end */ public String getEndLabel(DiagramNode n){ String s = endLabels.get(n); @@ -107,6 +110,7 @@ * edge end description will be set with the string at that position in the array. * if index is equal to NO_END_DESCRIPTION_INDEX, then the description will be set * as the empty string. + * @param source the source of the action that triggered this method * */ public void setEndDescription(DiagramNode n, int index, Object source){ @@ -145,6 +149,7 @@ * Removes a node from this edge. On a graphical representation of the diagram * this would mean that the node is no longer connected to the other nodes via this edge. * @param n the node to be removed + * @param source the source of this action * @return true if the inner collection changed as a result of the call */ public abstract boolean removeNode(DiagramNode n, Object source);
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramModel.java Tue Jul 10 22:39:37 2012 +0100 @@ -58,8 +58,10 @@ /** * Create a model instance starting from some nodes and edges prototypes. * All subsequently added element must be clones of such prototypes. - * @param nodePrototypes - * @param edgePrototypes + * @param nodePrototypes an array of {@code DiagramNode} prototypes, from which + * nodes that will be inserted in this model will be cloned + * @param edgePrototypes an array of {@code DiagramEdge} prototypes, from which + * edges that will be inserted in this model will be cloned */ @SuppressWarnings("serial") public DiagramModel(N [] nodePrototypes, E [] edgePrototypes) {
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/DiagramNode.java Tue Jul 10 22:39:37 2012 +0100 @@ -30,6 +30,13 @@ */ @SuppressWarnings("serial") public abstract class DiagramNode extends DiagramElement { + /** + * Constructor to be called by sub classes + * + * @param type the type of the new node. All nodes with this type will be + * put under the same tree node in the tree representation + * @param properties the properties of this node + */ public DiagramNode(String type, NodeProperties properties){ setType(type); this.properties = properties; @@ -68,6 +75,7 @@ /** * Set the NodeProperties of this node * @param properties the properties to set for this node + * @param source the source of the action that triggered this method */ public void setProperties(NodeProperties properties, Object source){ this.properties = properties; @@ -78,6 +86,9 @@ * Add a property to the NodeProperties of this node * @see NodeProperties#addValue(String, String) * + * @param propertyType the type of the property to add + * @param propertyValue the property to add + * @param source the source of the action that triggered this method */ public void addProperty(String propertyType, String propertyValue, Object source){ getProperties().addValue(propertyType, propertyValue); @@ -88,6 +99,10 @@ /** * Removes a property from the NodeProperties of this node * @see NodeProperties#removeValue(String, int) + * + * @param propertyType the type of the property to add + * @param valueIndex the index of the property to remove + * @param source the source of the action that triggered this method */ public void removeProperty(String propertyType, int valueIndex, Object source){ String oldValue = getProperties().getValues(propertyType).get(valueIndex); @@ -98,6 +113,11 @@ /** * Set a property on the NodeProperties of this node to a new value * @see NodeProperties#setValue(String, int, String) + * + * @param propertyType the type of the property to add + * @param valueIndex the index of the property to remove + * @param newValue the new value for this property + * @param source the source of the action that triggered this method */ public void setProperty(String propertyType, int valueIndex, String newValue, Object source){ String oldValue = getProperties().getValues(propertyType).get(valueIndex); @@ -108,6 +128,8 @@ /** * Removes all the values in the NodeProperties of this node * @see NodeProperties#clear() + * + * @param source the source of the action that triggered this method */ public void clearProperties(Object source){ getProperties().clear(); @@ -117,6 +139,13 @@ /** * set the modifier indexes in the NodeProperties of this node * @see Modifiers#setIndexes(int, Set) + * + * @param propertyType the type of the property to add + * @param propertyValueIndex the index of the property value whose modifiers + * are to be changed + * @param modifierIndexes the new modifiers (identified by their index ) + * for this property value + * @param source the source of the action that triggered this method */ public void setModifierIndexes(String propertyType, int propertyValueIndex, Set<Integer> modifierIndexes,Object source){ StringBuilder oldIndexes = new StringBuilder(); @@ -171,12 +200,16 @@ /** * add an edge to the attached edges list * @param e the edge to be added + * + * @return {@code true} if internal collection of edges changed as a result of the call */ public abstract boolean addEdge(DiagramEdge e); /** * Removes an edge from the attached edges list - * @param e the edge to be removed + * @param e the edge to be removed + * + * @return {@code true} if internal collection of edges changed as a result of the call */ public abstract boolean removeEdge(DiagramEdge e);
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/ElementChangedEvent.java Tue Jul 10 22:39:37 2012 +0100 @@ -32,7 +32,7 @@ * Creates a new instance of {@code ElementChangedEvent} * * @param element the element that has been changed - * @param args + * @param args the arguments of this change event, if any * @param changeType it's a {@code String} identifying the change type. Subclasses of {@code DiagramNode} and * {@code DiagramEdge} can define their own change events and and make listeners aware of them via their * {@code notifyChnage()} method. Listeners (defined outside this package as well) can then identify such changes using this string.
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/MalformedEdgeException.java Tue Jul 10 22:39:37 2012 +0100 @@ -45,10 +45,10 @@ public class MalformedEdgeException extends RuntimeException { /** - * Creates a new {@code MalformedEdgeException} holding the messsage passed as argument. + * Creates a new {@code MalformedEdgeException} holding the message passed as argument. * The message can be accessed by calling {@code getMessage()} on this exception. * - * @param message + * @param message the message of this Exception */ public MalformedEdgeException(String message) { super("Edge inserted into data structure was malformed for this reason:" + message);
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/NodeProperties.java Tue Jul 10 22:39:37 2012 +0100 @@ -269,7 +269,7 @@ /** * Returns true if there are no values for the specified type in this NodeProperties instance. * - * @param propertyType + * @param propertyType the property type to be checked for value presence * @return true if there are no values for the specified type in this NodeProperties instance */ public boolean typeIsEmpty(String propertyType){ @@ -446,7 +446,7 @@ * its own view, which will then be used by the client itself to visualise the modifier * * @param modifierType the type of modifier the view is associated with. - * @param o an object defined by user + * @param view an object defined by user defining how this property looks like * @see #getView(String) * @throws IllegalArgumentException if modifierType * is not among the ones in the type definition passed as argument to the constructor @@ -463,8 +463,9 @@ * integer set returned by this method tells the client code to which modifiers the property value at the index * passed as argument is assigned. The returned indexes refer to the modifier list returned by {@link #getTypes()} * - * @param propertyValueIndex - * @return a set of indexes of the modifier types the property value at the index passed as agument + * @param propertyValueIndex the index of the property value for which the set of modifier indexes + * is being queried + * @return a set of indexes of the modifier types the property value at the index passed as argument * is assigned to */ public Set<Integer> getIndexes(int propertyValueIndex){
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TreeModel.java Tue Jul 10 22:39:37 2012 +0100 @@ -69,6 +69,7 @@ * * @param bookmark a bookmark * @param treeNode the tree node to be bookmarked + * @param source the sorce of the action that triggered this method * @return previous value associated with specified key, or null if there was no mapping for key. * @throws IllegalArgumentException if bookmark is null */ @@ -91,6 +92,7 @@ * Remove the bookmark from the bookmark internal collection * * @param bookmark the bookmark to remove + * @param source the source of the action that triggered this method * @return previous value associated with specified key, or null if there was no mapping for key. */ DiagramTreeNode removeBookmark(String bookmark, Object source);
--- a/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/diagrammodel/TypeMutableTreeNode.java Tue Jul 10 22:39:37 2012 +0100 @@ -34,7 +34,7 @@ /** * Returns a prototype diagram element which can be cloned to create other diagram elements * of this type. - * @return + * @return the prototype diagram element */ public DiagramElement getPrototype(){ return (DiagramElement)prototype.clone();
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java Tue Jul 10 22:39:37 2012 +0100 @@ -248,7 +248,7 @@ * @param action the action to log. * @param args additional arguments to add to the log. * - * @see uk.ac.qmul.eecs.ccmi.utils#InteractionLog + * @see uk.ac.qmul.eecs.ccmi.utils.InteractionLog */ protected void iLog(String action, String args) { InteractionLog.log("GRAPH", action, args);
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramModelUpdater.java Tue Jul 10 22:39:37 2012 +0100 @@ -254,7 +254,7 @@ * model updater. * * @param ge the graph element being moved - * @param sourcethe source of the {@code stopMove} action. it can be used by collection listeners. + * @param source the source of the {@code stopMove} action. it can be used by collection listeners. */ public void stopMove(GraphElement ge,DiagramEventSource source); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramPanel.java Tue Jul 10 22:39:37 2012 +0100 @@ -202,12 +202,12 @@ } /** - * Sets the backing diagram for this panel. This method is used when a diagram is shared + * Sets the backing up delegate diagram for this panel. This method is used when a diagram is shared * (or reverted). A shared diagram has a different way of updating the * The modified status is changed according to * the modified status of the {@code DiagramModel} internal to the new {@code Diagram} * - * @param diagram + * @param diagram the backing up delegate diagram */ public void setDiagram(Diagram diagram){ /* remove the listener from the old model */ @@ -267,7 +267,7 @@ * to the {@code TreeModel} or {@code CollectionModel} it contains. To change the {@code modified} * status of the diagram (and of its models) {@code setModified()} must be used. * - * @return + * @return {@code true} if the diagram is modified, {@code false} otherwise */ public boolean isModified(){ return diagram.getCollectionModel().isModified();
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/DiagramTree.java Tue Jul 10 22:39:37 2012 +0100 @@ -98,7 +98,7 @@ /** * @see javax.swing.JTree#setModel(javax.swing.tree.TreeModel) * - * @param newModel the new mnodel for this tree + * @param newModel the new model for this tree */ public void setModel(TreeModel<Node,Edge> newModel){ DiagramTreeNode selectedTreeNode = (DiagramTreeNode)getSelectionPath().getLastPathComponent(); @@ -392,7 +392,6 @@ * Allows only cursor keys, tab key, delete, and actions (CTRL+something) * * @param e a key event - * */ @Override protected void processKeyEvent(KeyEvent e){
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Direction.java Tue Jul 10 22:39:37 2012 +0100 @@ -72,6 +72,8 @@ /** Turns this direction by an angle. @param angle the angle in degrees + + @return a new object representing the turned direction */ public Direction turn(double angle){ double a = Math.toRadians(angle); @@ -84,8 +86,7 @@ Gets the x-component of this direction @return the x-component (between -1 and 1) */ - public double getX() - { + public double getX() { return x; } @@ -93,8 +94,7 @@ Gets the y-component of this direction @return the y-component (between -1 and 1) */ - public double getY() - { + public double getY() { return y; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Edge.java Tue Jul 10 22:39:37 2012 +0100 @@ -223,11 +223,11 @@ @Override public boolean contains(Point2D aPoint){ if(points.isEmpty()){ - return fatStrokeContains (getSegment(nodes.get(0), nodes.get(1)), aPoint); + return fatStrokeContains (nodes.get(0), nodes.get(1), aPoint); } for(InnerPoint p : points){ for(GraphElement ge : p.neighbours){ - if(fatStrokeContains(getSegment(p,ge),aPoint)) + if(fatStrokeContains(p,ge,aPoint)) return true; } } @@ -284,9 +284,14 @@ } /* checks if a point belongs to a shape with a margin of MAX_DIST*/ - private boolean fatStrokeContains(Shape s, Point2D p){ + private boolean fatStrokeContains(GraphElement ge1, GraphElement ge2, Point2D p){ BasicStroke fatStroke = new BasicStroke((float) (2 * MAX_DIST)); - Shape fatPath = fatStroke.createStrokedShape(s); + Line2D line = new Line2D.Double( + ge1.getBounds().getCenterX(), + ge1.getBounds().getCenterY(), + ge2.getBounds().getCenterX(), + ge2.getBounds().getCenterY()); + Shape fatPath = fatStroke.createStrokedShape(line); return fatPath.contains(p); } @@ -346,8 +351,8 @@ * inner point is created and the line is broken into two sub lines. If the location is an * already existing inner point, then the point is translated. * - * @param p - * @param source + * @param p the starting point of the bending + * @param source the source of the bending action */ public void bend(Point2D p,Object source) { boolean found = false; @@ -356,29 +361,47 @@ points.add(newInnerPoint); newPointCreated = false; }else if(newPointCreated){ - /* find the segment where the new point lays */ - for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext() && !found; ){ + /* find the segment closest to where the new point lays */ + InnerPoint closestP1 = null; + GraphElement closestP2 = null; + double minDist = 0; + for(ListIterator<InnerPoint> pItr = points.listIterator(); pItr.hasNext(); ){ InnerPoint ePoint = pItr.next(); for(ListIterator<GraphElement> geItr = ePoint.neighbours.listIterator(); geItr.hasNext() && !found;){ /* find the neighbour of the current edge point whose line the new point lays on */ - GraphElement ge = geItr.next(); - if(fatStrokeContains(getSegment(ePoint, ge),downPoint)){ - if(ge instanceof InnerPoint ){ - /* remove current edge point from the neighbour's neighbours */ - ((InnerPoint)ge).neighbours.remove(ePoint); - ((InnerPoint)ge).neighbours.add(newInnerPoint); - } - /*remove old neighbour from edgePoint neighbours */ - geItr.remove(); - newInnerPoint.neighbours.add(ePoint); - newInnerPoint.neighbours.add(ge); - /* add the new node to the list of EdgeNodes of this edge */ - pItr.add(newInnerPoint); - geItr.add(newInnerPoint); - found = true; + GraphElement next = geItr.next(); + double dist = Line2D.ptSegDist( + ePoint.getBounds().getCenterX(), + ePoint.getBounds().getCenterY(), + next.getBounds().getCenterX(), + next.getBounds().getCenterY(), + downPoint.getX(), + downPoint.getY() + ); + + if(closestP1 == null || dist < minDist){ + closestP1 = ePoint; + closestP2 = next; + minDist = dist; + continue; } } } + + if(closestP2 instanceof InnerPoint ){ + /* remove current edge point from the neighbour's neighbours */ + ((InnerPoint)closestP2).neighbours.remove(closestP1); + /* add the new inner point */ + ((InnerPoint)closestP2).neighbours.add(newInnerPoint); + } + /*remove old neighbour from edge inner point neighbours */ + closestP1.neighbours.remove(closestP2); + newInnerPoint.neighbours.add(closestP1); + newInnerPoint.neighbours.add(closestP2); + /* add the new node to the list of EdgeNodes of this edge */ + points.add(newInnerPoint); + closestP1.neighbours.add(newInnerPoint); + found = true; newPointCreated = false; } newInnerPoint.translate(p, p.getX() - newInnerPoint.getBounds().getCenterX(), @@ -663,7 +686,7 @@ private LineStyle style; - private static final double MAX_DIST = 3; + private static final double MAX_DIST = 5; private static final Color POINT_COLOR = Color.GRAY; /** * The end description for an end that has no hand description set by the user @@ -754,7 +777,7 @@ private Rectangle2D bounds; private List<GraphElement> neighbours; - private static final int DIM = 5; + private static final int DIM = 7; } /**
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java Tue Jul 10 22:39:37 2012 +0100 @@ -43,6 +43,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; +import java.net.URL; import java.nio.channels.SocketChannel; import java.text.MessageFormat; import java.text.SimpleDateFormat; @@ -56,6 +57,7 @@ import java.util.regex.Pattern; import javax.imageio.ImageIO; +import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFrame; import javax.swing.JMenu; @@ -106,7 +108,6 @@ import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler; import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; -import uk.ac.qmul.eecs.ccmi.utils.Validator; /** * The main frame of the editor which contains diagram panes that show graphs and @@ -121,7 +122,7 @@ * @param templateFiles an array of template files. New diagrams can be created from template files by clonation * @param backupDirPath the path of a folder where all the currently open diagrams will be saved if * the haptic device crashes - * @param templateEditors + * @param templateEditors the template editors for this instance of the program */ public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors){ this.backupDirPath = backupDirPath; @@ -135,7 +136,8 @@ /* read editor related preferences */ preferences = PreferencesService.getInstance(); - setIconImage(new ResourceFactory.ImageFactory().getImage("ccmi_favicon.gif")); + URL url = getClass().getResource("ccmi_favicon.gif"); + setIconImage(new ImageIcon(url).getImage()); changeLookAndFeel(preferences.get("laf", null)); recentFiles = new ArrayList<String>(); @@ -625,6 +627,41 @@ JMenu preferencesMenu = factory.createMenu("preferences"); menuBar.add(preferencesMenu); + /* show haptic window menu item only unless it's a actual haptic device thread * + * that has its own window run by a native dll */ + if(!HapticsFactory.getInstance().isAlive()){ + preferencesMenu.add(factory.createCheckBoxMenuItem("preferences.show_haptics", new ActionListener(){ + @Override + public void actionPerformed(ActionEvent evt){ + JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); + haptics.setVisible(menuItem.isSelected()); + NarratorFactory.getInstance().speakWholeText(resources.getString( + menuItem.isSelected() ? "speech.haptic_window_open" : "speech.haptic_window_close")); + } + })); + } + + preferencesMenu.add(factory.createMenuItem("preferences.change_haptics", new ActionListener(){ + @Override + public void actionPerformed(ActionEvent evt){ + String [] hapticDevices = { + HapticsFactory.PHANTOM_ID, + HapticsFactory.FALCON_ID, + HapticsFactory.TABLET_ID}; + String selection = (String)SpeechOptionPane.showSelectionDialog( + EditorFrame.this, + resources.getString("dialog.input.haptics.select"), + hapticDevices, + hapticDevices[0]); + if(selection == null) + return; + preferences.put("haptic_device", selection); + SpeechOptionPane.showMessageDialog(EditorFrame.this, + MessageFormat.format(resources.getString("dialog.feedback.haptic_init"),selection), + SpeechOptionPane.WARNING_MESSAGE); + } + })); + // awareness menu JMenu awarenessMenu = factory.createMenu("preferences.awareness"); preferencesMenu.add(awarenessMenu); @@ -633,17 +670,18 @@ awarenessMenu.add(factory.createMenuItem("preferences.awareness.broadcast", this, "showAwarenessBroadcastDialog")); awarenessMenu.add(factory.createMenuItem("preferences.awareness.display", this, "showAwarenessDisplayDialog")); - JMenuItem enableAwarenessVoiceMenuItem = factory.createCheckBoxMenuItem("preferences.awareness.enable_voice",new ActionListener(){ - @Override - public void actionPerformed(ActionEvent evt) { - JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); - NarratorFactory.getInstance().setMuted(!menuItem.isSelected(),Narrator.SECOND_VOICE); - PreferencesService.getInstance().put("second_voice_enabled", Boolean.toString(menuItem.isSelected())); - } - }); + JMenuItem enableAwarenessVoiceMenuItem = factory.createCheckBoxMenuItem("preferences.awareness.enable_voice", + new ActionListener(){ + @Override + public void actionPerformed(ActionEvent evt) { + JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); + NarratorFactory.getInstance().setMuted(!menuItem.isSelected(),Narrator.SECOND_VOICE); + PreferencesService.getInstance().put("second_voice_enabled", Boolean.toString(menuItem.isSelected())); + } + }); NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE); - enableAwarenessVoiceMenuItem.setSelected( - Boolean.parseBoolean(PreferencesService.getInstance().get("second_voice_enabled", Boolean.toString(true)))); + enableAwarenessVoiceMenuItem.setSelected(Boolean.parseBoolean( + PreferencesService.getInstance().get("second_voice_enabled", Boolean.toString(true)))); awarenessMenu.add(enableAwarenessVoiceMenuItem); NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE); @@ -701,12 +739,32 @@ PreferencesService.getInstance().put("use_accessible_filechooser", Boolean.toString(menuItem.isSelected())); } }); + + JMenuItem enableLogMenuItem = factory.createCheckBoxMenuItem("preferences.enable_log", new ActionListener(){ + @Override + public void actionPerformed(ActionEvent evt) { + JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); + PreferencesService preferences = PreferencesService.getInstance(); + preferences.put("enable_log", Boolean.toString(menuItem.isSelected())); + if(menuItem.isSelected()){ + try{ + InteractionLog.enable(preferences.get("dir.log", "")); + }catch(IOException ioe){ + SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.log_enable")); + } + }else{ + InteractionLog.disable(); + } + } + }); - // temporarily mute the narrator to select the menu without triggering a speech + /* temporarily mute the narrator to select the menus without triggering a speech */ NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE); fileChooserMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true"))); + enableLogMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("enable_log", "false"))); NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE); preferencesMenu.add(fileChooserMenuItem); + preferencesMenu.add(enableLogMenuItem); /* --- HELP --- */ @@ -725,7 +783,7 @@ private void changeLookAndFeel(String lafName){ if(lafName == null) - lafName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; + lafName = UIManager.getSystemLookAndFeelClassName(); try{ UIManager.setLookAndFeel(lafName); SwingUtilities.updateComponentTreeUI(EditorFrame.this); @@ -835,6 +893,10 @@ } } + /** + * Close a diagram tab. If the diagram has not been saved the user will be + * asked if they want to save the diagram. + */ public void closeFile(){ DiagramPanel dPanel = getActiveTab(); if(dPanel.isModified()||dPanel.getFilePath() == null){ @@ -862,6 +924,12 @@ haptics.removeDiagram(dPanel.getDiagram().getName(), newFocusedTabName); } + /** + * Saves the currently open tab diagram into a file. If the diagram has no file path associated + * (it has never been saved before), {@link #saveFileAs()} is called. + * + * @return {@code true} if the file is successfully saved, or {@code false} otherwise. + */ public boolean saveFile(){ DiagramPanel diagramPanel = getActiveTab(); if (diagramPanel == null) // no tabs open @@ -892,7 +960,10 @@ } /** - Saves the current graph as a new file. + * Saves the current diagram as a new file. The user is prompter with a {@code FileChooser} + * to chose a file to save the diagram to. + * + * @return {@code true} if the file is successfully saved, or {@code false} otherwise. */ public boolean saveFileAs() { DiagramPanel diagramPanel = getActiveTab(); @@ -983,6 +1054,12 @@ System.exit(0); } + /** + * Changes the selection path of the diagram tree to a specific destination. + * The user with a selection dialog to choose the destination from the following + * choices : the root of the diagram, one of the element types, a bookmarked + * node or a node/edge reference. + */ public void jump(){ String[] options = new String[canJumpRef ? 4 : 3]; options[0] = resources.getString("options.jump.type"); @@ -1015,6 +1092,10 @@ } } + /** + * Locates on the haptic device the node or edge currently selected on the diagram tree. A command + * is sent to the haptic device which in turns drag the user to the node/edge location. + */ public void locate(){ DiagramPanel dPanel = getActiveTab(); DiagramTree tree = dPanel.getTree(); @@ -1023,15 +1104,30 @@ iLog("locate " +((de instanceof Node)? "node" : "edge"),DiagramElement.toLogString(de)); } + /** + * Selects on the diagram tree the node or edge that's currently being touched by the haptic + * device. + */ public void hHighlight() { getActiveTab().getTree().jumpTo(hapticHighlightDiagramElement); iLog("highlight " +((hapticHighlightDiagramElement instanceof Node)? "node" : "edge"),DiagramElement.toLogString(hapticHighlightDiagramElement)); } + /** + * Sends a command to the haptic device to pick up an element (node or edge) for + * moving it. + * + * @param de the diagram element to be picked up + */ public void hPickUp(DiagramElement de) { HapticsFactory.getInstance().pickUp(System.identityHashCode(de)); } + /** + * Prompts the user for an object insertion. The object can be a node, an edge, a property, + * a modifier, an edge label, an edge arrow head. Which object is to be inserted depends on + * the currently selected tree node on the tree. + */ public void insert(){ DiagramPanel dPanel = getActiveTab(); final DiagramTree tree = dPanel.getTree(); @@ -1079,7 +1175,8 @@ iLog("open insert property dialog",""); final String propertyValue = SpeechOptionPane.showInputDialog(EditorFrame.this, - MessageFormat.format(resources.getString("dialog.input.property.text"),propTypeNode.getName()) + MessageFormat.format(resources.getString("dialog.input.property.text"), propTypeNode.getName()), + "" ); if(propertyValue != null){ if(!propertyValue.isEmpty()){ //check that the user didn't enter an empty string @@ -1112,7 +1209,8 @@ int index = typeNode.getIndex(treeNode); Set<Integer> result = SpeechOptionPane.showModifiersDialog(EditorFrame.this, - MessageFormat.format(resources.getString("dialog.input.check_modifiers"),n.getProperties().getValues(typeNode.getType()).get(index)) , + MessageFormat.format(resources.getString("dialog.input.check_modifiers"), + n.getProperties().getValues(typeNode.getType()).get(index)) , modifiers.getTypes(), modifiers.getIndexes(index) ); @@ -1151,7 +1249,8 @@ if(choice == null){ iLog("cancel edge operation selection dialog",""); SoundFactory.getInstance().play(SoundEvent.CANCEL); - modelUpdater.yieldLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName())); + modelUpdater.yieldLock(e, Lock.EDGE_END, + new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName())); return; } if(choice.equals(operations[0])){ //operations[0] = edit edge end-label @@ -1198,10 +1297,16 @@ SoundFactory.getInstance().play(SoundEvent.CANCEL); } } - modelUpdater.yieldLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName())); + modelUpdater.yieldLock(e, Lock.EDGE_END, + new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName())); } } + /** + * Prompts the user for an object deletion. The object can be a node, an edge, a property, + * a modifier, an edge label, an edge arrow head. Which object is to be deleted depends on + * the currently selected tree node on the tree. + */ public void delete(){ DiagramPanel dPanel = getActiveTab(); final DiagramTree tree = dPanel.getTree(); @@ -1274,6 +1379,10 @@ throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName()); } + /** + * Prompts the user for an object deletion. The object can be a node, an edge or a property. + * Which object is to be renamed depends on the currently selected tree node on the tree. + */ public void rename(){ DiagramPanel dPanel = getActiveTab(); DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater(); @@ -1339,6 +1448,10 @@ throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName()); } + /** + * Prompts the user with a dialog to add or remove bookmarks on a tree node. Which node is to be + * (un)bookmarked depends on the currently selected tree node on the tree. + */ public void editBookmarks(){ boolean addBookmark = true; DiagramPanel dPanel = getActiveTab(); @@ -1383,7 +1496,7 @@ boolean uniqueBookmarkChosen = false; while(!uniqueBookmarkChosen){ iLog("open add bookmark dialog",""); - String bookmark = SpeechOptionPane.showInputDialog(EditorFrame.this, resources.getString("dialog.input.bookmark.text")); + String bookmark = SpeechOptionPane.showInputDialog(EditorFrame.this, resources.getString("dialog.input.bookmark.text"),""); if(bookmark != null){ if("".equals(bookmark)){ iLog("error: entered empty bookmark",""); @@ -1425,6 +1538,10 @@ modelUpdater.yieldLock(treeNode, Lock.BOOKMARK, DiagramEventActionSource.NULL); } + /** + * Prompts the user with a dialog edit notes on a tree node. Which node is to be + * noted depends on the currently selected tree node on the tree. + */ public void editNotes(){ DiagramPanel dPanel = getActiveTab(); DiagramTreeNode treeNode = (DiagramTreeNode)dPanel.getTree().getLastSelectedPathComponent(); @@ -1457,6 +1574,9 @@ modelUpdater.yieldLock(treeNode, Lock.NOTES,DiagramEventActionSource.NULL); } + /** + * Starts the server on a background thread. + */ public void startServer(){ iLog("server started",""); /* If the awareness filter has not been created yet (by opening the awareness filter dialog) then create it */ @@ -1485,6 +1605,9 @@ shareDiagramMenuItem.setEnabled(true); } + /** + * Stops the running server + */ public void stopServer(){ /* those network diagrams which are connected to the local server are reverted, * * that is the diagram panel is set with the delegate diagram of the network diagram */ @@ -1512,6 +1635,10 @@ iLog("server stopped",""); } + /** + * Makes a diagram shared on the server. When a diagram is shared, remote users can connect to the + * server and edit it collaboratively with the local and the other connected users. + */ public void shareDiagram(){ try{ if(server == null) @@ -1544,14 +1671,12 @@ } } - public void unshareDiagram(){ - DiagramPanel dPanel = getActiveTab(); - if(dPanel.getDiagram() instanceof NetDiagram){ - if(clientConnectionManager != null && clientConnectionManager.isAlive()) - clientConnectionManager.addRequest(new RmDiagramRequest(dPanel.getDiagram().getName())); - } - } - + /** + * Prompts the user for a server address and connect to the server. The server is queried for the list + * of the shared diagrams and the user is prompted with a selection dialog to chose a diagram to download. + * The diagram is then downloaded and loaded into the diagram editor, ready for shared editing. + * + */ public void openSharedDiagram(){ iLog("open open share diagram dialog",""); /* open the window prompting for the server address and make checks on the user input */ @@ -1563,7 +1688,7 @@ SoundFactory.getInstance().play(SoundEvent.CANCEL); iLog("cancel open share diagram dialog",""); return; - }else if(!Validator.validateIPAddr(addr)){ + }else if(!ProtocolFactory.validateIPAddr(addr)){ iLog("error:invalid IP address",addr); SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.share_diagram.wrong_ip")); return; @@ -1664,6 +1789,10 @@ } } + /** + * Shows the awareness panel, a text pane where all the awareness informations received by the server + * are displayed. + */ public void showAwarenessPanel(){ DiagramPanel dPanel = getActiveTab(); if(dPanel != null){ @@ -1672,6 +1801,10 @@ } } + /** + * Hides the awareness panel, a text pane where all the awareness informations received by the server + * are displayed. + */ public void hideAwarenessPanel(){ DiagramPanel dPanel = getActiveTab(); if(dPanel != null){ @@ -1681,6 +1814,10 @@ } } + /** + * Saves all the open diagram which have been modified since the last time they were saved into the + * <i>backup</i> folder in the ccmi_editor_data directory. + */ public void backupOpenDiagrams(){ SimpleDateFormat dateFormat = new SimpleDateFormat("EEE_d_MMM_yyyy_HH_mm_ss"); String date = dateFormat.format(new Date()); @@ -1754,6 +1891,8 @@ @param graph the graph @param out the output stream @param format the image file format + + @throws IOException if something goes wrong during the I/O operations */ public static void saveImage(GraphPanel graph, OutputStream out, String format) throws IOException { @@ -1780,6 +1919,12 @@ ImageIO.write(image, format, out); } + /** + * Shows the configuration dialog of the broadcast filter. The broadcast filter affects + * which awareness informations are broadcasted from the server to the other clients. + * If the local editor is not running the server, changes to the broadcast filter + * will have no effect. + */ public void showAwarenessBroadcastDialog(){ BroadcastFilter filter = BroadcastFilter.getInstance(); if(filter == null) @@ -1791,6 +1936,11 @@ filter.showDialog(this); } + /** + * Shows the configuration dialog of the display filter. The display filter affects + * which awareness informations are received from the server are actually displayed + * to the user. + */ public void showAwarenessDisplayDialog(){ DisplayFilter filter = DisplayFilter.getInstance(); if(filter == null) @@ -1802,6 +1952,10 @@ filter.showDialog(this); } + /** + * Prompts the user with a dialog to choose he awareness username. The username is used in the + * awareness information to identify which client is doing the actions that are being notified. + */ public void showAwarnessUsernameDialog(){ String oldName = AwarenessMessage.getDefaultUserName(); String newName = SpeechOptionPane.showInputDialog( @@ -1840,10 +1994,16 @@ } } + /** + * Prompts the user with a dialog to choose the port number the local server will listen on. + */ public void showLocalServerPortDialog(){ showServerPortDialog("server.local_port","dialog.input.local_server_port","dialog.feedback.local_server_port"); } + /** + * Prompts the user with a dialog to choose the remote server port number to connect to. + */ public void showRemoteServerPortDialog(){ showServerPortDialog("server.remote_port","dialog.input.remote_server_port","dialog.feedback.remote_server_port"); } @@ -1896,6 +2056,9 @@ ); } + /** + * Displays the Software license in a dialog box. + */ public void showLicense() { BufferedReader reader = null; try{ @@ -1921,6 +2084,15 @@ } } + /** + * Saves the diagram template in the <i>templates</i> folder. + * + * The template can then be reused to create new diagrams with the same type of nodes and + * edges. + * + * @param diagram the diagram to get the template from + * @throws IOException if something goes wrong with I/O when saving the file + */ public void saveDiagramTemplate(Diagram diagram) throws IOException { File file = new File( new StringBuilder(PreferencesService.getInstance().get("home", ".")) @@ -1936,9 +2108,11 @@ } /** - Adds a graph type to the File->New menu. - @param resourceName the name of the menu item resource - @param graphClass the class object for the graph + * Adds a diagram type to the File->New menu. + * + * @param diagram the diagram whose nodes and edges definition will be used as a template + * for new diagrams creation. + * */ public void addDiagramType(final Diagram diagram){ /* this is to prevent the user from creating other diagram prototypes with the same name */ @@ -1982,10 +2156,24 @@ preferences.put("recent", recent); } + /** + * Returns the currently selected tab's diagram panel. + * + * @return the currently selected tab's diagram panel or {@code null} + * if no tab is open. + */ public DiagramPanel getActiveTab(){ return (DiagramPanel)editorTabbedPane.getSelectedComponent(); } + /** + * Set the variable holding the node or edge that would be highlighted if + * {@link #hHighlight()} is called. The menu item for highlight is also enabled + * if {@code de} is not {@code null}. + * + * @param de the diagram element to be selected by {@code hHighlight()} + * or {@code null} for no selection. + */ public void selectHapticHighligh(DiagramElement de){ hapticHighlightDiagramElement = de; highlightMenuItem.setEnabled(de == null ? false : true); @@ -1997,7 +2185,7 @@ diagramPanel.getTree().addTreeSelectionListener(treeSelectionListener); diagramPanel.setAwarenessPanelListener(awarenessPanelListener); /* update the haptics */ - haptics.addNewDiagram(diagramPanel.getDiagram().getName(), true); + haptics.addNewDiagram(diagramPanel.getDiagram().getName()); for(Node n : diagram.getCollectionModel().getNodes()) haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null); for(Edge e : diagram.getCollectionModel().getEdges()){ @@ -2133,7 +2321,7 @@ private ClientConnectionManager clientConnectionManager; private Haptics haptics; private ResourceBundle resources; - private EditorTabbedPane editorTabbedPane; + public EditorTabbedPane editorTabbedPane; private FileService.ChooserService fileService; private PreferencesService preferences; private HapticTrigger hapticTrigger;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.properties Tue Jul 10 22:39:37 2012 +0100 @@ -50,6 +50,7 @@ dialog.error.save_image=Error: could not save the image to file dialog.error.unsupported_image=Error: {0} not supported dialog.error.no_connection_to_server=Could not connect to the server +dialog.error.log_enable=The log could not be enabled dialog.template_created=Diagram {0} created dialog.file_saved=File Saved @@ -87,6 +88,7 @@ dialog.input.awerness_username=Enter Awareness User Name dialog.input.local_server_port=Enter local server port number dialog.input.remote_server_port=Enter remote server port number +dialog.input.haptics.select=Select the haptic device you want to use dialog.confirm.deletion=Are you sure you want to delete the {0} {1} ? dialog.confirm.deletions=Are you sure you want to delete the selected objects ? @@ -124,12 +126,13 @@ dialog.share_diagram.enter_address="Enter server address" dialog.file_chooser.file_type=File Type: -dialog.file_chooser.file_name=file Name: +dialog.file_chooser.file_name=File Name: dialog.feedback.speech_rate=Speech rate set to {0} dialog.feedback.awareness_username=User Name set to {0} dialog.feedback.local_server_port=Local server port number set to {0} dialog.feedback.remote_server_port=Remote server port number set to {0} +dialog.feedback.haptic_init={0} selected\u000AYou need to restart the editor in order apply this change server.shutdown_msg=request by user server.not_running_exc=Server not running @@ -166,6 +169,9 @@ speech.awareness_panel.open=Awareness panel open speech.awareness_panel.close=Awareness panel close +speech.haptic_window_open=Haptic Window open +speech.haptic_window_close=Haptic Window closed + ### TABBED PANE ### tab.new_tab=new {0} tab.new_tab_id=new {0} ({1}) @@ -275,6 +281,7 @@ collab.hide_awareness_panel.text=Hide Awareness Panel preferences.text=Preferences preferences.mnemonic=P +preferences.show_haptics.text=Show Haptics Window preferences.awareness.text=Awareness preferences.awareness.mnemonic=A preferences.awareness.username.text=User Name @@ -282,6 +289,7 @@ preferences.awareness.display.text=Display Filter preferences.awareness.enable_voice.text=Enable Awareness Voice preferences.file_chooser.text=Enable accessible file chooser +preferences.enable_log.text=Enable interaction log preferences.sound.text=Sound preferences.sound.mnemonic=S preferences.sound.mute.text=Mute @@ -292,6 +300,7 @@ preferences.server.text=Networking preferences.local_server.port.text=Local Server Port preferences.remote_server.port.text=Remote Server Port +preferences.change_haptics.text=Change Haptic Device help.text=Help
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/EditorTabbedPane.java Tue Jul 10 22:39:37 2012 +0100 @@ -118,10 +118,10 @@ /** * The components in an {@code EditorTabbedPane} are all instances of {@code DiagramPanel}, which in turns - * holds an instance of {@code Diagram}. This method returns the index of the {@code DiagramPanel} + * hold an instance of {@code Diagram}. This method returns the index of the {@code DiagramPanel} * whose diagram has been saved on the file system at the path specified as argument. * - * @param path + * @param path a path on the file system identifying the diagram * @return the index of the {@code DiagramPanel} whose diagram has been saved on * the file system in {@code path}, or {@code -1} if such diagram doesn't exist */
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/ExtensionFilter.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ExtensionFilter.java Tue Jul 10 22:39:37 2012 +0100 @@ -28,9 +28,7 @@ A file filter that accepts all files with a given set of extensions. */ -public class ExtensionFilter - extends FileFilter -{ +public class ExtensionFilter extends FileFilter { /** Constructs an extension file filter. @param description the description (e.g. "Woozle files") @@ -46,9 +44,9 @@ /** Constructs an extension file filter. @param description the description (e.g. "Woozle files") + @param extensions a list of '|'-separated accepted extensions */ - public ExtensionFilter(String description, - String extensions){ + public ExtensionFilter(String description, String extensions){ this.description = description; StringTokenizer tokenizer = new StringTokenizer( extensions, "|"); @@ -72,10 +70,6 @@ return description; } - public String[] getExtensions(){ - return extensions; - } - @Override public String toString(){ return description;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/FileService.java Tue Jul 10 22:39:37 2012 +0100 @@ -39,6 +39,10 @@ import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; +/** + * A Utility class providing inner classes and interfaces for handling + * files. + */ public abstract class FileService { @@ -51,18 +55,18 @@ * Gets the input stream corresponding to the user selection. * @return the input stream, or null if the user cancels the file selection task */ - InputStream getInputStream() throws IOException ; + InputStream getInputStream(); /** * Gets the name of the file that the user selected. * @return the file name, or null if the user cancels the file selection task */ - String getName() throws IOException ; + String getName(); /** * Gets the path of the file that the user selected. * @return the file path , or null if the user cancels the file selection task */ - String getPath() throws IOException; + String getPath(); } @@ -89,18 +93,35 @@ } /** - * This class implements a FileService with a JFileChooser + * This class returns a FileService for opening and saving files, with either a JFileChooser or a + * SpeechFileChooser */ public static class ChooserService { + /** + * Creates a new {@code ChooserService}. + * + * @param initialDirectory the directory displayed when the files service is displayed + */ public ChooserService(File initialDirectory){ useAccessible = Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true")); fileChooser = FileChooserFactory.getFileChooser(useAccessible); fileChooser.setCurrentDirectory(initialDirectory); } + /** + * Returns a {@code FileService.Open} out of a file chosen by the user. The user is prompted + * with either a normal or an accessible file choser. + * + * @param defaultDirectory the directory to open the file chooser, prompted to the user + * @param defaultFile the file selected when the file chooser is prompted to the user + * @param filter an {@code ExtensionFilter} to filter the filed displayed on the file chooser + * @param frame the frame where the file chooser is displayed + * @return an {@code FileService.Open} to handle the file selected by the user + * @throws IOException if the file chosen by the uses doesn't exist. + */ public FileService.Open open(String defaultDirectory, String defaultFile, - ExtensionFilter filter, Frame frame) throws FileNotFoundException { + ExtensionFilter filter, Frame frame) throws IOException { checkChangedOption(); fileChooser.resetChoosableFileFilters(); fileChooser.setFileFilter(filter); @@ -122,9 +143,20 @@ } } - - /* If the user cancels the task (presses cancel button or the X at the top left) * - * the CANCEl sound is played (together with the registered playerListeenr if any) */ + /** + * Returns a {@code FileService.Save} out of a file chosen by the user. The user is prompted + * with either a normal or an accessible file chooser. + * + * @param defaultDirectory the directory to open the file chooser, prompted to the user + * @param defaultFile the file selected when the file chooser is prompted to the user + * @param filter an {@code ExtensionFilter} to filter the filed displayed on the file chooser + * @param removeExtension the extension to be removed from the chosen file name. Use {@code null} for removing no extension + * @param addExtension the extension to be added to the chosen file name. + * @param currentTabs an array of already open files names. If the selected file matches any of these, then an + * @return an {@code FileService.Save} to handle the file selected by the user + * @throws IOException if the file chosen by the uses doesn't exist or has a file name that's already + * in {@code currentTabs}. + */ public FileService.Save save(String defaultDirectory, String defaultFile, ExtensionFilter filter, String removeExtension, String addExtension, String[] currentTabs) throws IOException { checkChangedOption(); @@ -150,9 +182,12 @@ f = new File(f.getPath() + addExtension); String fileName = getFileNameFromPath(f.getAbsolutePath(),false); - for(String tab : currentTabs){ - if(fileName.equals(tab)) - throw new IOException(resources.getString("dialog.error.same_file_name")); + /* check the name against the names of already open tabs */ + if(currentTabs != null){ + for(String tab : currentTabs){ + if(fileName.equals(tab)) + throw new IOException(resources.getString("dialog.error.same_file_name")); + } } if (!f.exists()) // file doesn't exits return the new SaveImpl with no problems @@ -176,6 +211,8 @@ return new SaveImpl(f); } } + /* If the user cancels the task (presses cancel button or the X at the top left) * + * the CANCEl sound is played (together with the registered playerListeenr if any) */ if(useAccessible) SoundFactory.getInstance().play(SoundEvent.CANCEL); /* returned if the user doesn't want to overwrite the file */ @@ -197,12 +234,30 @@ private boolean useAccessible; } + /** + * A file service which doesn't show any dialog to let + * the user choose which file to open or save. + */ public static class DirectService { - public Open open(File file) throws IOException{ + /** + * Creates a {@code FileService.Open} out of the file passed as argument. + * + * @param file the file to open + * @return a new {@code FileService.Open} instance + * @throws IOException if {@code file} cannot be found + */ + public FileService.Open open(File file) throws IOException{ return new OpenImpl(file); } - public Save save(File file) throws IOException{ + /** + * Creates a {@code FileService.Save} out of the file passed as argument. + * + * @param file the file to save + * @return a new {@code FileService.Save} instance + * @throws IOException if {@code file} cannot be found + */ + public FileService.Save save(File file) throws IOException{ return new SaveImpl(file); } } @@ -228,8 +283,7 @@ private OutputStream out; } - private static class OpenImpl implements FileService.Open - { + private static class OpenImpl implements FileService.Open{ public OpenImpl(File f) throws FileNotFoundException{ if (f != null){ path = f.getPath(); @@ -251,16 +305,16 @@ } /** - Edits the file path so that it ends in the desired - extension. - @param original the file to use as a starting point - @param toBeRemoved the extension that is to be - removed before adding the desired extension. Use - null if nothing needs to be removed. - @param desired the desired extension (e.g. ".png"), - or a | separated list of extensions - @return original if it already has the desired - extension, or a new file with the edited file path + * Edits the file path so that it ends in the desired + * extension. + * @param original the file to use as a starting point + * @param toBeRemoved the extension that is to be + * removed before adding the desired extension. Use + * null if nothing needs to be removed. + * @param desired the desired extension (e.g. ".png"), + * or a | separated list of extensions + * @return original if it already has the desired + * extension, or a new file with the edited file path */ public static String editExtension(String original, String toBeRemoved, String desired){ @@ -277,6 +331,14 @@ return path; } + /** + * Returns the single file name from a file path + * + * @param path the path to extract the file name from + * @param keepExtension whether to keep the extension of the file + * in the returned string + * @return the name of the file identified by {@code path} + */ public static String getFileNameFromPath(String path,boolean keepExtension){ int index = path.lastIndexOf(System.getProperty("file.separator")); String name;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Finder.java Tue Jul 10 22:39:37 2012 +0100 @@ -26,29 +26,56 @@ /** * - * A utility class which provides methods for searching either a node or an edge + * A utility class providing static methods for searching either a node or an edge * in a collection or array. - * */ public abstract class Finder { - public static Node findNode(String nodeClass,Node[] prototypes){ - for(Node n : prototypes){ - if(n.getType().equals(nodeClass)){ + /** + * Finds a node of a type in an array of nodes. The types should be all different + * from each other as only the first node encountered will be returned. If none of the nodes + * as the type passed as argument, {@code null} is returned. + * + * @param nodeType the type of the node to find. + * @param nodes the array to search for the node + * @return the first node with type {@code nodeType} or {@code null} if such node + * doesn't exist + */ + public static Node findNode(String nodeType,Node[] nodes){ + for(Node n : nodes){ + if(n.getType().equals(nodeType)){ return n; } } return null; } - public static Edge findEdge(String edgeClass,Edge[] prototypes){ - for(Edge e : prototypes){ - if(e.getType().equals(edgeClass)){ + /** + * Finds an edge of a {@code edgeType} type in an array of nodes. The types should be all different + * from each other as only the first edge encountered will be returned. If none of the edges + * as the type passed as argument, {@code null} is returned. + * + * @param edgeType the type of the edge to find + * @param edges the array to search for the edge + * @return the first edge with type {@code nodeType} or {@code null} if such edge + * doesn't exist + */ + public static Edge findEdge(String edgeType,Edge[] edges){ + for(Edge e : edges){ + if(e.getType().equals(edgeType)){ return e; } } return null; } + /** + * Finds a node with an id in a {@code Collection} of nodes. If none of the nodes + * has the id passed as argument, {@code null} is returned. + * + * @param id the id of the node to find + * @param collection the collection to search for the node + * @return the node with the specified id, or {@code null} if such node doesn't exist + */ public static Node findNode(Long id, Collection<Node> collection){ for(Node n : collection) if(n.getId() == id) @@ -56,6 +83,14 @@ return null; } + /** + * Finds a node containing a point {@code p} in a {@code Collection} of nodes. If none of the nodes + * contains the point, {@code null} is returned. + * + * @param p the point in a graphic environment + * @param collection the collection to search for the node + * @return the node containing {@code p}, or {@code null} if such node doesn't exist + */ public static Node findNode(Point2D p, Collection<Node> collection){ for (Node n : collection) if (n.contains(p)) @@ -63,6 +98,29 @@ return null; } + /** + * Finds an edge with an id in a {@code Collection} of edges. If none of the edges + * has the id passed as argument, {@code null} is returned. + * + * @param id the id of the edge to find + * @param collection the collection to search for the edge + * @return the edge with the specified id, or {@code null} if such edge doesn't exist + */ + public static Edge findEdge(Long id, Collection<Edge> collection){ + for(Edge e : collection) + if(e.getId() == id) + return e; + return null; + } + + /** + * Finds an edge containing a point {@code p} in a {@code Collection} of edges. If none of the edges + * contains the point, {@code null} is returned. + * + * @param p the point in a graphic environment + * @param collection the collection to search for the edge + * @return the edge containing {@code p}, or {@code null} if such edge doesn't exist + */ public static Edge findEdge(Point2D p, Collection<Edge> collection){ for (Edge e : collection) if (e.contains(p)) @@ -70,13 +128,14 @@ return null; } - public static Edge findEdge(Long id, Collection<Edge> collection){ - for(Edge e : collection) - if(e.getId() == id) - return e; - return null; - } - + /** + * Finds a element (node or edge) with an id in a {@code Collection} of diagram elements. If none of the elements + * has the id passed as argument, {@code null} is returned. + * + * @param id the id of the element to find + * @param collection the collection to search for the element + * @return the element with the specified id, or {@code null} if such element doesn't exist + */ public static DiagramElement findElement(Long id, Collection<DiagramElement> collection){ for(DiagramElement e : collection) if(e.getId() == id) @@ -84,6 +143,16 @@ return null; } + /** + * Finds a element (node or edge) with an identity hash code in a {@code Collection} of diagram elements. If none of the elements + * has the code passed as argument, {@code null} is returned. + * + * @param identityHashcode the identity hash code of the element to find + * @param collection the collection to search for the element + * @return the element with the specified identity hash code, or {@code null} if such element doesn't exist + * + * @see Object#hashCode() + */ public static DiagramElement findElementByHashcode(long identityHashcode, Collection<DiagramElement> collection){ for(DiagramElement de : collection){ if(System.identityHashCode(de) == identityHashcode){ @@ -94,12 +163,13 @@ } /** - * Return the tree node whose path is described by the variable path - * where path contains the indexes returned by each node n of the path upon calling n.getParent().getChildAt(n) + * Returns the tree node whose path is described by the variable path + * where path contains the indexes returned by each node n of the + * path upon calling n.getParent().getChildAt(n). * - * @param path - * @param root - * @return + * @param path the path for the node in the tree + * @param root the node where the path starts + * @return the node at the specified path, or {@code null} otherwise */ public static DiagramTreeNode findTreeNode(int[] path, DiagramTreeNode root){ DiagramTreeNode retVal = root;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphElement.java Tue Jul 10 22:39:37 2012 +0100 @@ -61,7 +61,7 @@ * This method is to be called when the motion (e.g. a translation) is over. * Note that for instance a translation might be composed on several calls to {@code translate} * - * @param source + * @param source the source of the motion action */ public void stopMove(Object source);
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphPanel.java Tue Jul 10 22:39:37 2012 +0100 @@ -61,7 +61,7 @@ /** * Constructs a graph. * @param aDiagram a diagram to paint in the graph - * @param a aToolbar a toolbar containing the node and edges prototypes for creating + * @param aToolbar a toolbar containing the node and edges prototypes for creating * elements in the graph. */ @@ -85,7 +85,7 @@ selectedElements = new HashSet<DiagramElement>(); moveLockedElements = new HashSet<Object>(); - toolbar.addEdgeCreatedListener(new innerEdgeListener()); + toolbar.setEdgeCreatedListener(new innerEdgeListener()); /* ---- COLLECTION LISTENER ---- * Adding a collection listener. This listener reacts at changes in the model @@ -156,7 +156,11 @@ Object tool = toolbar.getSelectedTool(); /* - right click - */ if((event.getModifiers() & InputEvent.BUTTON1_MASK) == 0) { - if(e != null){ + if(n != null){ + CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements); + nodePopup = pop; + pop.show(GraphPanel.this, event.getX(), event.getY()); + }else if(e != null){ if( e.contains(mousePoint)){ Node extremityNode = e.getClosestNode(mousePoint,EDGE_END_MIN_CLICK_DIST); if(extremityNode == null){ // click far from the attached nodes, only prompt with set name item @@ -169,12 +173,9 @@ pop.show(GraphPanel.this, event.getX(), event.getY()); } } - }else if(n != null){ - CCmIPopupMenu.NodePopupMenu pop = new CCmIPopupMenu.NodePopupMenu(n,GraphPanel.this,modelUpdater,selectedElements); - nodePopup = pop; - pop.show(GraphPanel.this, event.getX(), event.getY()); - }else + }else { return; + } } /* - one click && palette == select - */ @@ -368,6 +369,13 @@ paintGraph(g); } + /** + * Paints the graph on the graphics passed as argument. This function is called + * each time the component is painted. + * + * @see #paintComponent(Graphics) + * @param g the graphics object used to paint this graph + */ public void paintGraph(Graphics g){ Graphics2D g2 = (Graphics2D) g; g2.translate(-minX, -minY); @@ -541,12 +549,23 @@ } /** - Gets the smallest rectangle enclosing the graph - @return the bounding rectangle + * Gets the smallest rectangle enclosing the graph + * @return the bounding rectangle */ public Rectangle2D getMinBounds() { return minBounds; } + + /** + * Sets the smallest rectangle enclosing the graph + * @param newValue the bounding rectangle + */ public void setMinBounds(Rectangle2D newValue) { minBounds = newValue; } + /** + * Returns the smallest rectangle enclosing the graph, that is all the nodes + * and all the edges in the diagram. + * + * @return the bounding rectangle + */ public Rectangle2D getGraphBounds(){ Rectangle2D r = minBounds; for (Node n : nodes){ @@ -561,6 +580,12 @@ r.getWidth() + Node.SHADOW_GAP + Math.abs(minX), r.getHeight() + Node.SHADOW_GAP + Math.abs(minY)); } + /** + * Sets the model updater for this graph. The model updater is used + * to make changes to the diagram (e.g. adding, removing, renaming nodes and edges) + * + * @param modelUpdater the model updater for this graph panel + */ public void setModelUpdater(DiagramModelUpdater modelUpdater){ this.modelUpdater = modelUpdater; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphToolbar.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/GraphToolbar.java Tue Jul 10 22:39:37 2012 +0100 @@ -46,8 +46,10 @@ */ @SuppressWarnings("serial") public class GraphToolbar extends JToolBar { - /** - Constructs a tool bar with no icons. + /** + * Constructs a tool bar with no icons. + * + * @param diagram the diagram this toolbar is related to */ public GraphToolbar(Diagram diagram){ /* creates icon for select button */ @@ -97,7 +99,7 @@ /** Gets the node prototype that is associated with the currently selected button - @return a Node or Edge prototype + @return a {@code Node} prototype */ public Node getSelectedTool() { @SuppressWarnings("rawtypes") @@ -117,9 +119,9 @@ /** Adds a node to the tool bar. @param n the node to add - @param tip the tool tip + @param tip the tool tip appearing when hovering on this edge button */ - public void add(final Node n, String text){ + public void add(final Node n, String tip){ Icon icon = new Icon(){ public int getIconHeight() { return BUTTON_SIZE; } public int getIconWidth() { return BUTTON_SIZE; } @@ -143,7 +145,7 @@ }; NodeButton button = new NodeButton(n, icon); - button.setToolTipText(text); + button.setToolTipText(tip); add(button); nodeButtonsGroup.add(button); @@ -151,10 +153,10 @@ /** Adds an edge to the tool bar. - @param n the node to add - @param tip the tool tip + @param e the edge to add + @param tip the tool tip appearing when hovering on this edge button */ - public void add(final Edge e, String text){ + public void add(final Edge e, String tip){ Icon icon = new Icon(){ public int getIconHeight() { return BUTTON_SIZE; } public int getIconWidth() { return BUTTON_SIZE; } @@ -189,7 +191,7 @@ } }; final JButton button = new JButton(icon); - button.setToolTipText(text); + button.setToolTipText(tip); button.setFocusable(false); button.addActionListener(new ActionListener(){ @@ -200,7 +202,13 @@ add(button); } - public void addEdgeCreatedListener(EdgeCreatedListener edgeCreatedListener){ + /** + * Sets the {@code EdgeCreatedListener} for this toolbar. Any previous set listener + * will be overwritten. + * + * @param edgeCreatedListener the new {@code EdgeCreatedListener} for this toolbar + */ + public void setEdgeCreatedListener(EdgeCreatedListener edgeCreatedListener){ this.edgeCreatedListener = edgeCreatedListener; } @@ -217,7 +225,17 @@ Node node; } + /** + * The listener interface receiving events when the user clicks on + * an {@code Edge} button. Unlike {@code Node} buttons which are {@code JTobbleButton} + * objects to select the {@code Node} returned by {@code getSelectedTool()}, + * the {@code Edge} buttons just trigger the registered listener with an immediate effect. + */ public interface EdgeCreatedListener { + /** + * Invoked when an {@code Edge} button is pressed. + * @param e the {@code Edge} related to the pressed button + */ void edgeCreated(Edge e); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/HapticKindle.java Tue Jul 10 22:39:37 2012 +0100 @@ -27,6 +27,7 @@ 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; @@ -39,20 +40,23 @@ /** * * An instance of HapticListener for the diagram editor. By this class visual diagrams - * can be manipulated by an haptic device. + * 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 HapticListener { +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(){ - EditorFrame frame = DiagramEditorApp.getFrame(); - if((frame == null)||(frame.getActiveTab() == null)) - return; - frame.selectHapticHighligh(null); + cmdImpl.executeCommand(HapticListenerCommand.UNSELECT, 0, 0, 0, 0, 0); } }; } @@ -62,57 +66,26 @@ * 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 HapticListener#executeCommand(HapticListenerCommand, int, double, double, double, double) + * @see HapticListenerThread#executeCommand(HapticListenerCommand, int, double, double, double, double) */ @Override - public void executeCommand(HapticListenerCommand cmd, final int ID, final double x, final double y, final double startX, final double startY) { - final EditorFrame frame = DiagramEditorApp.getFrame(); + 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(){ - 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 : - SwingUtilities.invokeLater(new Runnable(){ - public void run(){ - 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 : - SwingUtilities.invokeLater(new Runnable(){ - public void run(){ - 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); + 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 */ @@ -120,144 +93,194 @@ SwingUtilities.invokeAndWait(new Runnable(){ @Override public void run(){ - 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() - )); + cmdImpl.executeCommand(cmd, ID, x, y, startX, startY); } // run() }); } catch (Exception e) { throw new RuntimeException(e); } - SoundFactory.getInstance().play(SoundEvent.HOOK_OFF); } break; - case INFO : - SwingUtilities.invokeLater(new Runnable(){ - public void run(){ - 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()); + + 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()); } - }); - 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 : - try { - SwingUtilities.invokeAndWait(new Runnable (){ - @Override - public void run(){ + 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; - 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",""); - } - }); - }catch(Exception e){ - e.printStackTrace(); - throw new RuntimeException(); + frame.backupOpenDiagrams(); + NarratorFactory.getInstance().speak(ResourceBundle.getBundle(EditorFrame.class.getName()).getString("speech.haptic_device_crashed")); + }break; } - break; - case ERROR : - /* no synchronization necessary as the XMLManager looks after it*/ - SwingUtilities.invokeLater(new Runnable(){ - @Override - public void run(){ - 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); } } - private void iLog(String action, String args){ - InteractionLog.log(INTERACTION_LOG_SOURCE,action,args); - } - - private Runnable unselectRunnable; - private static String INTERACTION_LOG_SOURCE = "HAPTIC"; - //private EditorFrame frame; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LineStyle.java Tue Jul 10 22:39:37 2012 +0100 @@ -60,8 +60,8 @@ * Returns an a bit representation of the stippling of this edge. * This value can be used by openGL like libraries to draw the edge and it's used by * the OmniHaptic device native code to paint the edge visually and haptically. - * See also {@link http://www.opengl.org/sdk/docs/man/xhtml/glLineStipple.xml} - * + * + * @see <a href="http://www.opengl.org/sdk/docs/man/xhtml/glLineStipple.xml">glLineStipple</a> * * @return an int with the bit representation of the stipple pattern */
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Lock.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Lock.java Tue Jul 10 22:39:37 2012 +0100 @@ -24,13 +24,44 @@ * */ public enum Lock { + /** + * The element will be deleted, therefore no other operation on the same element is allowed to any other user. + */ DELETE, + /** + * The element will be renamed, therefore no renaming of the same element is allowed to any other user. + */ NAME, + /** + * The node properties will be edit, therefore no properties or modifiers editing + * on the same node will be allowed to any other user. + */ PROPERTIES, + /** + * The edge end is being edited (label or arrow head), therefore no end editing + * on the same edge will be allowed to any other user. + */ EDGE_END, + /** + * The element is being moved, therefore no move on the same element will be allowed to any other user. + */ MOVE, + /** + * The notes of the tree node will be edited, therefore no notes editing + * on the same tree node will be allowed to any other user. + */ NOTES, + /** + * The bookmarks of the tree node will be edited, therefore no bookmarks editing + * on the same tree node will be allowed to any other user. + */ BOOKMARK, + /** + * The element cannot be deleted by other users. + */ MUST_EXIST, + /** + * {@code null} value. + */ NONE }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/LoopComboBox.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/LoopComboBox.java Tue Jul 10 22:39:37 2012 +0100 @@ -73,7 +73,7 @@ } } - /** + /* * when the comboBox has only one item the ItemStateChanged listeners ain't fired by default. * This behaviour has to be forced in order to have the item label to be spoken out by * the narrator, in spite of the item number .
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/ModifierEditorDialog.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ModifierEditorDialog.java Tue Jul 10 22:39:37 2012 +0100 @@ -35,10 +35,8 @@ import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities; /** - * * A dialog showing a list of checkboxes. By selecting the checkboxes the user can choose * which modifiers are assigned to a property value. - * */ @SuppressWarnings("serial") public class ModifierEditorDialog extends JDialog { @@ -82,6 +80,16 @@ pack(); } + /** + * Shows a dialog with the checkboxes for the user to tick. + * + * @param parent the parent JDialog this dialog will appear in front of + * @param modifierTypes a list of the modifier that will be shown to the user, each near a checkbox + * @param modifiers a set of modifiers indexes. The {@code modifierTypes} at the specified indexes will + * be shown as already ticked + * + * @return a reference to {@code modifiers} after it has been updated according to the user selections. + */ public static Set<Integer> showDialog(JDialog parent, List<String> modifierTypes, Set<Integer> modifiers){ ModifierEditorDialog.modifiers = modifiers; dialog = new ModifierEditorDialog(parent, modifierTypes, modifiers); @@ -91,6 +99,16 @@ } + /** + * Shows a dialog with the checkboxes for the user to tick. + * + * @param parent the parent Frame this dialog will appear in front of + * @param modifierTypes a list of the modifier that will be shown to the user, each near a checkbox + * @param modifiers a set of modifiers indexes. The {@code modifierTypes} at the specified indexes will + * be shown as already ticked + * + * @return a reference to {@code modifiers} after it has been updated according to the user selections. + */ public static Set<Integer> showDialog(Frame parent, List<String> modifierTypes, Set<Integer> modifiers){ ModifierEditorDialog.modifiers = modifiers; dialog = new ModifierEditorDialog(parent, modifierTypes, modifiers);
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/Node.java Tue Jul 10 22:39:37 2012 +0100 @@ -35,7 +35,6 @@ import org.w3c.dom.NodeList; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramEdge; -import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramNode; import uk.ac.qmul.eecs.ccmi.diagrammodel.ElementChangedEvent; import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties; @@ -43,19 +42,24 @@ import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; /** - * An node in a graph. Node objects are used in a GraphPanel to render diagram nodes visually. - * Node objects are used in the tree representation of the diagram as well, as they're + * An node in a graph. {@code Node} objects are used in a {@code GraphPanel} to render diagram nodes visually. + * {@code Node} objects are used in the tree representation of the diagram as well, as they're * subclasses of {@link DiagramNode} * */ @SuppressWarnings("serial") public abstract class Node extends DiagramNode implements GraphElement{ + /** + * Constructor to be called by sub classes + * + * @param type the type of the new node. All nodes with this type will be + * put under the same tree node in the tree representation + * @param properties the properties of this node + */ public Node(String type, NodeProperties properties){ super(type,properties); - internalNodes = new ArrayList<Node>(); attachedEdges = new ArrayList<Edge>(); - externalNode = null; } /* --- DiagramNode abstract methods implementation --- */ @@ -81,35 +85,32 @@ @Override public Node getExternalNode(){ - return externalNode; + return null; } @Override public void setExternalNode(DiagramNode node){ - externalNode = (Node)node; + throw new UnsupportedOperationException(); } @Override public Node getInternalNodeAt(int i){ - return internalNodes.get(i); + throw new UnsupportedOperationException(); } @Override public int getInternalNodesNum(){ - return internalNodes.size(); + return 0; } @Override public void addInternalNode(DiagramNode node){ - internalNodes.add((Node)node); + throw new UnsupportedOperationException(); } @Override public void removeInternalNode(DiagramNode node){ - if (node.getExternalNode() != this) - return; - internalNodes.remove(node); - node.setExternalNode(null); + throw new UnsupportedOperationException(); } @Override @@ -122,12 +123,7 @@ } } - /** - * Translates the node by a given amount - * @param p the point we are translating from - * @param dx the amount to translate in the x-direction - * @param dy the amount to translate in the y-direction - */ + @Override public void translate( Point2D p , double dx, double dy, Object source){ translateImplementation( p, dx, dy); for(int i=0; i< getInternalNodesNum();i++){ @@ -136,9 +132,6 @@ notifyChange(new ElementChangedEvent(this, this, "translate", source)); } - /** - * @see DiagramTreeNode#setNotes(String) - */ @Override protected void setNotes(String notes,Object source){ this.notes = notes; @@ -163,10 +156,6 @@ */ public abstract boolean contains(Point2D aPoint); - /** - * Get the bounding rectangle of the shape of this node - * @return the bounding rectangle - */ @Override public abstract Rectangle2D getBounds(); @@ -175,6 +164,7 @@ /* useless, here just to comply with the GraphElement interface */ } + @Override public abstract Point2D getConnectionPoint(Direction d); @Override @@ -188,8 +178,21 @@ } } + /** + * Returns the geometric shape of this node + * + * @return the shape of this node + */ public abstract Shape getShape(); + /** + * Encodes the internal data of this node (position, name, properties, modifiers) in XML format. + * + * The saved data can be retrieved and set back via {@code decode}. + * + * @param doc An XMl document + * @param parent the parent XML tag this node tag will be nested in + */ public void encode(Document doc, Element parent){ parent.setAttribute(PersistenceManager.NAME,getName()); @@ -240,6 +243,15 @@ } } + /** + * Sets the internal data of this node (position, name, properties, modifiers) from an XML file + * node tag previously encoded via {@code encode} + * + * @param doc An XMl document + * @param nodeTag the XML {@code PersistenceManager.NODE } tag with data for this node + * + * @see uk.ac.qmul.eecs.ccmi.gui.persistence + */ public void decode(Document doc, Element nodeTag) throws IOException{ setName(nodeTag.getAttribute(PersistenceManager.NAME),DiagramEventSource.PERS); try{ @@ -339,17 +351,19 @@ @Override public Object clone(){ Node clone = (Node)super.clone(); - clone.internalNodes = (ArrayList<Node>) internalNodes.clone(); - clone.externalNode = null; clone.attachedEdges = (ArrayList<Edge>) attachedEdges.clone(); return clone; } - protected ArrayList<Node> internalNodes; - protected Node externalNode; + /** + * An array of references to the edges attached to this node + */ protected ArrayList<Edge> attachedEdges; private final int MARKER_SIZE = 7; + /** + * The shadow color of nodes + */ protected static final Color SHADOW_COLOR = Color.LIGHT_GRAY; public static final int SHADOW_GAP = 2;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyEditorDialog.java Tue Jul 10 22:39:37 2012 +0100 @@ -89,6 +89,17 @@ pack(); } + /** + * A static method to show a {@code PropertyEditorDialog}. Via this dialog a user can + * specify the entries of a {@code NodeProperties} object and their modifiers, if any have been + * defined during the template creation. + * + * @param parent The parent {@code Frame} of the dialog + * @param properties an instance of {@code NodeProeprties} whose value will be shown in the dialog + * for the user to edit + * @return a reference to {@code properties} containing the new values entered by the user or + * {@code null} if the user presses the cancel button or closes the window. + */ public static NodeProperties showDialog(Frame parent, NodeProperties properties){ if(properties == null) throw new IllegalArgumentException(resources.getString("dialog.property_editor.error.property_null"));
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyTableModel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/PropertyTableModel.java Tue Jul 10 22:39:37 2012 +0100 @@ -29,13 +29,20 @@ /** - * - * A table model containing the property values currently edited and the modifiers assigned to them + * A table model containing the property values currently edited in a table of a {@code PropertyEditorDialog} + * and the modifiers assigned to them * in the form of an array of indexes pointing to the modifiers types */ @SuppressWarnings("serial") public class PropertyTableModel extends AbstractTableModel { - + + /** + * Construct a new {@code PropertyTableModel}. + * + * @param propertyType the type of the node property related to this table model + * @param values the list of values for this node property + * @param modifiers the modifiers for this node property + */ public PropertyTableModel(String propertyType, List<String> values, Modifiers modifiers ){ data = new ArrayList<ModifierString>(); for(int i = 0; i< values.size();i++){ @@ -88,23 +95,44 @@ return true; } + /** + * Returns the indexes (pointing to the modifiers types) of the property value at the specified row in the table + * with this model. + * + * @param row the row of the property value + * @return the modifiers type indexes for the specified property value + */ public Set<Integer> getIndexesAt(int row){ return data.get(row).modifierIndexes; } + /** + * Set the the indexes (pointing to the modifiers types) of the property value at the specified row in the table + * with this model. + * + * @param row he row of the property value + * @param indexes the modifiers type indexes for the specified property value + */ public void setIndexesAt(int row, Set<Integer> indexes){ data.get(row).modifierIndexes = new HashSet<Integer>(); data.get(row).modifierIndexes.addAll(indexes); } + /** + * Set the the indexes (pointing to the modifiers types) of the property value at the specified row in the table + * with this model. + * + * @param row he row of the property value + * @param indexes the modifiers type indexes for the specified property value + */ public void setIndexesAt(int row, Integer[] indexes){ data.get(row).modifierIndexes = new HashSet<Integer>(); for(int i=0; i<indexes.length; i++) data.get(row).modifierIndexes.add(indexes[i]); } - List<ModifierString> data; - String columnName; + private List<ModifierString> data; + private String columnName; private class ModifierString { ModifierString(String value, Set<Integer> s){ @@ -134,8 +162,8 @@ return value; } - String value; - Set<Integer> modifierIndexes; + private String value; + private Set<Integer> modifierIndexes; } }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/ResourceFactory.java Tue Jul 10 22:39:37 2012 +0100 @@ -20,30 +20,26 @@ package uk.ac.qmul.eecs.ccmi.gui; -import java.awt.Image; import java.awt.event.ActionListener; import java.beans.EventHandler; -import java.net.URL; import java.util.MissingResourceException; import java.util.ResourceBundle; -import javax.swing.Action; -import javax.swing.ImageIcon; -import javax.swing.JButton; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.KeyStroke; /** - * A factory class for swing components creation support - * + * A factory class for swing components creation support. Components that are created via this + * class are sonyfied via the Narrator */ -public class ResourceFactory{ +class ResourceFactory{ public ResourceFactory(ResourceBundle bundle){ this.bundle = bundle; } + public JMenuBar createMenuBar(){ return SpeechMenuFactory.getMenuBar(); } @@ -121,80 +117,6 @@ } return menu; } - - public JButton createButton(String prefix) { - String text = bundle.getString(prefix + ".text"); - JButton button = new JButton(text); - try { - String mnemonic = bundle.getString(prefix + ".mnemonic"); - button.setMnemonic(mnemonic.charAt(0)); - } - catch (MissingResourceException exception) { - // ok not to set mnemonic - } - - try { - String tooltip = bundle.getString(prefix + ".tooltip"); - button.setToolTipText(tooltip); - } - catch (MissingResourceException exception) { - // ok not to set tooltip - } - return button; - } - - public Action configureAction(String prefix, Action action) - { - try - { - String text = bundle.getString(prefix + ".text"); - action.putValue(Action.NAME, text); - } - catch (MissingResourceException exception) - { - // ok not to set name - } - - try - { - String mnemonic = bundle.getString(prefix + ".mnemonic"); - action.putValue(Action.MNEMONIC_KEY, new Integer(mnemonic.charAt(0))); - } - catch (MissingResourceException exception) - { - // ok not to set mnemonic - } - - try { - String accelerator = bundle.getString(prefix + ".accelerator"); - action.putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke(accelerator)); - } - catch (MissingResourceException exception){ - // ok not to set accelerator - } - - try - { - String tooltip = bundle.getString(prefix + ".tooltip"); - action.putValue(Action.SHORT_DESCRIPTION, tooltip); - } - catch (MissingResourceException exception) - { - // ok not to set tooltip - } - return action; - } - - public static class ImageFactory{ - public Image getImage(String path){ - URL url = getClass().getResource(path); - if(url != null) - return new ImageIcon(url).getImage(); - else - return null; - } - } - private ResourceBundle bundle; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechMenuFactory.java Tue Jul 10 22:39:37 2012 +0100 @@ -41,15 +41,14 @@ import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; -/** - * +/* * This class provides a version of JMenuItem, JCheckBox, and JMenu which emit an "error" sound when * they're disabled and the user tries to use it by an accelerator */ @SuppressWarnings("serial") -public class SpeechMenuFactory extends JMenu { +class SpeechMenuFactory extends JMenu { - /* implements the singleton pattern and keeps a static reference to the menuBar used bu JMenuItems and JMenus */ + /* implements the singleton pattern and keeps a static reference to the menuBar used by JMenuItems and JMenus */ public static JMenuBar getMenuBar(){ if(menuBar == null){ menuBar = new JMenuBar(){
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechOptionPane.java Tue Jul 10 22:39:37 2012 +0100 @@ -67,8 +67,6 @@ * cancelling the option. * Furthermore, this class provides one-line calls to display accessible dialog boxes. Input by the user as well * as focused components are spoken out through text to speech synthesis performed by a {@link Narrator} instance. - * - * */ public class SpeechOptionPane { @@ -96,7 +94,7 @@ * Pops the a dialog holding this SpeechOptionPane * * @param parent the parent component of the dialog - * @param the {@code Object} to display + * @param message the {@code Object} to display * @return an integer indicating the option selected by the user */ @SuppressWarnings("serial") @@ -121,12 +119,13 @@ optPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTROL,InputEvent.CTRL_DOWN_MASK),"shut_up"); optPane.getActionMap().put("shut_up", SpeechUtilities.getShutUpAction()); final JDialog dialog = optPane.createDialog(parent, title); + dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); SpeechUtilities.changeTabListener(optPane,dialog); /* when either button is pressed, dialog is disposed and the button itself becomes the optPane.value */ ActionListener buttonListener = new ActionListener(){ @Override public void actionPerformed(ActionEvent evt) { - onClose(dialog,message,(JButton)evt.getSource()); + onClose(dialog,(JButton)evt.getSource()); } }; okButton.addActionListener(buttonListener); @@ -135,7 +134,6 @@ SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); - dialog.dispose(); if(okButton.equals(optPane.getValue())){ return OK_OPTION; }else{ @@ -146,7 +144,7 @@ /** * Sets the string appearing at the top of the dialog where this option pane is displayed when {@code showDialog} * is called. - * @param title + * @param title the title of this option pane */ public void setDialogTitle(String title){ this.title = title; @@ -173,17 +171,16 @@ } /** - * This method is called just after the user pressed either button of the dialog displayed + * This method is called just after the user presses either button of the dialog displayed * after {@code showDialog} is called. * It assign a value to the return value and it frees the dialog resources. * It can be overwritten by subclasses but care should be taken of calling this class method via * {@code super} in order to properly close the dialog. * * @param dialog the dialog displayed after {@code showDialog} is called. - * @param message * @param source the button that triggered the closing of {@code dialog} */ - protected void onClose(JDialog dialog,Object message, JButton source){ + protected void onClose(JDialog dialog, JButton source){ optPane.setValue(source); dialog.dispose(); } @@ -196,13 +193,31 @@ /* -------- STATIC METHODS ----------- */ - public static String showTextAreaDialog(Component parentComponent, String message, String initialSelectionValue){ + /** + * Shows a dialog with a text area requesting input for the user. + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message a displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param text the initial text the text area contains when the dialog is displayed + * + * @return the new text entered by the user + */ + public static String showTextAreaDialog(Component parentComponent, String message, String text){ JTextArea textArea = new JTextArea(NOTES_TEXT_AREA_ROW_SIZE,NOTES_TEXT_AREA_COL_SIZE); - textArea.setText(initialSelectionValue); + textArea.setText(text); NarratorFactory.getInstance().speak(message); return textComponentDialog(parentComponent, message, textArea); } + /** + * Shows a dialog with a text field requesting input from the user + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param initialSelectionValue the initial text the text field contains when the dialog is displayed + * + * @return the text entered by the user + */ public static String showInputDialog(Component parentComponent, String message, String initialSelectionValue){ final JTextField textField = new JTextField(initialSelectionValue); textField.selectAll(); @@ -240,6 +255,7 @@ // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed @@ -251,10 +267,15 @@ } } - public static String showInputDialog(Component parentComponent, String message){ - return showInputDialog(parentComponent, message, ""); - } - + /** + * Shows a dialog with a {@code JComboBox} requesting selection from the user + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param options options for the {@code JComboBox} + * @param initialValue the options value selected when the dialog is shown + * @return the option selected by the user + */ public static Object showSelectionDialog(Component parentComponent, String message, Object[] options, Object initialValue){ final LoopComboBox comboBox = new LoopComboBox(options); comboBox.setSelectedItem(initialValue); @@ -282,6 +303,7 @@ // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed return null; @@ -292,6 +314,17 @@ } } + /** + * Shows the dialog with a {@code JSpinner} requesting the selection of the speech rate + * of the main voice of the {@code Narrator} + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param value the initial value + * @param min the minimum value of the spinner + * @param max the maximum value of the spinner + * @return the selected integer value or {@code null} if the user cancels the dialog + */ public static Integer showNarratorRateDialog(Component parentComponent, String message, int value, int min, int max){ NarratorFactory.getInstance().speak(message); final JSpinner spinner = new JSpinner(new LoopSpinnerNumberModel(value,min,max)); @@ -329,6 +362,7 @@ // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); /* set the speech rate back to the value passed as argument */ @@ -342,6 +376,14 @@ } } + /** + * Brings up a dialog with selected options requesting user to confirmation. + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param optionType an integer designating the options available on the dialog + * @return an integer indicating the option selected by the user + */ public static int showConfirmDialog(Component parentComponent, String message, int optionType){ NarratorFactory.getInstance().speak(message); JOptionPane optPane = new JOptionPane(); @@ -357,6 +399,7 @@ SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed @@ -365,6 +408,13 @@ return ((Integer)optPane.getValue()).intValue(); } + /** + * Displays a message to the user. + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message the message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param messageType the type of message to be displayed + */ public static void showMessageDialog(Component parentComponent, String message, int messageType){ NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.speech_option_pane.message"), message)); JOptionPane optPane = new JOptionPane(); @@ -378,13 +428,34 @@ SpeechUtilities.changeTabListener(optPane,dialog); SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); } + /** + * Displays an error message to the user. + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message the message displayed in the dialog, such text is also uttered by the {@code Narrator} + */ public static void showMessageDialog(Component parentComponent, String message){ showMessageDialog(parentComponent,message,ERROR_MESSAGE); } + /** + * Execute a ProgressDialogWorker task and + * shows an indeterminate progress bar dialog if the task is not completed after + * {@code millisToDecideToPopup}. The user can use the dialog <i>cancel</i> button + * to cancel the task. + * + * @param <T> the result type returned by the worker + * @param <V> the intermediate result type of the worker + * @param parentComponent the parent {@code Component} for the dialog + * @param message message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param worker a {@code ProgressDialogWorker} that is executed when this method is called + * @param millisToDecideToPopup the millisecond to let to the worker before popping the dialog up + * @return an integer indicating whether the task was completed or it was interrupted by the user + */ public static <T,V> int showProgressDialog(Component parentComponent, String message,final ProgressDialogWorker<T,V> worker, int millisToDecideToPopup){ JProgressBar progressBar = worker.bar; Object displayObjects[] = {message, progressBar}; @@ -421,6 +492,15 @@ return OK_OPTION; } + /** + * Shows a check box dialog to select the modifiers of a given property + * + * @param parentComponent the parent {@code Component} for the dialog + * @param message message a message displayed in the dialog, such text is also uttered by the {@code Narrator} + * @param modifierTypes the different types of modifiers that are available for a given property + * @param modifierIndexes the initial selection of modifiers as the dialog is shown + * @return a new set with the modifier indexes selected by the user + */ public static Set<Integer> showModifiersDialog(Component parentComponent, String message, List<String> modifierTypes, Set<Integer> modifierIndexes){ JOptionPane optPane = new JOptionPane(); @@ -454,6 +534,7 @@ SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); if(optPane.getValue() == null)//window closed @@ -469,6 +550,12 @@ } } + /** + * Returns the specified component's {code Frame}. + * + * @param parentComponent the component for this dialog + * @return the {@code Frame} that contains the component + */ public static Frame getFrameForComponent(Component parentComponent){ return JOptionPane.getFrameForComponent(parentComponent); } @@ -492,8 +579,17 @@ public static final int YES_OPTION = JOptionPane.YES_OPTION; public static final int NO_OPTION = JOptionPane.NO_OPTION; - + /** + * A swing worker to be passed as argument to {@code showProgressDialog}. The {@code execute} + * method can be interrupted by the user by clicking on the {@code cancel} button of the dialog. + * + * @param <T> the result type returned by this {@code SwingWorker}'s {@code doInBackground} and {@code get} methods + * @param <V> the type used for carrying out intermediate results by this {@code SwingWorker}'s {@code publish} and {@code process} methods + */ public static abstract class ProgressDialogWorker<T,V> extends SwingWorker<T,V> { + /** + * Creates a new ProgressDialogWorker + */ public ProgressDialogWorker(){ bar = new JProgressBar(); bar.setIndeterminate(true); @@ -510,7 +606,7 @@ } private JDialog dialog; - protected JProgressBar bar; + private JProgressBar bar; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechSummaryPane.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/SpeechSummaryPane.java Tue Jul 10 22:39:37 2012 +0100 @@ -43,14 +43,24 @@ /** * Abstract class with an one-line call to display a summary dialog. * The summary text as well as focused components are spoken out through text to speech - * synthesis performed by the {@link Narrator} instance. - * A summary dialog has non editable text field and a button - * for confirmation only. + * synthesis performed by the {@code Narrator} instance. + * A summary dialog has non editable text field and a button for confirmation only. * * */ public abstract class SpeechSummaryPane { + /** + * shows the summary dialog + * @param parentComponent determines the {@code Frame} in which the dialog is displayed + * @param title the title of the displayed dialog + * @param text the text to be displayed in this dialog. his text, together with the title + * is uttered through the {@code Narrator} as soon as the dialog is shown + * @param optionType an integer designating the options available on the dialog + * either {@code OK_CANCEL_OPTION} or {@code OK_OPTION} + * @param options an array of strings indicating the possible choices the user can make + * @return an integer indicating the option selected by the user. Either {@code OK} or {@code CANCEL} + */ public static int showDialog(Component parentComponent, String title, String text, int optionType, String[] options){ if(optionType == OK_CANCEL_OPTION && options.length < 2) throw new IllegalArgumentException("option type and opions number must be consistent"); @@ -98,6 +108,7 @@ // start the editing sound SoundFactory.getInstance().startLoop(SoundEvent.EDITING); dialog.setVisible(true); + dialog.dispose(); SoundFactory.getInstance().stopLoop(SoundEvent.EDITING); NarratorFactory.getInstance().shutUp();
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/TemplateEditor.java Tue Jul 10 22:39:37 2012 +0100 @@ -25,7 +25,7 @@ /** * A template editor is used to create new types of diagrams. * - * Template editors are run in the Event Dispatching Thread and can therefore make use of swimg components + * Template editors are run in the Event Dispatching Thread and can therefore make use of <i>Swing</i> components * to prompt the user with choices about the diagram to be created. The diagram created by * a template editor is precisely a prototype. * Such prototypes diagrams will then be used
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessFilter.java Tue Jul 10 22:39:37 2012 +0100 @@ -40,9 +40,22 @@ import uk.ac.qmul.eecs.ccmi.speech.TreeSonifier; import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities; +/** + * Base class for an awareness filter. It provides basic functionalities + * for properties handling (save to a file, show edit dialog) + */ public abstract class AwarenessFilter { - - AwarenessFilter(String propertiesFilePath, String propertiesFileName) throws IOException{ + /** + * The constructor to be called by sub classes of this class. The filter is built + * from a {@code SetProperties} previously saved on a file. If the file doesn't exist + * a new empty {@code SetProperties} is created. + * + * @param propertiesFilePath the path to the directory where the properties file is located. + * @param propertiesFileName the name of the properties file + * + * @throws IOException if I/O problems occurred when retrieving the properties file + */ + protected AwarenessFilter(String propertiesFilePath, String propertiesFileName) throws IOException{ if(propertiesFilePath == null) throw new IOException(ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("error.no_properties_dir")); properties = new SetProperties(); @@ -53,19 +66,37 @@ /** * Returns the file name this instance of AwarenessFilter was build from. It should return an existing - * XML file name, which will be read through {@code getClass().getResourceAsStream}, representing the tree + * XML file name, which will be read through {@code getClass().getResourceAsStream()}, representing the tree * displayed after calling {@code showDialog}. * - * @see {@link uk.ac.qmul.eecs.ccmi.checkboxtree.CheckBoxTree} + * @see uk.ac.qmul.eecs.ccmi.checkboxtree.CheckBoxTree * * @return the name of the XML file */ protected abstract String getXMLFileName(); + /** + * Returns the title of the dialog displayed when {@link #showDialog(Component)} + * is called. + * + * @return a title for the preferences dialog + */ protected abstract String getDialogTitle(); + /** + * Saves the properties with a custom comment. Subclasses must implement + * this method providing the custom comment. + * + * @param parentComponent the parent component of the displayed dialog + */ public abstract void saveProperties(Component parentComponent); + /** + * Brings up a dialog with a {@code CheckBoxTree} that can be used to set the properties of + * this filter. Once the user presses the OK button, the selected properties are saved. + * + * @param parent the parent component of the displayed dialog + */ public void showDialog(Component parent){ /* create and init components */ InputStream in = getClass().getResourceAsStream(getXMLFileName()); @@ -136,7 +167,14 @@ } } - public void saveProperties(Component parentComponent, String comments) { + /** + * Saves the properties to a file + * + * @param parentComponent a parent component where a message dialog will be displayed + * if an I/O error occur when saving the file + * @param comments additional comments to add at the beginning of the file. + */ + protected void saveProperties(Component parentComponent, String comments) { ResourceBundle resources = ResourceBundle.getBundle(AwarenessFilter.class.getName()); try { if(!propertiesFile.getParentFile().exists()) @@ -152,6 +190,10 @@ } } + /** + * An instance of {@code SetProperties} where the user configuration + * is saved. + */ protected final SetProperties properties; private boolean configurationHasChanged; private File propertiesFile;
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanel.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanel.java Tue Jul 10 22:39:37 2012 +0100 @@ -43,14 +43,31 @@ this.diagramName = diagramName; } - public AwarenessTextPane getUsersPane() { + /** + * Returns a reference to the bottom pane where the name of the users + * is displayed + * + * @return an awareness text pane + */ + AwarenessTextPane getUsersPane() { return usersPane; } - public AwarenessTextPane getRecordsPane() { + /** + * Returns a reference to the top pane where the name of the users + * is displayed + * + * @return an awareness text pane + */ + AwarenessTextPane getRecordsPane() { return recordsPane; } + /** + * Returns the name of the diagram this panel is bound to + * + * @return the name of the diagram this panel is bound to. + */ public String getDiagramName(){ return diagramName; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanelEditor.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessPanelEditor.java Tue Jul 10 22:39:37 2012 +0100 @@ -29,14 +29,27 @@ import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage; public class AwarenessPanelEditor { + /** + * Creates a new editor + */ public AwarenessPanelEditor() { awarenessPanels = Collections.synchronizedList(new ArrayList<AwarenessPanel>()); } + /** + * Adds a new panel to the editor. {@code getDiagramName} of {@code panel} + * will be used to retrieve the panel when a change is done to it. + * + * @param panel the panel to add + */ public void addAwarenessPanel(AwarenessPanel panel){ awarenessPanels.add(panel); } + /** + * Removes a panel from the editor + * @param panel the panel to remove + */ public void removeAwarenessPanel(AwarenessPanel panel){ awarenessPanels.remove(panel); } @@ -44,7 +57,8 @@ /** * Replaces a user's user name with a new one. * - * @param diagramName the diagram the update has to be performed on + * @param diagramName the name of the diagram linked to the panel + * the update has to be performed on * @param userNames a concatenation of the new user name and the old one. The * old user name can possibly be the empty string if the client has sent its * user name for the first time (hence no old user name exists). @@ -64,12 +78,25 @@ panel.getUsersPane().insert(names[0]+'\n'); } + /** + * Removes a user name + * @param diagramName the name of the diagram linked to the panel + * the update has to be performed on + * @param userName the name to be removed + */ public void removeUserName(String diagramName, String userName){ AwarenessPanel panel = findPanel(diagramName); if(panel != null) panel.getUsersPane().remove(userName+'\n'); } + /** + * Adds a new record to the panel + * + * @param diagramName the name of diagram linked to the panel the record + * has to be added to + * @param record the record to add + */ public void addRecord(String diagramName, String record){ AwarenessPanel panel = findPanel(diagramName); if(panel == null) @@ -77,6 +104,12 @@ panel.getRecordsPane().insert(record); } + /** + * Adds a record that will automatically be removed after TIMER_DELAY millisecond + * @param diagramName the name of diagram linked to the panel the record + * has to be added to + * @param record the record to add + */ public void addTimedRecord(final String diagramName, final String record){ addRecord(diagramName, record); Timer timer = new Timer(TIMER_DELAY,new ActionListener(){ @@ -89,6 +122,13 @@ timer.start(); } + /** + * Removes a record from the panel + * + * @param diagramName the name of diagram linked to the panel the record + * has to be removed from + * @param record the record to add + */ public void removeRecord(String diagramName, String record){ AwarenessPanel panel = findPanel(diagramName); if(panel == null) @@ -96,6 +136,11 @@ panel.getRecordsPane().remove(record); } + /** + * Removes all the record from a panel + * @param diagramName the name of diagram linked to the panel the records + * have to be removed from + */ public void clearRecords(String diagramName){ AwarenessPanel panel = findPanel(diagramName); if(panel == null) @@ -116,5 +161,8 @@ } private List<AwarenessPanel> awarenessPanels; - private static int TIMER_DELAY = 2000; + /** + * The time (in milliseconds) a {@code TimedRecord} will stay in the panel + */ + public static final int TIMER_DELAY = 2000; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessTextPane.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/AwarenessTextPane.java Tue Jul 10 22:39:37 2012 +0100 @@ -32,8 +32,8 @@ import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities; @SuppressWarnings("serial") -public class AwarenessTextPane extends JTextPane { - public AwarenessTextPane(String accessibleName){ +class AwarenessTextPane extends JTextPane { + AwarenessTextPane(String accessibleName){ records = new LinkedList<String>(); addKeyListener(SpeechUtilities.getSpeechKeyListener(false,true)); /* prevents getText() from automatically turn all the \n to \r\n */ @@ -43,17 +43,17 @@ SpeechUtilities.changeTabListener(this,DiagramEditorApp.getFrame()); } - public void insert(String record){ + void insert(String record){ records.add(0, record); update(); } - public void remove(String record){ + void remove(String record){ records.remove(record); update(); } - public void clear(){ + void clear(){ records.clear(); update(); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilter.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/BroadcastFilter.java Tue Jul 10 22:39:37 2012 +0100 @@ -27,13 +27,32 @@ import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; +/** + * A filter for {@code DiagramEventActionSource} generated by a client and to be broadcasted + * by the server to the other connected clients. The user (running the server) can select on a dialog + * the cases in which the {@code DiagramEventActionSource} is to be broadcasted or not. + * User selections are saved persistently using the persistence functionalities of {@code SetProperties}. + * + * @see uk.ac.qmul.eecs.ccmi.checkboxtree.SetProperties + */ public class BroadcastFilter extends AwarenessFilter { - public static BroadcastFilter getInstance(){ + /** + * Creates a new instance of this class. Successive calls on this method + * will create a new filter, which will be returned by {@code getInstance} from then on + * + * @return the instance created + * @throws IOException if I/O problems occurred when retrieving the properties file + */ + public static BroadcastFilter createInstance() throws IOException { + broadcastFilter = new BroadcastFilter(); return broadcastFilter; } - public static BroadcastFilter createInstance() throws IOException { - broadcastFilter = new BroadcastFilter(); + /** + * Returns the instance created by the last call of {@code createInstance} + * @return the instance of this class + */ + public static BroadcastFilter getInstance(){ return broadcastFilter; } @@ -43,12 +62,12 @@ } @Override - protected String getXMLFileName(){ + protected final String getXMLFileName(){ return "BroadcastFilterTree.xml"; } @Override - protected String getDialogTitle(){ + protected final String getDialogTitle(){ return ResourceBundle.getBundle(AwarenessFilter.class.getName()).getString("dialog.broadcast.title"); } @@ -61,6 +80,14 @@ ); } + /** + * Accept or refuse for broadcasting an action generated remotely by a client. + * The local user can determine which actions must be filtered out via the dialog + * displayed by {@code showDialog}. + * + * @param action the action to filter + * @return whether the action can be broadcasted to other client + */ public boolean accept(DiagramEventActionSource action){ /* don't accept if the user didn't select the modality this action was generated from */ switch(action.type){ @@ -110,6 +137,14 @@ return true; } + /** + * Process for broadcasting an action generated remotely by a client. + * The local user can determine which parts of the action can be excluded + * via the dialog displayed by {@code showDialog}. + * + * @param action the action to filter + * @return whether the action can be broadcasted to other client + */ public DiagramEventActionSource process(DiagramEventActionSource action){ /* delete the timestamp if the user decided not to broadcast it */ if(properties.contains("Broadcast Filter.When.Active History"))
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilter.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/awareness/DisplayFilter.java Tue Jul 10 22:39:37 2012 +0100 @@ -28,14 +28,34 @@ import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; - +/** + * A filter for {@code DiagramEventActionSource} generated remotely and to be displayed + * both visually and via a text to speech sinthesizer. The user can select on a dialog + * the cases in which the {@code DiagramEventActionSource} is to be displayed or not. + * User selections are saved persistently using the persistence functionalities of {@code SetProperties}. + * + * @see uk.ac.qmul.eecs.ccmi.checkboxtree.SetProperties + */ public class DisplayFilter extends AwarenessFilter { + /** + * Creates a new instance of this class. Successive calls on this method will return + * the filter already created rather than creating anew one. + * + * @return the instance created + * @throws IOException if I/O problems occurred when retrieving the properties file + */ public static DisplayFilter createInstance() throws IOException{ if(displayFilter == null) displayFilter = new DisplayFilter(); return displayFilter; } + /** + * Returns the unique instance of this class. + * + * @return the unique instance of this class or {@code null} if {@code createInstance} has never + * been called before. + */ public static DisplayFilter getInstance(){ return displayFilter; } @@ -47,12 +67,12 @@ } @Override - protected String getXMLFileName() { + protected final String getXMLFileName() { return "DisplayFilterTree.xml"; } @Override - protected String getDialogTitle(){ + protected final String getDialogTitle(){ return resources.getString("dialog.display.title"); } @@ -61,10 +81,26 @@ super.saveProperties(parentComponent, resources.getString("display.properties.comments")); } + /** + * Returns a string to be uttered by the narrator starting from the action + * that the user is to be made aware of + * + * @param actionSource the action source describing a remote action that the local + * user has to be made aware of + * @return a string to be uttered by a {@code Narrator} + */ public String processForSpeech(DiagramEventActionSource actionSource){ return process(actionSource,"Speech"); } + /** + * Returns a string to be inserted on the awareness panel starting from the action + * that the user is to be made aware of + * + * @param actionSource the action source describing a remote action that the local + * user has to be made aware of + * @return a string to be inserted in the awareness panel + */ public String processForText(DiagramEventActionSource actionSource){ return process(actionSource,"Text"); } @@ -153,7 +189,7 @@ } private static DisplayFilter displayFilter; - ResourceBundle resources; + private ResourceBundle resources; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/filechooser/SpeechFileChooser.java Tue Jul 10 22:39:37 2012 +0100 @@ -157,14 +157,14 @@ * been entered by the user. If not, the dialog won't close and a error will be notified through the narrator */ SpeechOptionPane optionPane = new SpeechOptionPane(resources.getString("dialog.open.title")){ @Override - protected void onClose(JDialog dialog,Object message, JButton source){ + protected void onClose(JDialog dialog, JButton source){ if(source.equals(getOkButton())){ if(fileNameTextField.getText().isEmpty()){ NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_file_name")); return; } } - super.onClose(dialog, message, source); + super.onClose(dialog, source); } };
--- a/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/gui/persistence/PersistenceManager.java Tue Jul 10 22:39:37 2012 +0100 @@ -61,9 +61,10 @@ import uk.ac.qmul.eecs.ccmi.utils.CharEscaper; /** - * The PersistanceManager provides methods saving and retrieving diagrams from an XML + * The PersistanceManager provides methods for saving and retrieving diagrams from an XML * file. Both templates diagrams (prototypes from which actual diagram instances are created - * through cloning) and diagram instances can be saved to a file. + * through cloning) and diagram instances can be saved to a file. The tag name used in the XML + * file can be accessed via the static {@code String} variables of this class. * */ public abstract class PersistenceManager { @@ -121,6 +122,8 @@ * * @param XMLFile the file to read the diagram from * @throws IOException if there are any I/O problems with the file + * + * @return the diagram encoded in {@code XMLFile} */ public static Diagram decodeDiagramTemplate(File XMLFile) throws IOException{ ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); @@ -163,12 +166,13 @@ /** * Encodes a diagram instance into the given output stream. Using output stream - * instead of Writer as it's advised by the StreamResult API - * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html#setOutputStream(java.io.OutputStream) + * instead of {@code Writer} as it's advised by the <i>StreamResult API</i> + * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html * - * @param diagram : the diagram to encode - * @param out : where the diagram will be encoded - * @throws IOException + * @param diagram the diagram to encode + * @param newName the new name of the diagram to encode. This will also be the name of the file + * @param out where the diagram will be encoded + * @throws IOException if there are any I/O problems with the file */ public static void encodeDiagramInstance(Diagram diagram, String newName, OutputStream out) throws IOException{ ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); @@ -277,10 +281,29 @@ diagram.setName(newName); } + /** + * Encodes a diagram instance into the given output stream. Using output stream + * instead of {@code Writer} as it's advised by the <i>StreamResult API</i> + * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html + * + * @param diagram the diagram to encode + * @param out an output stram to the file where the diagram will be encoded + * @throws IOException if there are any I/O problems with the file + */ public static void encodeDiagramInstance(Diagram diagram, OutputStream out) throws IOException{ encodeDiagramInstance(diagram,null,out); } + /** + * Decodes a diagram instance from the given input stream. Using input stream + * instead of {@code Reader} as it's advised by the <i>StreamResult API</i> + * @see http://download.oracle.com/javase/6/docs/api/javax/xml/transform/stream/StreamResult.html + * + * @param in an input stream to the file the diagram is decoded from + * @throws IOException if there are any I/O problems with the file + * + * @return the diagram encoded in the file + */ public static Diagram decodeDiagramInstance(InputStream in) throws IOException { ResourceBundle resources = ResourceBundle.getBundle(PersistenceManager.class.getName()); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/DummyHaptics.java Tue May 29 15:32:19 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -/* - 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.haptics; - -import java.awt.geom.Line2D; -import java.util.BitSet; - -/* - * A dummy implementation of the Haptics interface. All its methods are empty, - * so every call will have no effect whatsoever. - */ -class DummyHaptics implements Haptics { - - public DummyHaptics(){} - - @Override - public int init(int width, int height) { return 0;} - - @Override - public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName) {} - - @Override - public synchronized void removeNode(int nodeHashCode, String diagramName) {} - - @Override - public synchronized void removeEdge(int nodeHashCode, String diagramName){} - - @Override - public synchronized void dispose() {} - - @Override - public void addNewDiagram(String diagramName, boolean switchAfter) {} - - @Override - public synchronized void switchDiagram(String diagramName) { } - - @Override - public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext) {} - - @Override - public synchronized void moveNode(double x, double y, int nodeHashCode, String diagramName) {} - - @Override - public synchronized void addEdge(int nodeHashCode, double[] xs, double[] ys, - BitSet[] adjMatrix, int nodeStart, int stipplePattern, Line2D attractLine, String diagramName) {} - - @Override - public synchronized void updateEdge(int nodeHashCode, double[] xs, - double[] ys, BitSet[] adjMatrix, int nodeStart, Line2D attractLine, String diagramName) {} - - @Override - public synchronized void attractTo(int elementHashCode) {} - - @Override - public void pickUp(int elementHashCode){} - - @Override - public boolean isAlive(){ - return false; - } - - @Override - public void run() {} -}
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Edge.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Edge.java Tue Jul 10 22:39:37 2012 +0100 @@ -50,5 +50,9 @@ public double attractPointX; public double attractPointY; public int nodeStart; + + static final int DOTTED_LINE = 0xF0F0; + static final int DASHED_LINE = 0xAAAA; + static final int SOLID_LINE = 0xFFFF; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Empties.java Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,30 @@ +/* + 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.haptics; + +import java.util.ArrayList; +import java.util.HashMap; + +class Empties { + public static final ArrayList<Node> EMPTY_NODE_LIST = new ArrayList<Node>(0); + public static final ArrayList<Edge> EMPTY_EDGE_LIST = new ArrayList<Edge>(0); + public static final HashMap<Integer,Node> EMPTY_NODE_MAP = new HashMap<Integer,Node>(); + public static final HashMap<Integer,Edge> EMPTY_EDGE_MAP = new HashMap<Integer,Edge>(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/FalconHaptics.java Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,300 @@ +/* + 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.haptics; + +import java.awt.Dimension; +import java.awt.Toolkit; +import java.awt.geom.Line2D; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.ListIterator; + +import uk.ac.qmul.eecs.ccmi.utils.OsDetector; +import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; +import uk.ac.qmul.eecs.ccmi.utils.ResourceFileWriter; + +class FalconHaptics extends Thread implements Haptics { + static Haptics createInstance(HapticListenerThread listener) { + if(OsDetector.isWindows()){ + /* create a directory for the dll distributed with HAPI library */ + String libDir = PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")); + File hapiDir = new File(libDir,"HAPI"); + hapiDir.mkdir(); + + /* try to load .dll's. First copy it in the home/ccmi_editor_data/lib directory */ + String[] dlls = {"HAPI/pthreadVC2.dll","HAPI/FreeImage.dll","HAPI/freeglut.dll", + "HAPI/H3DUtil_vc9.dll","HAPI/HAPI_vc9.dll","FalconHaptics.dll"}; + ResourceFileWriter fileWriter = new ResourceFileWriter(); + for(String dll : dlls){ + URL url = OmniHaptics.class.getResource(dll); + fileWriter.setResource(url); + fileWriter.writeOnDisk(libDir, dll); + String path = fileWriter.getFilePath(); + try{ + if(path == null) + throw new UnsatisfiedLinkError(dll+" missing"); + System.load( path ); + }catch(UnsatisfiedLinkError e){ + System.err.println(e.getMessage()); + e.printStackTrace(); + return null; + } + } + }else{ + return null; + } + + FalconHaptics falcon = new FalconHaptics("FalconHaptics"); + falcon.hapticListener = listener; + /* start up the listener which immediately stops, waiting for commands */ + if(!falcon.hapticListener.isAlive()) + falcon.hapticListener.start(); + /* start up the haptics thread which issues commands from the java to the c++ thread */ + falcon.start(); + synchronized(falcon){ + try { + falcon.wait(); + }catch (InterruptedException ie) { + throw new RuntimeException(ie); // must never happen + } + } + if(falcon.initFailed) + return null; + else + return falcon; + } + + private FalconHaptics(String threadName){ + super(threadName); + + nodes = new HashMap<String,ArrayList<Node>>(); + edges = new HashMap<String,ArrayList<Edge>>(); + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; + + attractTo = 0; + } + + private native int initFalcon(int width, int height) throws IOException ; + + @Override + public void addNewDiagram(String diagramName) { + ArrayList<Node> cNodes = new ArrayList<Node>(30); + ArrayList<Edge> cEdges = new ArrayList<Edge>(30); + nodes.put(diagramName, cNodes); + edges.put(diagramName, cEdges); + + synchronized(this){ + currentNodes = cNodes; + currentEdges = cEdges; + collectionsChanged = true; + } + } + + @Override + public synchronized void switchDiagram(String diagramName) { + if(!nodes.containsKey(diagramName)) + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName); + + currentNodes = nodes.get(diagramName); + currentEdges = edges.get(diagramName); + collectionsChanged = true; + } + + @Override + public synchronized void removeDiagram(String diagramNameToRemove, + String diagramNameOfNext) { + if(!nodes.containsKey(diagramNameToRemove)) + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove); + + nodes.remove(diagramNameToRemove); + edges.remove(diagramNameToRemove); + + if(diagramNameOfNext == null){ + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; + }else { + if(!nodes.containsKey(diagramNameOfNext)) + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameOfNext); + currentNodes = nodes.get(diagramNameOfNext); + currentEdges = edges.get(diagramNameOfNext); + } + collectionsChanged = true; + } + + @Override + public synchronized void addNode(double x, double y, int nodeHashCode, String diagramName) { + Node n = new Node(x,y,nodeHashCode, nodeHashCode); + if(diagramName == null){ + currentNodes.add(n); + }else{ + nodes.get(diagramName).add(n); + } + collectionsChanged = true; + } + + @Override + public synchronized void removeNode(int nodeHashCode, String diagramName) { + ListIterator<Node> itr = (diagramName == null) ? currentNodes.listIterator() : nodes.get(diagramName).listIterator(); + while(itr.hasNext()){ + Node n = itr.next(); + if(n.diagramId == nodeHashCode){ + itr.remove(); + collectionsChanged = true; + break; + } + } + } + + @Override + public synchronized void moveNode(double x, double y, int nodeHashCode, + String diagramName) { + ArrayList<Node> iterationList = (diagramName == null) ? currentNodes : nodes.get(diagramName); + for(Node n : iterationList){ + if(n.diagramId == nodeHashCode){ + n.x = x; + n.y = y; + collectionsChanged = true; + break; + } + } + } + + @Override + public synchronized void addEdge(int edgeHashCode, double[] xs, double[] ys, + BitSet[] adjMatrix, int nodeStart, int stipplePattern, + Line2D attractLine, String diagramName) { + /* find the mid point of the line of attraction */ + double pX = Math.min(attractLine.getX1(), attractLine.getX2()); + double pY = Math.min(attractLine.getY1(), attractLine.getY2()); + pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; + pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; + + Edge e = new Edge(edgeHashCode,edgeHashCode, xs, ys, adjMatrix, nodeStart, stipplePattern, pX, pY); + /* add the edge reference to the edges list */ + if(diagramName == null){ + currentEdges.add(e); + }else{ + edges.get(diagramName).add(e); + } + collectionsChanged = true; + } + + @Override + public synchronized void updateEdge(int edgeHashCode, double[] xs, double[] ys, + BitSet[] adjMatrix, int nodeStart, Line2D attractLine, + String diagramName) { + + for(Edge e : currentEdges){ + if(e.diagramId == edgeHashCode){ + e.xs = xs; + e.ys = ys; + e.size = xs.length; + e.adjMatrix = adjMatrix; + e.nodeStart = nodeStart; + // find the mid point of the line of attraction + double pX = Math.min(attractLine.getX1(), attractLine.getX2()); + double pY = Math.min(attractLine.getY1(), attractLine.getY2()); + pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; + pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; + e.attractPointX = pX; + e.attractPointY = pY; + } + } + collectionsChanged = true; + } + + @Override + public synchronized void removeEdge(int edgeHashCode, String diagramName) { + ListIterator<Edge> itr = (diagramName == null) ? currentEdges.listIterator() : edges.get(diagramName).listIterator(); + while(itr.hasNext()){ + Edge e = itr.next(); + if(e.diagramId == edgeHashCode){ + itr.remove(); + collectionsChanged = true; + break; + } + } + collectionsChanged = true; + } + + @Override + public synchronized void attractTo(int elementHashCode) { + attractTo = elementHashCode; + } + + @Override + public synchronized void pickUp(int elementHashCode) { + pickUp = true; + } + + @Override + public void setVisible(boolean visible) { + // falcon haptics window cannot be made invisible + } + + @Override + public synchronized void dispose(){ + shutdown = true; + /* wait for the haptic thread to shut down */ + try { + wait(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void run() { + /* get the screen size which will be passed to init methos in order to set up a window + * for the haptic with the same size as the swing one + */ + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + + int screenWidth = (int)screenSize.getWidth(); + int screenHeight = (int)screenSize.getHeight(); + currentNodes.size(); + try { + initFalcon(screenWidth * 5 / 8,screenHeight * 5 / 8); + } catch (IOException e) { + throw new RuntimeException();// OMNI haptic device doesn't cause any exception + } + } + + /* the diagram currently selected */ + private ArrayList<Node> currentNodes; + private ArrayList<Edge> currentEdges; + + /* maps with all the diagrams in the editor */ + private HashMap<String,ArrayList<Node>> nodes; + private HashMap<String,ArrayList<Edge>> edges; + + private HapticListenerThread hapticListener; + private boolean initFailed; + private boolean collectionsChanged; + private boolean pickUp; + private int attractTo; + private boolean shutdown; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListSupport.java Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,129 @@ +/* + 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.haptics; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/* used by MouseHaptics */ +class HapticListSupport { + + public HapticListSupport(){ + nodes = new HashMap<String,List<Node>>(); + edges = new HashMap<String,List<Edge>>(); + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; + } + + public void addNewDiagram(String diagramName) { + currentNodes = new ArrayList<Node>(30); + nodes.put(diagramName,currentNodes); + currentEdges = new ArrayList<Edge>(30); + edges.put(diagramName,currentEdges); + } + + public void switchDiagram(String diagramName) { + currentNodes = nodes.get(diagramName); + currentEdges = edges.get(diagramName); + } + + public void removeDiagram(String diagramNameToRemove, + String diagramNameOfNext) { + nodes.remove(diagramNameToRemove); + edges.remove(diagramNameToRemove); + currentNodes = nodes.get(diagramNameOfNext); + currentEdges = edges.get(diagramNameOfNext); + if(currentNodes == null){ + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; + } + } + + public void addNode(Node n, String diagramName){ + if(diagramName == null) + currentNodes.add(n); + else + nodes.get(diagramName).add(n); + } + + public Node removeNode(int nodeHashCode, String diagramName) { + List<Node> list = (diagramName == null) ? currentNodes : nodes.get(diagramName); + for(Node n : list){ + if(n.diagramId == nodeHashCode){ + list.remove(n); + return n; + } + } + return null; + } + + public Node getNode(int nodeHashCode, String diagramName){ + List<Node> list = (diagramName == null) ? currentNodes : nodes.get(diagramName); + for(Node n : list){ + if(n.diagramId == nodeHashCode){ + return n; + } + } + return null; + } + + public void addEdge(Edge e, String diagramName){ + if(diagramName == null) + currentEdges.add(e); + else + edges.get(diagramName).add(e); + } + + public Edge removeEdge(int edgeHashCode, String diagramName){ + List<Edge> list = (diagramName == null) ? currentEdges : edges.get(diagramName); + for(Edge e : list){ + if(e.diagramId == edgeHashCode){ + list.remove(e); + return e; + } + } + return null; + } + + public Edge getEdge(int edgeHashCode, String diagramName){ + List<Edge> list = (diagramName == null) ? currentEdges : edges.get(diagramName); + for(Edge e : list){ + if(e.diagramId == edgeHashCode){ + return e; + } + } + return null; + } + + public List<Node> getCurrentNodes(){ + return currentNodes; + } + + public List<Edge> getCurrentEdges(){ + return currentEdges; + } + + private Map<String,List<Node>> nodes; + private Map<String,List<Edge>> edges; + private List<Node> currentNodes; + private List<Edge> currentEdges; +}
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListener.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListener.java Tue Jul 10 22:39:37 2012 +0100 @@ -15,55 +15,13 @@ 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.haptics; /** - * - * An HapticListeners is a thread listening to commands sent by an haptic device through - * shared memory and executes them. The piece of software that manages the haptic device - * runs on its own thread, hence the need for a inter-thread communication. Listening - * to the haptic device cannot be done by the event dispatching thread as this - * would prevent the user from using the graphical user interface, therefore a further thread is needed - * for this task. - * HapticListener is an abstract class which must be extended by implementing the - * {@link #executeCommand(HapticListenerCommand, int, double, double, double, double)} method. - * + * An interface for executing commands sent from an haptic device. */ -public abstract class HapticListener extends Thread { - - public HapticListener() { - super("Haptic Listener"); - mustSayGoodBye = false; - } - - @Override - public final void run(){ - synchronized(this){ - while(!mustSayGoodBye){ - try { - wait(); - executeCommand(HapticListenerCommand.fromChar(cmd), diagramElementID, x, y, startX, startY); - notify(); // notify the command has been executed - } catch (InterruptedException e) { - dispose(); - } - } - } - } - - public abstract void executeCommand(HapticListenerCommand cmd, int ID, double x, double y, double startX, double startY); - - public void dispose(){ - mustSayGoodBye = true; - } - - private char cmd; - private int diagramElementID; - private boolean mustSayGoodBye; - private double x; - private double y; - private double startX; - private double startY; +public interface HapticListener { + public void executeCommand(HapticListenerCommand cmd, int ID, double x, double y, double startX, double startY); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread.java Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,72 @@ +/* + 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.haptics; + +/** + * + * An HapticListenerThread is a thread listening to commands sent by an haptic device through + * shared memory and executes them. The piece of software that manages the haptic device + * runs on its own thread, hence the need for a inter-thread communication. Listening + * to the haptic device cannot be done by the event dispatching thread as this + * would prevent the user from using the graphical user interface, therefore a further thread is needed + * for this task. + * HapticListener is an abstract class which must be extended by implementing the + * {@link #executeCommand(HapticListenerCommand, int, double, double, double, double)} method. + * + */ +public abstract class HapticListenerThread extends Thread implements HapticListener { + + public HapticListenerThread() { + super("Haptic Listener"); + mustSayGoodBye = false; + } + + @Override + public final void run(){ + synchronized(this){ + while(!mustSayGoodBye){ + try { + wait(); + executeCommand(HapticListenerCommand.fromChar(cmd), diagramElementID, x, y, startX, startY); + notify(); // notify the command has been executed + } catch (InterruptedException e) { + dispose(); + } + } + } + } + + @Override + public abstract void executeCommand(HapticListenerCommand cmd, int ID, double x, double y, double startX, double startY); + + public abstract HapticListener getNonRunnableListener(); + + public void dispose(){ + mustSayGoodBye = true; + } + + private char cmd; + private int diagramElementID; + private boolean mustSayGoodBye; + private double x; + private double y; + private double startX; + private double startY; +}
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Haptics.java Tue Jul 10 22:39:37 2012 +0100 @@ -20,14 +20,16 @@ package uk.ac.qmul.eecs.ccmi.haptics; import java.awt.geom.Line2D; -import java.io.IOException; import java.util.BitSet; +/** + * + * An interface for rendering a visual diagram hapticly. + * + */ public interface Haptics extends Runnable{ - public int init(int width, int height) throws IOException; - - public void addNewDiagram(String diagramName, boolean switchAfter); + public void addNewDiagram(String diagramName); public void switchDiagram(String diagramName); @@ -53,6 +55,8 @@ public void pickUp(int elementHashCode); public boolean isAlive(); + + public void setVisible(boolean visible); public void dispose();
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/HapticsFactory.java Tue Jul 10 22:39:37 2012 +0100 @@ -19,6 +19,8 @@ package uk.ac.qmul.eecs.ccmi.haptics; +import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; + /** * * Creates an instance of a class implementing the Haptics interface. There can only be one instance of such @@ -33,12 +35,37 @@ * * @param listener an haptic commands listener to link to the {@code Haptics} instance. */ - public static void createInstance(HapticListener listener) { + public static void createInstance(HapticListenerThread listener) { if(hapticsInstance != null) throw new IllegalStateException("create instance must be called once only"); - hapticsInstance = OmniHaptics.createInstance(listener); + + /* use the preferences service to pick the right device to use (the one the user selected * + * in their preferences). This allows to avoid loading the .dll file for the other device * + * that the user doesn't need, which would entail a waste of memory and, more important * + * a conflict between function names. */ + String defaultDevice = PreferencesService.getInstance().get("haptic_device", TABLET_ID); + + if(PHANTOM_ID.equals(defaultDevice)){ + /* OmniHaptics first */ + hapticsInstance = OmniHaptics.createInstance(listener); + if(hapticsInstance != null)//OmniHaptics instance successfully created: return + return; + }else if(FALCON_ID.equals(defaultDevice)){ + /* Falcon first */ + hapticsInstance = FalconHaptics.createInstance(listener); + if(hapticsInstance != null){ //FalconHaptics instance successfully created: return + return; + } + } + + /* no devices available, stop the listener and go for the default */ + if(listener.isAlive()){ + listener.interrupt(); + } + hapticsInstance = MouseHaptics.createInstance(listener.getNonRunnableListener()); if(hapticsInstance == null) - hapticsInstance = new DummyHaptics(); + throw new RuntimeException(); + } public static Haptics getInstance(){ @@ -49,4 +76,7 @@ } private static Haptics hapticsInstance; + public static final String PHANTOM_ID = "Phantom Omni"; + public static final String FALCON_ID = "Falcon"; + public static final String TABLET_ID = "Tablet/Mouse"; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/MouseHaptics.java Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,452 @@ +/* + 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.haptics; + +import java.awt.AWTException; +import java.awt.BasicStroke; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Robot; +import java.awt.Stroke; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.io.IOException; +import java.util.BitSet; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +import uk.ac.qmul.eecs.ccmi.gui.DiagramPanel; +import uk.ac.qmul.eecs.ccmi.main.DiagramEditorApp; +import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; + +@SuppressWarnings("serial") +class MouseHaptics extends JPanel implements Haptics, KeyListener, MouseListener, MouseMotionListener { + static Haptics createInstance(HapticListener listener){ + MouseHaptics instance = new MouseHaptics(listener); + try{ + instance.initMouseHaptics(); + return instance; + }catch(IOException ioe){ + return null; + } + } + + private MouseHaptics(HapticListener listener){ + this.listener = listener; + solidStroke = new BasicStroke(EDGE_THICKNESS); + dottedStroke = new BasicStroke(EDGE_THICKNESS, + BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, + 0.0f, + new float[]{1.0f,30.0f}, + 0.0f); + dashedStroke = new BasicStroke(EDGE_THICKNESS, + BasicStroke.CAP_ROUND, + BasicStroke.JOIN_ROUND, + 0.0f, + new float[]{80.0f,50.0f}, + 0.0f); + } + + @Override + public void paintComponent(Graphics g){ + super.paintComponent(g); + + Graphics2D g2 = (Graphics2D)g; + + Stroke oldStroke = g2.getStroke(); + // draw edges + g2.setColor(EDGE_COLOR); + for(Edge e : listSupport.getCurrentEdges()){ + switch(e.stipplePattern){ + case Edge.SOLID_LINE : + g2.setStroke(solidStroke);break; + case Edge.DOTTED_LINE : + g2.setStroke(dottedStroke);break; + case Edge.DASHED_LINE : + g2.setStroke(dashedStroke);break; + } + for(int i=0; i< e.adjMatrix.length; i++){ + BitSet adj = e.adjMatrix[i]; + for (int j = adj.nextSetBit(0); j >= 0; j = adj.nextSetBit(j+1)) { + g2.drawLine((int)e.xs[i], (int)e.ys[i], (int)e.xs[j], (int)e.ys[j]); + } + } + } + + g2.setStroke(oldStroke); + // draw nodes + g2.setColor(NODE_COLOR); + for(Node n : listSupport.getCurrentNodes()){ + g2.fillOval((int)(n.x - NODE_DIAMETER/2), (int)(n.y - NODE_DIAMETER/2), NODE_DIAMETER, NODE_DIAMETER); + } + } + + @Override + public void run() {} + + void initMouseHaptics() throws IOException { + hapticFrame = new JFrame("Haptic Frame"); + try { + robot = new Robot(); + } catch (AWTException e) { + throw new IOException(e); + } + /* this is necessary to make the window screen size. it doens't work with the default layout */ + setLayout(new BorderLayout()); + + setBackground(Color.black); + + listSupport = new HapticListSupport(); + + hapticFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + hapticFrame.addWindowFocusListener(new WindowFocusListener(){ + @Override + public void windowGainedFocus(WindowEvent arg0) { + NarratorFactory.getInstance().speak("Haptic window Focused"); + } + + @Override + public void windowLostFocus(WindowEvent arg0) {} + }); + hapticFrame.setContentPane(this); + hapticFrame.setExtendedState(JFrame.MAXIMIZED_BOTH); + hapticFrame.setUndecorated(true); + hapticFrame.addKeyListener(this); + addMouseMotionListener(this); + addMouseListener(this); + hapticFrame.pack(); + } + + @Override + public void addNewDiagram(String diagramName) { + listSupport.addNewDiagram(diagramName); + repaint(); + } + + @Override + public void switchDiagram(String diagramName) { + listSupport.switchDiagram(diagramName); + repaint(); + } + + @Override + public void removeDiagram(String diagramNameToRemove, + String diagramNameOfNext) { + listSupport.removeDiagram(diagramNameToRemove, diagramNameOfNext); + repaint(); + } + + @Override + public void addNode(double x, double y, int nodeHashCode, String diagramName) { + Node n = new Node(x,y,nodeHashCode, 0); + listSupport.addNode(n,diagramName); + repaint(); + } + + @Override + public void removeNode(int nodeHashCode, String diagramName) { + listSupport.removeNode(nodeHashCode, diagramName); + repaint(); + } + + @Override + public void moveNode(double x, double y, int nodeHashCode, + String diagramName) { + Node n = listSupport.getNode(nodeHashCode, diagramName); + n.x = x; + n.y = y; + repaint(); + } + + @Override + public void addEdge(int edgeHashCode, double[] xs, double[] ys, + BitSet[] adjMatrix, int nodeStart, int stipplePattern, + Line2D attractLine, String diagramName) { + // find the mid point of the line of attraction + double pX = Math.min(attractLine.getX1(), attractLine.getX2()); + double pY = Math.min(attractLine.getY1(), attractLine.getY2()); + pX += Math.abs(attractLine.getX1() - attractLine.getX2())/2; + pY += Math.abs(attractLine.getY1() - attractLine.getY2())/2; + Edge e = new Edge(edgeHashCode,0, xs, ys, + adjMatrix, nodeStart, stipplePattern, + pX, pY); + listSupport.addEdge(e, diagramName); + repaint(); + } + + @Override + public void updateEdge(int edgeHashCode, double[] xs, double[] ys, + BitSet[] adjMatrix, int nodeStart, Line2D attractLine, + String diagramName) { + Edge e = listSupport.getEdge(edgeHashCode, diagramName); + e.xs = xs; + e.ys = ys; + e.adjMatrix = adjMatrix; + e.nodeStart = nodeStart; + repaint(); + } + + @Override + public void removeEdge(int edgeHashCode, String diagramName) { + listSupport.removeEdge(edgeHashCode, diagramName); + repaint(); + } + + @Override + public void attractTo(int elementHashCode) { + + } + + @Override + public void pickUp(int elementHashCode) { + // needed to change the status of the haptic device. not needed here + } + + @Override + public boolean isAlive() { + return false; + } + + @Override + public void setVisible(boolean visible){ + hapticFrame.setVisible(visible); + } + + @Override + public void dispose() { + // no resources to free up + } + + @Override + public void keyPressed(KeyEvent evt) { + DiagramPanel panel = DiagramEditorApp.getFrame().getActiveTab(); + if(panel == null) + DiagramEditorApp.getFrame().editorTabbedPane.dispatchEvent(evt); + else + panel.getTree().dispatchEvent(evt); + } + + @Override + public void keyReleased(KeyEvent evt) { + keyPressed(evt); + } + + @Override + public void keyTyped(KeyEvent evt) { + keyPressed(evt); + } + + @Override + public void mouseDragged(MouseEvent evt) { + /* priority to nodes: check if the mouse pointer is inside a circle centred on * + * the node and with radius equal to NODE_RADIUS. If the mouse pointer is within * + * the radius of two or more nodes then the closest is picked up. The command is * + * executed once when the node is touched. In order have it executed again the * + * used must get away from it and hover above it again */ + Point2D p = evt.getPoint(); + if(elementPickedUp){ + draggedDistance += evt.getPoint().distance(lastDragPoint); + if( draggedDistance > CHAIN_SOUND_INTERVAL){ + listener.executeCommand(HapticListenerCommand.PLAY_SOUND, 3, 0,0,0,0); + draggedDistance = 0; + } + lastDragPoint = evt.getPoint(); + } + + Node hoveredNode = null; + for(Node n : listSupport.getCurrentNodes()){ + double distance = p.distance(n.x, n.y); + if( distance < NODE_HOVER_DIST){ + if(hoveredNode == null || distance < p.distance(hoveredNode.x,hoveredNode.y) ){ + hoveredNode = n; + } + } + } + if(hoveredNode != null && hoveredNode != lastTouchedNode){ + listener.executeCommand(HapticListenerCommand.PLAY_ELEMENT_SPEECH, hoveredNode.diagramId, 0, 0, 0, 0); + lastTouchedNode = hoveredNode; + lastTouchedEdge = null; + return; + } + lastTouchedNode = hoveredNode; + /* if hovering inside a node neither send the command nor take edges into account */ + if(hoveredNode != null) + return; + + /* if no node is being touched, check the edges out. */ + Edge hoveredEdge = null; + Line2D line = new Line2D.Double(); + /* look at all edges */ + for(Edge e : listSupport.getCurrentEdges()){ + /* look at all edge's lines */ + for(int i=0; i< e.adjMatrix.length; i++){ + BitSet adj = e.adjMatrix[i]; + for (int j = adj.nextSetBit(0); j >= 0; j = adj.nextSetBit(j+1)) { + line.setLine(e.xs[i], e.ys[i], e.xs[j], e.ys[j]); + if(lastTouchedEdge != e && line.ptSegDist(p)<EDGE_HOVER_DIST){ + hoveredEdge = e; + listener.executeCommand(HapticListenerCommand.PLAY_ELEMENT_SPEECH, hoveredEdge.diagramId, 0, 0, 0, 0); + lastTouchedEdge = hoveredEdge; + return; + } + } + } + } + lastTouchedEdge = hoveredEdge; + } + + @Override + public void mouseMoved(MouseEvent evt) { + /* right click on a graphic tablet used as mouse has the effect of nullifying the * + * dragging. That is there is no right click but rather it's like if you untouch * + * the tablet. In order to address this a robot is used in order to re-leftclick each time * + * the right click is pressed. In this way we assure that the left click is always held * + * and therefore the mouse is always dragging rather than moving */ + if(mustReclick){ + mustReclick = false; + reclickedAfterMove = true; // this is to make this.mousePressed() have no effect, when it's the robot clicking + robot.mousePress(InputEvent.BUTTON1_MASK); + } + } + + @Override + public void mousePressed(MouseEvent evt) { + /* by clicking on the object, its name is stated by the TTS. * + * Much as what happens when hovering on it with the button pressed */ + if(evt.getButton() == MouseEvent.BUTTON1){ + if(reclickedAfterMove){ + reclickedAfterMove = false; + return; + } + lastTouchedEdge = null; // these two fields are used with dragging to avoid repeating + lastTouchedNode = null; + + mouseDragged(evt);//left clicking on an object is like to drag on it + return; + } + /* button 3 (right click) is for moving the objects (picking up and dropping) */ + if(evt.getButton() != MouseEvent.BUTTON3) + return; + if(!secondClick){ // clicked for the first time: pick up the node or edge + Point2D p = evt.getPoint(); + Node hoveredNode = null; + for(Node n : listSupport.getCurrentNodes()){ + double distance = p.distance(n.x, n.y); + if( distance < NODE_HOVER_DIST){ + if(hoveredNode == null || distance < p.distance(hoveredNode.x,hoveredNode.y) ){ + hoveredNode = n; + } + } + } + if(hoveredNode != null){ // clicked on a node + listener.executeCommand(HapticListenerCommand.PICK_UP, hoveredNode.diagramId, 0, 0, 0, 0); + secondClick = true; + startX = evt.getX(); + startY = evt.getY(); + pickedUpElementId = hoveredNode.diagramId; + mustReclick = true; + /* sets the variables for the chain sound when dragging the element around */ + elementPickedUp = true; + lastDragPoint = evt.getPoint(); + return; + } + /* if no node is being touched, check the edges out. */ + Line2D line = new Line2D.Double(); + /* look at all edges */ + for(Edge e : listSupport.getCurrentEdges()){ + /* look at all edge's lines */ + for(int i=0; i< e.adjMatrix.length; i++){ + BitSet adj = e.adjMatrix[i]; + for (int j = adj.nextSetBit(0); j >= 0; j = adj.nextSetBit(j+1)) { + line.setLine(e.xs[i], e.ys[i], e.xs[j], e.ys[j]); + if(/*lastTouchedEdge != e && */line.ptSegDist(p)<EDGE_HOVER_DIST){ + listener.executeCommand(HapticListenerCommand.PICK_UP, e.diagramId, 0, 0, 0, 0); + secondClick = true; + startX = evt.getX(); + startY = evt.getY(); + pickedUpElementId = e.diagramId; + mustReclick = true; + /* sets the variables for the chain sound when dragging the element around */ + elementPickedUp = true; + lastDragPoint = evt.getPoint(); + return; + } + } + } + } + }else{ + listener.executeCommand(HapticListenerCommand.MOVE, pickedUpElementId, evt.getX(), evt.getY(), startX, startY); + elementPickedUp = false; + secondClick = false; + } + mustReclick = true; + } + + @Override + public void mouseEntered(MouseEvent evt) {} + + @Override + public void mouseExited(MouseEvent evt) {} + + @Override + public void mouseClicked(MouseEvent evt) {} + + @Override + public void mouseReleased(MouseEvent evt) {} + + private HapticListener listener; + private JFrame hapticFrame; + private HapticListSupport listSupport; + private Stroke solidStroke; + private Stroke dashedStroke; + private Stroke dottedStroke; + private Node lastTouchedNode; + private Edge lastTouchedEdge; + private Robot robot; + private boolean mustReclick; + private boolean reclickedAfterMove; + private boolean secondClick; + private boolean elementPickedUp; + private Point2D lastDragPoint; + private double draggedDistance; + private int startX; + private int startY; + private int pickedUpElementId; + private static final Color EDGE_COLOR = Color.RED; + private static final Color NODE_COLOR = Color.WHITE; + private static final int EDGE_THICKNESS = 26;//2; + private static final int NODE_DIAMETER = 50;//10; + private static final int NODE_HOVER_DIST = 25; + private static final int EDGE_HOVER_DIST = 13; + private static final double CHAIN_SOUND_INTERVAL = 150; + +}
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/Node.java Tue Jul 10 22:39:37 2012 +0100 @@ -38,11 +38,7 @@ public double x; public double y; - /** - * the id on the diagram "id space". it corresponds to the hash code of the - * diagram nodes - * @see : uk.ac.eecs.qmul.ccmi.components.Node - */ + /* the id on the diagram "id space". it corresponds to the hash code of the diagram nodes */ public int diagramId; // not shared with the haptic thread public int hapticId; public ArrayList<Edge> edges;
--- a/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/haptics/OmniHaptics.java Tue Jul 10 22:39:37 2012 +0100 @@ -41,23 +41,24 @@ */ class OmniHaptics extends Thread implements Haptics { - static Haptics createInstance(HapticListener listener) { + static Haptics createInstance(HapticListenerThread listener) { if(listener == null) throw new IllegalArgumentException("listener cannot be null"); if(OsDetector.isWindows()){ /* try to load .dll. First copy it in the home/ccmi_editor_data/lib directory */ - URL url = OmniHaptics.class.getResource("Haptics.dll"); + URL url = OmniHaptics.class.getResource("OmniHaptics.dll"); ResourceFileWriter fileWriter = new ResourceFileWriter(url); - fileWriter.writeToDisk( + fileWriter.writeOnDisk( PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")), - "Haptics.dll"); + "OmniHaptics.dll"); String path = fileWriter.getFilePath(); - if(path == null) - return null; try{ + if(path == null) + throw new UnsatisfiedLinkError("OmniHaptics.dll missing"); System.load( path ); }catch(UnsatisfiedLinkError e){ + e.printStackTrace(); return null; } }else{ @@ -80,11 +81,12 @@ } } if(omniHaptics.hapticInitFailed){ - /* the initialization has failed, the haptic thread is about to die */ - while(!omniHaptics.hapticListener.isInterrupted()){ - omniHaptics.hapticListener.interrupt(); - } - omniHaptics.hapticListener = null; //leave the listener to the GC + /* the initialization has failed, the haptic thread is about to die */ + /* don't kill the listener as initialization will be tried on other devices */ +// while(!omniHaptics.hapticListener.isInterrupted()){ +// omniHaptics.hapticListener.interrupt(); +// } +// omniHaptics.hapticListener = null; //leave the listener to the GC return null; }else{ return omniHaptics; @@ -110,37 +112,34 @@ hapticInitFailed = false; nodes = new HashMap<String,ArrayList<Node>>(); edges = new HashMap<String,ArrayList<Edge>>(); - currentNodes = EMPTY_NODE_LIST; - currentEdges = EMPTY_EDGE_LIST; + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; nodesMaps = new HashMap<String,HashMap<Integer,Node>>(); edgesMaps = new HashMap<String,HashMap<Integer,Edge>>(); - currentNodesMap = EMPTY_NODE_MAP; - currentEdgesMap = EMPTY_EDGE_MAP; + currentNodesMap = Empties.EMPTY_NODE_MAP; + currentEdgesMap = Empties.EMPTY_EDGE_MAP; } - @Override - public native int init(int width, int height) throws IOException; + public native int initOmni(int width, int height) throws IOException; @Override - public void addNewDiagram(String name, boolean switchAfter){ + public void addNewDiagram(String diagramName){ ArrayList<Node> cNodes = new ArrayList<Node>(30); ArrayList<Edge> cEdges = new ArrayList<Edge>(30); - nodes.put(name, cNodes); - edges.put(name, cEdges); + nodes.put(diagramName, cNodes); + edges.put(diagramName, cEdges); HashMap<Integer,Node> cNodesMap = new HashMap<Integer,Node>(); HashMap<Integer,Edge> cEdgesMap = new HashMap<Integer,Edge>(); - nodesMaps.put(name, cNodesMap); - edgesMaps.put(name, cEdgesMap); + nodesMaps.put(diagramName, cNodesMap); + edgesMaps.put(diagramName, cEdgesMap); - if(switchAfter){ - synchronized(this){ - currentNodes = cNodes; - currentEdges = cEdges; - currentNodesMap = cNodesMap; - currentEdgesMap = cEdgesMap; - } + synchronized(this){ + currentNodes = cNodes; + currentEdges = cEdges; + currentNodesMap = cNodesMap; + currentEdgesMap = cEdgesMap; } } @@ -148,7 +147,7 @@ public synchronized void switchDiagram(String diagramName){ // check nodes only, as the edges and nodes maps are strongly coupled if(!nodes.containsKey(diagramName)) - throw new IllegalArgumentException("Diagram " + diagramName + " not present among the current ones"); + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramName); currentNodes = nodes.get(diagramName); currentEdges = edges.get(diagramName); @@ -159,20 +158,20 @@ @Override public synchronized void removeDiagram(String diagramNameToRemove, String diagramNameNext){ if(!nodes.containsKey(diagramNameToRemove)) - throw new IllegalArgumentException("Id " + diagramNameToRemove + " not present aong the current ones"); + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameToRemove); nodes.remove(diagramNameToRemove); edges.remove(diagramNameToRemove); nodesMaps.remove(diagramNameToRemove); edgesMaps.remove(diagramNameToRemove); if(diagramNameNext == null){ - currentNodes = EMPTY_NODE_LIST; - currentEdges = EMPTY_EDGE_LIST; - currentNodesMap = EMPTY_NODE_MAP; - currentEdgesMap = EMPTY_EDGE_MAP; + currentNodes = Empties.EMPTY_NODE_LIST; + currentEdges = Empties.EMPTY_EDGE_LIST; + currentNodesMap = Empties.EMPTY_NODE_MAP; + currentEdgesMap = Empties.EMPTY_EDGE_MAP; }else{ if(!nodes.containsKey(diagramNameNext)) - throw new IllegalArgumentException("Id " + diagramNameNext + " not present aong the current ones"); + throw new IllegalArgumentException("Diagram not found among added diagrams:" + diagramNameNext); currentNodes = nodes.get(diagramNameNext); currentEdges = edges.get(diagramNameNext); currentNodesMap = nodesMaps.get(diagramNameNext); @@ -239,7 +238,7 @@ for(Node n : iterationList){ if(n.diagramId == nodeHashCode){ n.x = x; - n.y = y; + n.y = y; break; } } @@ -266,12 +265,12 @@ if(diagramName == null){ /* add the edge reference to the Haptic edges list */ currentEdges.add(e); - /* add the edge reference to the haptic edges map */ + /* add the edge reference to the Haptic edges map */ currentEdgesMap.put(currentHapticId, e); }else{ /* add the edge reference to the Haptic edges list */ edges.get(diagramName).add(e); - /* add the edge reference to the haptic edges map */ + /* add the edge reference to the Haptic edges map */ edgesMaps.get(diagramName).put(currentHapticId, e); } } @@ -366,6 +365,11 @@ } @Override + public void setVisible(boolean visible){ + // not implemented but required by Haptic interface + } + + @Override public synchronized void dispose(){ shutdown = true; /* wait for the haptic thread to shut down */ @@ -379,7 +383,7 @@ @Override public void run() { try { - init(width,height); + initOmni(width,height); } catch (IOException e) { throw new RuntimeException();// OMNI haptic device doesn't cause any exception } @@ -421,11 +425,6 @@ private boolean pickUp; /* currentHapticId is used to share haptic ids between the threads */ private int currentHapticId; - private HapticListener hapticListener; - - private static final ArrayList<Node> EMPTY_NODE_LIST = new ArrayList<Node>(0); - private static final ArrayList<Edge> EMPTY_EDGE_LIST = new ArrayList<Edge>(0); - private static final HashMap<Integer,Node> EMPTY_NODE_MAP = new HashMap<Integer,Node>(); - private static final HashMap<Integer,Edge> EMPTY_EDGE_MAP = new HashMap<Integer,Edge>(); + private HapticListenerThread hapticListener; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.java Tue Jul 10 22:39:37 2012 +0100 @@ -26,7 +26,9 @@ import java.text.MessageFormat; import java.util.ResourceBundle; +import javax.swing.JOptionPane; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import uk.ac.qmul.eecs.ccmi.gui.EditorFrame; import uk.ac.qmul.eecs.ccmi.gui.HapticKindle; @@ -50,29 +52,10 @@ */ public class DiagramEditorApp implements Runnable { - /** - * perform initialization prior to displaying the GUI - * @throws IOException - */ - public void init(String[] args) throws IOException { + /* initialize all the non gui resources */ + private void init() throws IOException { Thread.setDefaultUncaughtExceptionHandler(new CCmIUncaughtExceptionHandler()); - final ResourceBundle resources = ResourceBundle.getBundle(this.getClass().getName()); - /* read command line arguments */ - if(args.length > 1){ - System.out.println(resources.getString("usage")); - System.exit(-1); - } - boolean enableLog = false; - if(args.length == 1){ - if(args[0].equals("-l")){ - enableLog = true; - System.out.println("log enabled"); - }else{ - System.out.println(resources.getString("usage")); - System.exit(-1); - } - } - + final ResourceBundle resources = ResourceBundle.getBundle(DiagramEditorApp.class.getName()); /* create the home directory if it does not exist and store the path into the preferences */ PreferencesService preferences = PreferencesService.getInstance(); String homeDirPath = preferences.get("home", null); @@ -93,26 +76,26 @@ /* create the images directory into the home directory */ File imagesDir = new File(homeDir,resources.getString("dir.images")); - if(mkDir(imagesDir,resources)) - preferences.put("dir.images", imagesDir.getAbsolutePath()); + mkDir(imagesDir,resources); + preferences.put("dir.images", imagesDir.getAbsolutePath()); /* create the diagrams dir into the home directory */ File diagramDir = new File(homeDir,resources.getString("dir.diagrams")); - if(mkDir(diagramDir,resources)) - preferences.put("dir.diagrams", diagramDir.getAbsolutePath()); + mkDir(diagramDir,resources); + preferences.put("dir.diagrams", diagramDir.getAbsolutePath()); /* create the libs directory into he home directory */ File libsDir = new File(homeDir,resources.getString("dir.libs")); - if(mkDir(libsDir,resources)) - preferences.put("dir.libs", libsDir.getAbsolutePath()); + mkDir(libsDir,resources); + preferences.put("dir.libs", libsDir.getAbsolutePath()); /* write the template files included in the software in the template dir, if they don't exist yet */ ResourceFileWriter resourceWriter = new ResourceFileWriter(getClass().getResource("UML Diagram.xml")); - resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"UML Diagram.xml"); - resourceWriter.serResource(getClass().getResource("Tube.xml")); - resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"Tube.xml"); - resourceWriter.serResource(getClass().getResource("Organization Chart.xml")); - resourceWriter.writeToDisk(templateDir.getAbsolutePath(),"Organization Chart.xml"); + resourceWriter.writeOnDisk(templateDir.getAbsolutePath(),"UML Diagram.xml"); + resourceWriter.setResource(getClass().getResource("Tube.xml")); + resourceWriter.writeOnDisk(templateDir.getAbsolutePath(),"Tube.xml"); + resourceWriter.setResource(getClass().getResource("Organization Chart.xml")); + resourceWriter.writeOnDisk(templateDir.getAbsolutePath(),"Organization Chart.xml"); /* read the template files into an array to pass to the EditorFrame instance */ FilenameFilter filter = new FilenameFilter() { @@ -122,30 +105,70 @@ } }; templateFiles = templateDir.listFiles(filter); - - if(enableLog){ - File logDir = new File(homeDir,resources.getString("dir.log")); - mkDir(logDir,resources); + File logDir = new File(homeDir,resources.getString("dir.log")); + mkDir(logDir,resources); + preferences.put("dir.log", logDir.getAbsolutePath()); + + String enableLog = preferences.get("enable_log", "false"); + if(Boolean.parseBoolean(enableLog)){ try{ InteractionLog.enable(logDir.getAbsolutePath()); InteractionLog.log("PROGRAM STARTED"); }catch(IOException ioe){ /* if logging was enabled, the possibility to log is considered inescapable */ - /* do not allow the execution to continue any further */ + /* at launch time: do not allow the execution to continue any further */ throw new RuntimeException(ioe); } } - /* create sound, speech and haptic engines */ + /* create sound, speech engines */ NarratorFactory.createInstance(); SoundFactory.createInstance(); - + } + + /* loads haptic device. If the user is running the software for the first time * + * they're prompted with a dialog to select the device they want to use */ + private void initHaptics(){ + final PreferencesService preferences = PreferencesService.getInstance(); + if(!Boolean.parseBoolean(preferences.get("haptic_device.initialized", "false"))){ + try { + SwingUtilities.invokeAndWait(new Runnable(){ + @Override + public void run() { + try { + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + }catch(Exception e){/* nevermind */} + + String [] hapticDevices = { + HapticsFactory.PHANTOM_ID, + HapticsFactory.FALCON_ID, + HapticsFactory.TABLET_ID}; + String selection = (String)SpeechOptionPane.showSelectionDialog(null, + ResourceBundle.getBundle(DiagramEditorApp.class.getName()).getString("haptics_init.welcome"), + hapticDevices, + hapticDevices[0]); + if(selection == null) + System.exit(0); + preferences.put("haptic_device", selection); + preferences.put("haptic_device.initialized", "true"); + } + }); + }catch(InvocationTargetException ite){ + throw new RuntimeException(ite); + }catch(InterruptedException ie){ + throw new RuntimeException(ie); + } + } + + /* creates the Haptics instance */ HapticsFactory.createInstance(new HapticKindle()); haptics = HapticsFactory.getInstance(); if(haptics.isAlive()) NarratorFactory.getInstance().speakWholeText("Haptic device successfully initialized"); } - + + /* return true if the directory was created, false if it existed before */ private boolean mkDir(File dir,ResourceBundle resources) throws IOException{ boolean created = dir.mkdir(); if(!dir.exists()) @@ -162,8 +185,6 @@ @Override public void run() { editorFrame = new EditorFrame(haptics,templateFiles,backupDirPath,getTemplateEditors()); -// if(hapticKindle != null) -// hapticKindle.setEditorFrame(editorFrame); } public TemplateEditor[] getTemplateEditors(){ @@ -173,32 +194,39 @@ } /** - * @param args + * The main function + * @param args this software accepts no args from the command line */ - public static void main(String[] args) { + public static void main(String[] args) { DiagramEditorApp application = new DiagramEditorApp(); - - try { - application.init(args); + try{ + application.init(); } catch (IOException e) { final String msg = e.getLocalizedMessage(); try { SwingUtilities.invokeAndWait(new Runnable(){ @Override public void run(){ - SpeechOptionPane.showMessageDialog(null, msg); + try { + UIManager.setLookAndFeel( + UIManager.getSystemLookAndFeelClassName()); + }catch(Exception e){/* nevermind */} + JOptionPane.showMessageDialog(null, msg); } }); - System.exit(-1); } catch (InterruptedException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { throw new RuntimeException(ex); } + System.exit(-1); } + application.initHaptics(); + + /* start the application */ try { - SwingUtilities.invokeAndWait(application); + SwingUtilities.invokeAndWait(application); } catch (InvocationTargetException ex) { throw new RuntimeException(ex); } catch (InterruptedException ex) { @@ -206,6 +234,12 @@ } } + /** + * Returns the reference to the unique {@code EditorFrame} instance of the program. + * The main GUI class. + * + * @return an reference to {@code EditorFrame} + */ public static EditorFrame getFrame(){ return editorFrame; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/main/DiagramEditorApp.properties Tue Jul 10 22:39:37 2012 +0100 @@ -7,11 +7,11 @@ dir.images=images dir.libs=libs dir.log=log -usage=Unrecognized option(s)\nUsage : java -jar ccmi.jar [-l] \n -l : enables interaction log dir.error_msg=Could not create the following directory: {0}\u000A\ Please check directory permissions and try again. +haptics_init.welcome=Select the haptic device you want to use #### APPLICATION PREFERENCES #### # server.local_port @@ -20,6 +20,7 @@ # dir.diagrams # dir.images # dir.libs +# dir.log # laf # recent # use_accessible_filechooser @@ -28,3 +29,6 @@ # server.local_port # server.remote_port # second_voice_enabled +# haptic_device +# haptic_device.initialized +# enable_log
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Command.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Command.java Tue Jul 10 22:39:37 2012 +0100 @@ -78,7 +78,7 @@ /** * Utility method to log, through the interaction log, the receipt of a command * @param cmd the received command - * @param action + * @param action a further description of the action that triggered this command */ public static void log(Command cmd, String action){ if(cmd.getName() != Command.Name.LOCAL && cmd.getName() != Command.Name.BEND
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/LockMessage.java Tue Jul 10 22:39:37 2012 +0100 @@ -35,6 +35,12 @@ /** * Creates a lock message for the given diagram and with the given timestamp. + * + * @param name the message name + * @param timestamp the time when the message was issued + * @param diagram the name of the diagram this message refers to + * @param args arguments of the message + * @param source the source that generated this message see {@link DiagramEventActionSource} */ public LockMessage(Name name, long timestamp, String diagram, Object[] args, Object source) { super(timestamp, diagram,source); @@ -45,6 +51,11 @@ /** * Creates a lock message for the given diagram and timestamp of the moment * the message is created. + * + * @param name the message name + * @param diagram the name of the diagram this message refers to + * @param args arguments of the message + * @param source the source that generated this message see {@link DiagramEventActionSource} */ public LockMessage(Name name, String diagram, Object[] args, DiagramEventActionSource source) { super(diagram,source);
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/NetDiagram.java Tue Jul 10 22:39:37 2012 +0100 @@ -423,7 +423,6 @@ * This class wraps an existing diagram into a network diagram. * The network diagrams returns a TreeModelNetWrap and a CollectionModelNetWrap * when the relative getters are called - * @see TreeModelNetWrap, CollectionModelNetWrap * * @param diagram the diagram to wrap * @param connectionManager a connected socket channel
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ProtocolFactory.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ProtocolFactory.java Tue Jul 10 22:39:37 2012 +0100 @@ -19,6 +19,9 @@ package uk.ac.qmul.eecs.ccmi.network; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * The factory class to create {@code Protocol} instances. * @@ -27,4 +30,16 @@ public static Protocol newInstance(){ return new OscProtocol(); } + + /** + * Utility method to check if an address is in a valid IPv4 format. + * @param addr the address to check + * @return {@code true} if {@code addr} is in a valid IPv4 format + */ + public static boolean validateIPAddr(String addr){ + Matcher m = ip.matcher(addr); + return m.matches(); + } + + private static final Pattern ip = Pattern.compile("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b"); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/Server.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/Server.java Tue Jul 10 22:39:37 2012 +0100 @@ -61,7 +61,7 @@ * Create a new server instance. If another instance is already running then it's shut * down before the new instance is created. * - * @return a {@Server} instance. + * @return a {@code Server} instance. */ public static Server createServer(){ if(server != null)
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerConnectionManager.java Tue Jul 10 22:39:37 2012 +0100 @@ -422,7 +422,8 @@ if(channel != localChannel){ /* update the local awareness panel and speech */ awarenessPanelEditor.addTimedRecord(awMsg.getDiagram(), DisplayFilter.getInstance().processForText(processedSource)); - NarratorFactory.getInstance().speakWholeText(DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE); + NarratorFactory.getInstance().speakWholeText( + DisplayFilter.getInstance().processForSpeech(processedSource), Narrator.SECOND_VOICE); } /* broadcast the awareness message to all the clients but one which sent the * * command and the local one to inform them the action has started */
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerLockManager.java Tue Jul 10 22:39:37 2012 +0100 @@ -209,13 +209,13 @@ /** * Request an editing lock for a tree node * - * @param treeNode : the treeNode the caller is trying to lock - * @param lock : the type of lock requested - * @param channel : the channel works as a unique identifier for the clients + * @param treeNode the treeNode the caller is trying to lock + * @param lock the type of lock requested + * @param channel the channel works as a unique identifier for the clients + * @param diagramName the name of the diagram the lock is requested on * @return true if the lock is successfully granted, or false otherwise (because of another client * holding a lock which clashes with this request) */ - public static ServerLockManager singletonLockManager; public boolean requestLock(DiagramTreeNode treeNode, Lock lock, SocketChannel channel,String diagramName){ // System.out.println("lock before request:"+lockStatusDescription(diagramName)+"\n----"); List<LockEntry> locks = locksMap.get(diagramName); @@ -224,7 +224,6 @@ /* there is no entry in the map and one must be created */ locks = new LinkedList<LockEntry>(); locksMap.put(diagramName,locks); - singletonLockManager = this; } /* deleting a node will cause all the attached two-ended edges to * * be deleted, therefore we need to lock all those edges too, before */
--- a/java/src/uk/ac/qmul/eecs/ccmi/network/ServerNotRunningException.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/network/ServerNotRunningException.java Tue Jul 10 22:39:37 2012 +0100 @@ -21,7 +21,6 @@ /** * Exception thrown when the user tries to share a diagram before starting the server - * */ @SuppressWarnings("serial") public class ServerNotRunningException extends DiagramShareException {
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/EdgeDrawSupport.java Tue Jul 10 22:39:37 2012 +0100 @@ -28,6 +28,7 @@ import javax.swing.JLabel; +import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; import uk.ac.qmul.eecs.ccmi.gui.GraphPanel; @@ -37,16 +38,17 @@ */ public abstract class EdgeDrawSupport { /** - Draws a string. - @param g2 the graphics context - @param p an endpoint of the segment along which to - draw the string - @param q the other endpoint of the segment along which to - draw the string - @param s the string to draw - @param center true if the string should be centered - along the segment - * + * Draws a string in proximity of a edge. + * @param g2 the graphics context + * @param p an endpoint of the segment along which to + * draw the string + * @param q the other endpoint of the segment along which to + * draw the string + * @param arrow the arrow head painted on the edge end where the string + * is to be painted + * @param s the string to draw + * @param center true if the string should be centered + * along the segment */ public static void drawString(Graphics2D g2, @@ -69,6 +71,14 @@ g2.translate(-b.getX(), -b.getY()); } + /** + * Draws a graphical marker when the edge has notes associated to it + * @see uk.ac.qmul.eecs.ccmi.diagrammodel.TreeModel#setNotes(DiagramTreeNode, String, Object) + * + * @param g2 the graphics context + * @param p an endpoint of the segment along which to draw the string + * @param q the other endpoint of the segment along which to draw the string + */ public static void drawMarker(Graphics2D g2, Point2D p, Point2D q){ Point2D attach = q; if (p.getX() > q.getX()){ @@ -83,17 +93,9 @@ g2.setColor(oldColor); } - /** + /* Computes the attachment point for drawing a string. - @param g2 the graphics context - @param p an endpoint of the segment along which to - draw the string - @param q the other endpoint of the segment along which to - draw the string - @param b the bounds of the string to draw - @param center true if the string should be centered - along the segment - @return the point at which to draw the string + return the point at which to draw the string */ private static Point2D getAttachmentPoint(Graphics2D g2, Point2D p, Point2D q, ArrowHead arrow, Dimension d, boolean center){ @@ -135,17 +137,9 @@ return new Point2D.Double(attach.getX() + xoff, attach.getY() + yoff); } - /** - Computes the extent of a string that is drawn along a line segment. - @param g2 the graphics context - @param p an endpoint of the segment along which to - draw the string - @param q the other endpoint of the segment along which to - draw the string - @param s the string to draw - @param center true if the string should be centered - along the segment - @return the rectangle enclosing the string + /* + * Computes the extent of a string that is drawn along a line segment. + * The rectangle enclosing the string */ private static Rectangle2D getStringBounds(Graphics2D g2, Point2D p, Point2D q, ArrowHead arrow, String s, boolean center){ @@ -158,6 +152,7 @@ return new Rectangle2D.Double(a.getX(), a.getY(), d.getWidth(), d.getHeight()); } + /* size of the marker when an edge as notes */ private static final int MARKER_SIZE = 7; private static JLabel label = new JLabel(); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ModifierView.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/ModifierView.java Tue Jul 10 22:39:37 2012 +0100 @@ -21,9 +21,9 @@ /** * This immutable class represents the view associated with the modifier type - * associated with it in a {@link NodeProperties} instance. + * associated with it in a {@code NodeProperties} instance. * - * */ + */ public class ModifierView { public ModifierView(boolean underline, boolean bold, boolean italic,
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/MultiLineString.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/MultiLineString.java Tue Jul 10 22:39:37 2012 +0100 @@ -220,7 +220,6 @@ /** Gets the bounding rectangle for this multiline string. - @param g2 the graphics context @return the bounding rectangle (with top left corner (0,0)) */ public Rectangle2D getBounds(){
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/PropertyView.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/PropertyView.java Tue Jul 10 22:39:37 2012 +0100 @@ -24,7 +24,7 @@ /** * This immutable class represents the view associated with the property type - * associated with it in a {@link NodeProperties} instance. + * associated with it in a {@code NodeProperties} instance. * * */ public class PropertyView {
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeEdge.java Tue Jul 10 22:39:37 2012 +0100 @@ -43,6 +43,20 @@ import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; +/** + * An edge rendered as a straight, dotted or dashed line. The edge can have an arrow head + * at each end. Possible arrow heads are : + * <ul> + * <li> Triangle + * <li> Black Triangle + * <li> V + * <li> Half V + * <li> Diamond + * <li> Black Diamond + * <li> Tail + * </ul> + * + */ @SuppressWarnings("serial") public class SimpleShapeEdge extends Edge { @@ -53,16 +67,17 @@ } @Override - public boolean removeNode(DiagramNode n,Object source){ + public boolean removeNode(DiagramNode n, Object source){ currentHeads.remove(n); return super.removeNode(n,source); } + @Override public void draw(Graphics2D g2) { Stroke oldStroke = g2.getStroke(); g2.setStroke(getStyle().getStroke()); - + /* straight line */ if(points.isEmpty()){ Line2D line = getSegment(getNodeAt(0),getNodeAt(1));
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SimpleShapeNode.java Tue Jul 10 22:39:37 2012 +0100 @@ -98,8 +98,8 @@ * node centre from its original position. the next line is to place it back to the right position */ Point2D start = new Point2D.Double(boundsAfterReshape.getCenterX(),boundsAfterReshape.getCenterY()); translateImplementation(start, - boundsBeforeReshape.getCenterX() - boundsAfterReshape.getCenterX(), - boundsBeforeReshape.getCenterY() - boundsAfterReshape.getCenterY()); + boundsBeforeReshape.getCenterX() - boundsAfterReshape.getCenterX(), + boundsBeforeReshape.getCenterY() - boundsAfterReshape.getCenterY()); } super.notifyChange(evt); } @@ -116,9 +116,9 @@ * point is at the same position as before just to keep it more consistent */ Rectangle2D boundsAfterReshape = getBounds(); translateImplementation( - new Point2D.Double(), - boundsBeforeReshape.getX() - boundsAfterReshape.getX(), - boundsBeforeReshape.getY() - boundsAfterReshape.getY() + new Point2D.Double(), + boundsBeforeReshape.getX() - boundsAfterReshape.getX(), + boundsBeforeReshape.getY() - boundsAfterReshape.getY() ); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/SpeechWizardDialog.java Tue Jul 10 22:39:37 2012 +0100 @@ -82,7 +82,7 @@ /** * Enables or disables the finish button. - * @param enabled + * @param enabled {@code true} to enable the button, {@code false} to disable */ public void setFinishButtonEnabled(boolean enabled){ finished = enabled;
--- a/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java Tue Jul 10 22:39:37 2012 +0100 @@ -73,7 +73,7 @@ * how to build a template diagram. A template diagram is a prototype diagram * (containing prototype nodes and edges) which can later on be used for creating instances * of that type of diagram through clonation. The wizard is completely accessible via audio - * as all the content and all focused components names are spoken out by the {@link Narrator} through a text to speech synthesizer. + * as all the content and all focused components names are spoken out by the {@code Narrator} through a text to speech synthesizer. * */ public class Wizard {
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/BeadsAudioPlayer.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/BeadsAudioPlayer.java Tue Jul 10 22:39:37 2012 +0100 @@ -39,6 +39,10 @@ import com.sun.speech.freetts.audio.AudioPlayer; +/** + * An implementation of {@code AudioPlayer} using the {@code Beads} + * library. + */ public class BeadsAudioPlayer implements AudioPlayer { public BeadsAudioPlayer(){ format = new AudioFormat(8000f, 16, 1, true, true);
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/NativeNarrator.java Tue Jul 10 22:39:37 2012 +0100 @@ -40,7 +40,7 @@ URL url = NativeNarrator.class.getResource("WinNarrator.dll"); if(url != null){ ResourceFileWriter fileWriter = new ResourceFileWriter(url); - fileWriter.writeToDisk( + fileWriter.writeOnDisk( PreferencesService.getInstance().get("dir.libs", System.getProperty("java.io.tmpdir")), "CCmIWinNarrator.dll"); String path = fileWriter.getFilePath(); @@ -49,6 +49,7 @@ System.load( path ); nativeLibraryNotFound = false; }catch(UnsatisfiedLinkError e){ + e.printStackTrace(); /* do nothing: nativeLibraryNotFound won't be set to false */ /* which will trigger a NarratorException */ }
--- a/java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/speech/SpeechUtilities.java Tue Jul 10 22:39:37 2012 +0100 @@ -25,9 +25,6 @@ import java.awt.FocusTraversalPolicy; import java.awt.KeyboardFocusManager; import java.awt.event.ActionEvent; -import java.awt.event.FocusAdapter; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; @@ -153,8 +150,9 @@ /** * Returns a {@code speechKeyListener} using first voice (default) - * @param editableComponent - * @return + * @param editableComponent whether this key listener is for a component + * that will be editable + * @return a key listener that utters the letters when typed */ public static KeyListener getSpeechKeyListener(boolean editableComponent){ return getSpeechKeyListener(editableComponent,false); @@ -168,10 +166,6 @@ return checkBoxItemListener; } - public static FocusListener getFocusSpeechListener(){ - return focusListener; - } - public static Action getShutUpAction(){ return shutUpAction; } @@ -393,12 +387,6 @@ } }; - private static final FocusListener focusListener = new FocusAdapter(){ - public void focusGained(FocusEvent evt){ - NarratorFactory.getInstance().speak(getComponentSpeech(evt.getComponent())); - } - }; - @SuppressWarnings("serial") private static final Action shutUpAction = new AbstractAction(){ @Override
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/CharEscaper.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/CharEscaper.java Tue Jul 10 22:39:37 2012 +0100 @@ -29,8 +29,8 @@ * The original {@code String} can be restored by passing the returned {@code String} to * {@link #restoreNewline(String)}. Existing '|' characters will be escaped in order not to miss * them in the restore process. - * @param s the {@String} to remove new line characters from - * @return a {@String} where all new line characters have been replaced by '|' + * @param s the {@code String} to remove new line characters from + * @return a {@code String} where all new line characters have been replaced by '|' */ public static String replaceNewline(String s){ String result = s.replace("|", "'|"); @@ -40,8 +40,8 @@ /** * Restores a {@code String} whose new line characters have been previously replaced by * {@link #replaceNewline(String)}, to the original form. - * @param s - * @return + * @param s the string to be restored + * @return the restored string */ public static String restoreNewline(String s){ String result = s.replaceAll("([^'])\\|","$1\n");
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/ExceptionHandler.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/ExceptionHandler.java Tue Jul 10 22:39:37 2012 +0100 @@ -19,6 +19,16 @@ package uk.ac.qmul.eecs.ccmi.utils; +/** + * An interface implemented by classes that can handle an Exception + * + * + */ public interface ExceptionHandler { + + /** + * Called when an exception occurs + * @param e The occurred exception + */ void handleException(Exception e); }
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/GridBagUtilities.java Tue Jul 10 22:39:37 2012 +0100 @@ -26,8 +26,6 @@ * A Utility class providing static method to quickly arrange components, laid out by * a GridBagLayout, in the following way: one component per row and * either taking the whole column or just the right part of it, if preceded by a label. - * - * */ public class GridBagUtilities { public GridBagUtilities(){ @@ -57,7 +55,8 @@ /** * Equivalent to {@link #label(int)} passing as argument the value previously * set by {@link #setLabelPad(int)} or {@link #DEFAULT_LABEL_PAD} otherwise. - * @return + * + * @return a {@code GridBagConstrains} object to pass to the {@code add} method of {@code JComponent} */ public GridBagConstraints label(){ return label(labelPad); @@ -72,6 +71,12 @@ this.labelPad = labelPad; } + /** + * Provides the {@code GridBagConstrains} for a component placed on the same row + * and on the right of a label. + * + * @return a {@code GridBagConstrains} object to pass to the {@code add} method of {@code JComponent} + */ public GridBagConstraints field(){ GridBagConstraints c; @@ -85,6 +90,11 @@ return c; } + /** + * Provides the {@code GridBagConstrains} for a component placed alone on a row. + * + * @return a {@code GridBagConstrains} object to pass to the {@code add} method of {@code JComponent} + */ public GridBagConstraints all(){ GridBagConstraints c;
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/InteractionLog.java Tue Jul 10 22:39:37 2012 +0100 @@ -33,30 +33,42 @@ * relevant actions. */ public class InteractionLog { + /** + * Enable the logging + * @param logFileDir the path of the directory where the log file will be saved + * @throws IOException if a I/O problem occurs when writing log entry to the log file + */ public static void enable(String logFileDir) throws IOException{ logger.setLevel(Level.FINE); logger.setUseParentHandlers(false); if(fileHandler == null){ fileHandler = new FileHandler(logFileDir+System.getProperty("file.separator")+"interaction%u.log",true); - fileHandler.setFormatter(new CCmILogFormatter()); + fileHandler.setFormatter(new InteractionLogFormatter()); logger.addHandler(fileHandler); + + /* also print the log on the console */ + java.util.logging.ConsoleHandler ch = new java.util.logging.ConsoleHandler(); + ch.setLevel(Level.ALL); + ch.setFormatter(new InteractionLogFormatter()); + logger.addHandler(ch); } - - /* also print the log on the console */ - java.util.logging.ConsoleHandler ch = new java.util.logging.ConsoleHandler(); - ch.setLevel(Level.ALL); - ch.setFormatter(new CCmILogFormatter()); - logger.addHandler(ch); } + /** + * Disable the logging + */ public static void disable(){ logger.setLevel(Level.OFF); } - public static CCmILogFormatter newFormatter(){ - return new CCmILogFormatter(); - } - + /** + * Logs a log entry in the file. Log entries are action that occurred during + * the interaction by local or remote user. + * + * @param source the source of the interaction + * @param action the occurred action + * @param args further informations about the occurred action + */ public static void log(String source, String action, String args){ StringBuilder builder = new StringBuilder(source); builder.append(SEPARATOR) @@ -66,14 +78,19 @@ logger.fine(builder.toString()); } - + + /** + * Logs a general message in the log file. This log entries are not + * linked to specific action of a local or remote user. + * @param msg the message to log + */ public static void log(String msg){ logger.config(msg); } - public static class CCmILogFormatter extends Formatter{ + private static class InteractionLogFormatter extends Formatter{ - private CCmILogFormatter(){ + private InteractionLogFormatter(){ super(); } @@ -99,7 +116,11 @@ return builder.toString(); } } - + + /** + * Release allocated resources. to be called hwen the interaction log is + * no longer needed. + */ public static void dispose(){ if(fileHandler != null) fileHandler.close();
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/Pair.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/Pair.java Tue Jul 10 22:39:37 2012 +0100 @@ -64,6 +64,12 @@ return true; } + /** + * the first item of the pair + */ public T1 first; + /** + * the second item of the pair + */ public T2 second; }
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/PreferencesService.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/PreferencesService.java Tue Jul 10 22:39:37 2012 +0100 @@ -38,10 +38,8 @@ return service; } catch (SecurityException exception){ - // that happens when we run under Web Start + throw new RuntimeException(exception); } - - return new NullPreferencesService(); } /** @@ -65,27 +63,15 @@ * The default preferences service that uses the java.util.prefs API. */ class DefaultPreferencesService extends PreferencesService{ - /** - * Gets an instance of the service, suitable for the package of the given class. - * @param appClass the main application class (only the package name is used as the path to - * app-specific preferences storage) - * @return an instance of the service - */ - public DefaultPreferencesService() - { - prefs = Preferences.userNodeForPackage(this.getClass()); - } - - public String get(String key, String defval) { return prefs.get(key, defval); } - public void put(String key, String defval) { prefs.put(key, defval); } - private Preferences prefs; + public DefaultPreferencesService(){ + prefs = Preferences.userNodeForPackage(this.getClass()); + } + + @Override + public String get(String key, String defval) { return prefs.get(key, defval); } + @Override + public void put(String key, String defval) { prefs.put(key, defval); } + + private Preferences prefs; } - -/** - * The null preferences service that is returned when we are an applet. - */ -class NullPreferencesService extends PreferencesService { - public String get(String key, String defval) { return defval; } - public void put(String key, String defval) { } -} \ No newline at end of file
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/ResourceFileWriter.java Tue May 29 15:32:19 2012 +0100 +++ b/java/src/uk/ac/qmul/eecs/ccmi/utils/ResourceFileWriter.java Tue Jul 10 22:39:37 2012 +0100 @@ -26,36 +26,54 @@ import java.net.URL; /** - * This class is used to extract a native library (e.g. .dll file in windows) from within a jar - * to the local file system, in order to allow the virtual machine to load it. - * + * This class is used to store a resource (e.g. .dll file in windows)to the local file system. + * + * This class can be used to allow the virtual machine to load resources which come embedded + * into a jar file. */ public class ResourceFileWriter { /** - * Creates an instance of the the class linked to a native library file. - * @param resource the URL of the native library file. The URL can be obtained by - * @see Class#getResource(String), therefore can be called from a class within a jar file - * which needs to access a static library. + * Creates an instance of the the class linked to no resource. an resource file writer + * created with this constructor is useless, unless {@code setResource} is called before + * writing the resource on the disk. + * + * @see Class#getResource(String) + */ + public ResourceFileWriter(){ + } + + /** + * Creates an instance of the the class linked to a specific resource + * + * @param resource the resource URL. The URL can be obtained by + * {@code getResource}, therefore can be called from a class within a jar file + * which needs to access embedded resources. */ public ResourceFileWriter(URL resource){ this.resource = resource; } - public void serResource(URL resource){ + /** + * Sets a new resource for this resource file writer. Successive calls to {@code writeToDisk} + * will store the resouce passed as argument in the file system. + * + * @param resource the URL of the new resource this resource file writer is linked to + */ + public void setResource(URL resource){ this.resource = resource;; path = null; } /** - * Writes the file in a directory returned by {@link PreferencesService#get(String, String)}} if defined, or - * the System default temporary directory otherwise. - * The path to the file can be retrieved by @see {@link #getFilePath()} and then passed as argument - * to {@link System#load(String)} + * Writes the resource in the file passed as argument. * - * @param prefix a prefix the temporary native library file will have in the temporary directory + * The path to the file can be retrieved afterwards through @see {@link #getFilePath()} + * + * @param dir the directory where the resource will be saved + * @param fileName the name of the file the resoruce will be saved in */ - public void writeToDisk(String dir,String fileName){ + public void writeOnDisk(String dir,String fileName){ if (resource == null) return; InputStream in = null; @@ -78,9 +96,9 @@ path = null; }finally{ if(in != null) - try{in.close();}catch(IOException ioe){} + try{in.close();}catch(IOException ioe){ioe.printStackTrace();} if(out != null) - try{out.close();}catch(IOException ioe){} + try{out.close();}catch(IOException ioe){ioe.printStackTrace();} } } @@ -88,7 +106,9 @@ /** * Returns the absolute path of the last written file. If the writing wasn't successfully * or no writing took place yet, then {@code null} is returned. - * @return the path of the last written file or {@code null} + * + * @return the path of the last written file or {@code null} if no resource was set for this + * resource file writer or {@code writeToDisk} was unsuccessful */ public String getFilePath(){ return path;
--- a/java/src/uk/ac/qmul/eecs/ccmi/utils/Validator.java Tue May 29 15:32:19 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -/* - 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.utils; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class Validator { - - public static boolean validateIPAddr(String addr){ - Matcher m = ip.matcher(addr); - return m.matches(); - } - - private static Pattern ip = Pattern.compile("\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b"); -}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/CollectionsManager.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,277 @@ +/* + 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/>. +*/ + +#include "CollectionsManager.h" + +using namespace H3DUtil::ArithmeticTypes; + +void CollectionsManager::init(void){ + /**************** init jni variables **********************/ + // --- classes --- + // get the java haptics class + hapticClass = env->GetObjectClass(*haptics); + if(hapticClass == NULL){ + stopExecution("Could not find the Haptics class"); + } + + bitsetClass = env->FindClass("Ljava/util/BitSet;"); + if(bitsetClass == NULL) + stopExecution("failed to find bitset class"); + + //get the node list class + listClass = env->FindClass("Ljava/util/ArrayList;"); + if(listClass == NULL) + stopExecution("failed to find list class"); + + // get the Node class to call the get method on the node list + hapticNodeClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/Node;"); + if(hapticNodeClass == NULL){ + stopExecution("Could not find the Node class"); + } + + // get the Edge class to call the get method on the edge list + hapticEdgeClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/Edge;"); + if(hapticEdgeClass == NULL){ + stopExecution("Could not find the Edge class"); + } + // --- methods --- + // "get" method id + getMethodId = env->GetMethodID(listClass, "get", "(I)Ljava/lang/Object;"); + if(getMethodId == NULL) + stopExecution("Could not retrieve the get method id"); + + //size method id + sizeMethodId = env->GetMethodID(listClass, "size", "()I"); + if(sizeMethodId == NULL) + stopExecution("failed to get size method id"); + + //get method of the BitSet class + getBitMethodId = env->GetMethodID(bitsetClass, "get","(I)Z"); + if(getBitMethodId == NULL) + stopExecution("failed to get the get method id of the BitSet class"); + + // -- field id's -- + //retrieve edgeList field id + edgeListfieldId = env->GetFieldID(hapticClass,"currentEdges", "Ljava/util/ArrayList;"); + if(edgeListfieldId == NULL){ + stopExecution("failed to find the edge list field id"); + } + + //retrieve nodeList field id + nodeListfieldId = env->GetFieldID(hapticClass,"currentNodes", "Ljava/util/ArrayList;"); + if(nodeListfieldId == NULL){ + stopExecution("failed to find the node list field id"); + } + + // Node fields id's + xFieldId = env->GetFieldID(hapticNodeClass,"x","D"); + if(xFieldId == NULL) + stopExecution("Could not find the x field ID"); + + + yFieldId = env->GetFieldID(hapticNodeClass,"y","D"); + if(yFieldId == NULL) + stopExecution("Could not find the y field ID"); + + nodeHapticIdFieldId = env->GetFieldID(hapticNodeClass, "hapticId", "I"); + if(nodeHapticIdFieldId == NULL) + stopExecution("Could not find the node hapticId field ID"); + + nodeDiagramIdFieldId = env->GetFieldID(hapticNodeClass, "diagramId", "I"); + if(nodeDiagramIdFieldId == NULL) + stopExecution("Could not find the node diagramId field ID"); + + // Edge field id's + // edge.hapticId + edgeHapticIdFieldId = env->GetFieldID(hapticEdgeClass, "hapticId", "I"); + if(edgeHapticIdFieldId == NULL) + stopExecution("Could not find the edge hapticId field ID"); + + edgeDiagramIdFieldId = env->GetFieldID(hapticEdgeClass, "diagramId", "I"); + if(edgeDiagramIdFieldId == NULL) + stopExecution("Could not find the edge diagramId field ID"); + + // stipplePattern + stipplePatternfieldId = env->GetFieldID(hapticEdgeClass, "stipplePattern", "I"); + if(stipplePatternfieldId == NULL){ + stopExecution("Could not find the stipplePattern field ID"); + } + + edgeSizefieldId = env->GetFieldID(hapticEdgeClass, "size", "I"); + if(edgeSizefieldId == NULL) + stopExecution("Could not find edge size field ID"); + + edgeXsFieldId = env->GetFieldID(hapticEdgeClass,"xs", "[D"); + if(edgeXsFieldId == NULL) + stopExecution("Could not find edge xs field ID"); + + edgeYsFieldId = env->GetFieldID(hapticEdgeClass,"ys", "[D"); + if(edgeYsFieldId == NULL) + stopExecution("Could not find edge ys field ID"); + + edgeAdjMatrixFieldId = env->GetFieldID(hapticEdgeClass,"adjMatrix", "[Ljava/util/BitSet;"); + if(edgeAdjMatrixFieldId == NULL) + stopExecution("Could not find edge adjMatrix field ID"); + + attractPointXFieldId = env->GetFieldID(hapticEdgeClass, "attractPointX", "D"); + if(attractPointXFieldId == NULL) + stopExecution("Could not find the edge attractPointX field ID"); + + attractPointYFieldId = env->GetFieldID(hapticEdgeClass, "attractPointY", "D"); + if(attractPointYFieldId == NULL) + stopExecution("Could not find the edge attractPointY field ID"); + + edgeNodeStartFieldId = env->GetFieldID(hapticEdgeClass, "nodeStart", "I"); + if(edgeNodeStartFieldId == NULL) + stopExecution("Could not find the edge nodeStart field ID"); +} + +const jint CollectionsManager::getNodesNum() { + /* to read the node list field into the nodeList variable each time we get the size is * + * needed as, when the tab is switched the nodeList variables still points to the previuos * + * tab's node list. We do it only here and not in getNodeData because getNodesData * + * follows this call before releasing the monitor, therefore data integrity is granted. */ + env->DeleteLocalRef(nodeList); + nodeList = env->GetObjectField(*haptics, nodeListfieldId); + if(nodeList == NULL){ + stopExecution("could not get the node list field of Haptic Class"); + } + jint size = env->CallIntMethod( nodeList, sizeMethodId); + checkExceptions(env,"Could not call ArrayList<Node>.size()"); + return size; +} + +const jint CollectionsManager::getEdgesNum(){ + env->DeleteLocalRef(edgeList); + edgeList = env->GetObjectField(*haptics, edgeListfieldId); + if(edgeList == NULL){ + stopExecution("could not get the edge list field of Haptic Class"); + } + + jint size = env->CallIntMethod( edgeList, sizeMethodId); + checkExceptions(env, "Could not call ArrayList<Edge>.size()"); + return size; +} + +CollectionsManager::NodeData & CollectionsManager::getNodeData(const int i){ + /* get the i-th node */ + jobject currentNode = env->CallObjectMethod(nodeList,getMethodId,i); + checkExceptions(env,"Could not call ArrayList<Node>.size()"); + fillupNodeData(nd,currentNode); + env->DeleteLocalRef(currentNode); + return nd; +} + +CollectionsManager::EdgeData & CollectionsManager::getEdgeData(const int i){ + /* first we look for the i-th edge in the Haptics java class. Once we get it, * + * we need all the coordinates of the node this edge is connecting, so that we * + * can draw it. The edge mantains a list of references to such nodes. */ + + /* get the i-th edge */ + jobject currentEdge = env->CallObjectMethod(edgeList,getMethodId,i); + checkExceptions(env, "Could not call ArrayList<Edge>.get(int) in the haptics edges"); + if(currentEdge == NULL) + stopExecution("Could not find get current Edge"); + jint size = env->GetIntField(currentEdge, edgeSizefieldId); + ed.setSize(size); + fillupEdgeData(ed,currentEdge); + env->DeleteLocalRef(currentEdge); + return ed; +} + +void CollectionsManager::fillupNodeData(NodeData & nd, jobject & currentNode){ + //reads the fields of the current node + nd.x = env->GetDoubleField(currentNode,xFieldId); + nd.y = env->GetDoubleField(currentNode,yFieldId); + // takes coordinates from the screen. Needs to convert the y axis as in openGL (0,0) = bottom left corner + Vec3d & glCoordinatePosition = screenToHapticSpace(Vec3d(nd.x, nd.y, 0),screenWidth,screenHeight); + nd.x = glCoordinatePosition[0]; + nd.y = glCoordinatePosition[1]; + nd.hapticId = env->GetIntField(currentNode, nodeHapticIdFieldId); + nd.diagramId = env->GetIntField(currentNode, nodeDiagramIdFieldId); +} + +void CollectionsManager::fillupEdgeData(EdgeData & ed, jobject & currentEdge){ + /* get the array of x coordinates */ + jobject xsAsObj = env->GetObjectField(currentEdge,edgeXsFieldId); + if(xsAsObj == NULL) + stopExecution("Cannot get the xs field"); + jdoubleArray *jxs = reinterpret_cast<jdoubleArray*>(&xsAsObj); + double * xs = env->GetDoubleArrayElements(*jxs, NULL); + if(xs == NULL) + stopExecution("Cannot get the xs field array of double"); + + /* get the array of y coordinates */ + jobject ysAsObj = env->GetObjectField(currentEdge,edgeYsFieldId); + if(ysAsObj == NULL) + stopExecution("Cannot get the xs field"); + + jdoubleArray *jys = reinterpret_cast<jdoubleArray*>(&ysAsObj); + double * ys = env->GetDoubleArrayElements(*jys, NULL); + if(ys == NULL) + stopExecution("Cannot get the ys field array of double"); + // copy the data into the edgeData object + for(unsigned int i=0; i<ed.getSize(); i++){ + ed.x[i] = xs[i]; + ed.y[i] = ys[i]; + // takes coordinates from the screen (needs to convert the y axis as in openGL 0 = bottom left corner + Vec3d & glCoordinatePosition = screenToHapticSpace(Vec3d(ed.x[i], ed.y[i], 0),screenWidth,screenHeight); + ed.x[i] = glCoordinatePosition[0]; + ed.y[i] = glCoordinatePosition[1]; + } + env->ReleaseDoubleArrayElements(*jxs, xs, 0); + env->ReleaseDoubleArrayElements(*jys, ys, 0); + env->DeleteLocalRef(xsAsObj); + env->DeleteLocalRef(ysAsObj); + jobject adjMatrixAsObj = env->GetObjectField(currentEdge, edgeAdjMatrixFieldId); + if(adjMatrixAsObj == NULL) + stopExecution("Cannot get the adjMatrix field"); + jobjectArray *jadjMatrix = reinterpret_cast<jobjectArray*>(&adjMatrixAsObj); + + for(unsigned int i=0; i<ed.getSize(); i++){ + jobject adjMatrixBitSet = env->GetObjectArrayElement(*jadjMatrix,i); + if(adjMatrixBitSet == NULL) + stopExecution("Cannot get the adjMatrix field array element"); + for(unsigned int j=0;j<ed.getSize(); j++){ + jboolean b = env->CallBooleanMethod(adjMatrixBitSet,getBitMethodId,j); + checkExceptions(env,"Could not call BitSet.get()"); + if(b == JNI_TRUE) + ed.adjMatrix[i][j] = true; + else + ed.adjMatrix[i][j] = false; + } + env->DeleteLocalRef(adjMatrixBitSet); + } + env->DeleteLocalRef(adjMatrixAsObj); + + // set the haptic id used by the haptic device + ed.hapticId = env->GetIntField(currentEdge, edgeHapticIdFieldId); + // set the diagram id used in the java thread + ed.diagramId = env->GetIntField(currentEdge, edgeDiagramIdFieldId); + // set the stipple pattern + ed.stipplePattern = env->GetIntField(currentEdge, stipplePatternfieldId); + // set the attract point + ed.attractPoint[0] = env->GetDoubleField(currentEdge, attractPointXFieldId); + ed.attractPoint[1] = env->GetDoubleField(currentEdge, attractPointYFieldId); + Vec3d & glCoordinatePosition = screenToHapticSpace(Vec3d(ed.attractPoint[0], ed.attractPoint[1], 0),screenWidth,screenHeight); + ed.attractPoint[0] = glCoordinatePosition[0]; + ed.attractPoint[1] = glCoordinatePosition[1]; + //set the index from which the nodes start in the adjMatrix + ed.nodeStart = env->GetIntField(currentEdge, edgeNodeStartFieldId); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/CollectionsManager.h Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,156 @@ +/* + 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/>. +*/ + +#pragma once +#include <jni.h> +#include <new> +#include <H3DUtil/Vec3d.h> + +#include "utils.h" + + +/* this class uses the java haptic object lists to provide nodes * + * and edges to draw to the graphic and haptic managers */ +class CollectionsManager { +public: + struct NodeData { + jdouble x; + jdouble y; + jint hapticId; + jint diagramId; + + NodeData(){} + private : + NodeData(const NodeData& n){/* avoid mistakenly copy construction calls */} + }; + + struct EdgeData{ + jdouble *x; + jdouble *y; + bool **adjMatrix; + jint hapticId; + jint diagramId; + jint stipplePattern; + jsize nodeStart; + jdouble attractPoint[2]; + void setSize(unsigned int s){ + size = s; + if(s > previousSize){ + /* delete the old memory */ + delete [] x; + delete [] y; + for(unsigned int i=0; i<previousSize; i++) + delete[] adjMatrix[i]; + delete [] adjMatrix; + /* allocates a bigger one */ + try{ + x = new jdouble[size]; + y = new jdouble[size]; + adjMatrix = new bool* [size]; + for(unsigned int i=0; i<size; i++){ + adjMatrix[i] = new bool[size]; + for(unsigned int j=0; j<size; j++) + adjMatrix[i][j] = false; + } + }catch(std::bad_alloc){ + stopExecution("Could not allocate memory for the program.\nAborting..."); + } + previousSize = size; + } + } + + unsigned int getSize() const { + return size; + } + + EdgeData(unsigned int s) : size(s), previousSize(s){ + x = new jdouble[size]; + y = new jdouble[size]; + adjMatrix = new bool* [size]; + for(unsigned int i=0; i<size; i++){ + adjMatrix[i] = new bool[size]; + for(unsigned int j=0; j<size; j++) + adjMatrix[i][j] = false; + } + } + ~EdgeData(){ + delete [] x; + delete [] y; + for(unsigned int i=0; i<size; i++) + delete[] adjMatrix[i]; + delete [] adjMatrix; + } + private : + unsigned int size; + unsigned int previousSize; + EdgeData(const EdgeData& e){ /* avoid mistakenly copy construction */} + + }; +private: + JNIEnv *env; + jobject *haptics; + + jclass hapticClass; + jclass hapticNodeClass; + jclass hapticEdgeClass; + jclass listClass; + jclass bitsetClass; + jobject nodeList; + jobject edgeList; + jmethodID getMethodId; + jmethodID sizeMethodId; + jmethodID getBitMethodId; + jfieldID xFieldId; + jfieldID yFieldId; + jfieldID nodeHapticIdFieldId; + jfieldID edgeHapticIdFieldId; + jfieldID nodeDiagramIdFieldId; + jfieldID edgeDiagramIdFieldId; + jfieldID nodeListfieldId; + jfieldID stipplePatternfieldId; + jfieldID edgeListfieldId; + jfieldID edgeSizefieldId; + jfieldID edgeXsFieldId; + jfieldID edgeYsFieldId; + jfieldID edgeAdjMatrixFieldId; + jfieldID attractPointXFieldId; + jfieldID attractPointYFieldId; + jfieldID edgeNodeStartFieldId; + + + int screenHeight; + int screenWidth; + NodeData nd; + EdgeData ed; + + void fillupNodeData(struct NodeData & nd, jobject & currentNode); + void fillupEdgeData(struct EdgeData & ed, jobject & currentEdge); +public: + CollectionsManager(JNIEnv *environment, jobject *obj) : env(environment), haptics(obj), ed(2){} + virtual ~CollectionsManager(void){} + const jint getNodesNum(); + const jint getEdgesNum(); + struct NodeData & getNodeData(const int i); + struct EdgeData & getEdgeData(const int i); + inline int getScreenHeight() const { return screenHeight;} + inline void setScreenHeight(const int sh) { screenHeight = sh; } + inline int getScreenWidth() const { return screenWidth;} + inline void setScreenWidth(const int sw) { screenWidth = sw; } + void init(void); +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/FalconHaptics.vcproj Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,213 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="9.00" + Name="FalconHaptics" + ProjectGUID="{1639E3C7-81A2-4887-B953-DB4DB44566DA}" + RootNamespace="FalconHaptic" + TargetFrameworkVersion="196613" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_25\include";"C:\Program Files\Java\jdk1.6.0_25\include\win32";C:\H3D\H3DUtil\include;C:\H3D\External\include\pthread;C:\H3D\HAPI\include;C:\H3D\External\include" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="3" + WarningLevel="3" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="C:\H3D\External\lib\freeglut.lib C:\H3D\lib\H3DUtil_vc9.lib C:\H3D\External\lib\pthreadVC2.lib C:\H3D\lib\HAPI_vc9.lib" + GenerateDebugInformation="true" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="$(SolutionDir)$(ConfigurationName)" + IntermediateDirectory="$(ConfigurationName)" + ConfigurationType="2" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="2" + EnableIntrinsicFunctions="true" + AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_25\include";"C:\Program Files\Java\jdk1.6.0_25\include\win32";C:\H3D\H3DUtil\include;C:\H3D\External\include\pthread;C:\H3D\HAPI\include;C:\H3D\External\include" + RuntimeLibrary="2" + EnableFunctionLevelLinking="true" + WarningLevel="3" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="C:\H3D\External\lib\freeglut.lib C:\H3D\lib\H3DUtil_vc9.lib C:\H3D\External\lib\pthreadVC2.lib C:\H3D\lib\HAPI_vc9.lib" + AdditionalLibraryDirectories="" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath=".\CollectionsManager.cpp" + > + </File> + <File + RelativePath=".\HapticManager.cpp" + > + </File> + <File + RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.cpp" + > + </File> + <File + RelativePath=".\utils.cpp" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <File + RelativePath=".\CollectionsManager.h" + > + </File> + <File + RelativePath=".\HapticManager.h" + > + </File> + <File + RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.h" + > + </File> + <File + RelativePath=".\utils.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/HapticManager.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,550 @@ +/* + 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/>. +*/ + +#include "HapticManager.h" + +using namespace HAPI; + +// Render a sphere using OpenGL calls. +void drawSphere() { + GLUquadricObj* pObj = gluNewQuadric(); + gluSphere(pObj, 0.0010, 10, 10); + gluDeleteQuadric(pObj ); +} + +HapticManager::HapticManager(CollectionsManager * cManager, + void (*func)( + const jchar cmd, + const jint ID, + const jdouble startx, + const jdouble starty, + const jdouble endx, + const jdouble endy) + ) + : executeCommand(func), cm(cManager), force_effect(0), last_touched_point(NULL), + last_touched_line(NULL),magnetic_mode(LOOSE), pickup_mode(RELEASED), object_unselected(false), + magnetic_mode_changed(false), pickedup_point(NULL), pickedup_line(NULL) {} + + +bool HapticManager::init (void){ + + hd = new AnyHapticsDevice(); + + // The haptics renderer to use. + hd->setHapticsRenderer( new HAPI::GodObjectRenderer() ); + + /* init the device */ + if(hd->initDevice() != HAPIHapticsDevice::SUCCESS){ + return false; + } + + /* enable the device ( forces and positions will be updated) */ + hd->enableDevice(); + + return true; +} + +/* draws the visual diagram every time it's called. The haptic diagram is redrawn only if * + * something has changed in the diagram ( redraw_haptic_scene = true ) as, unlike openGL, * + * it doesn't need to be drawn at each frame but only once */ +void HapticManager::drawDiagram(bool redraw_haptics_scene, bool pickup, int _attract_to ){ + + /* STOP_ATTRACTION means the object has been reached (or eventually deleted). The attraction * + * force must therefore be stopped and a redrawing of the haptic forces is necessary */ + if(attraction_mode == STOP_ATTRACTION){ + attraction_mode = NO_ATTRACTION; + attract_to = NO_ID; + redraw_haptics_scene = true; + } + + /* _attract_to is different from NO_ID there is no attraction going on (attraction_mode = NO_ATTRACTION)* + * It differs from STOP_ATTRACTION in that the haptic scene doesn't have to be repainted */ + if(_attract_to != NO_ID){ + attract_to = _attract_to; + attraction_mode = ACTIVE; + redraw_haptics_scene = true; + } + + if(magnetic_mode_changed){ + redraw_haptics_scene = true; + magnetic_mode_changed = false; + } + + if(pickup){ + pickup_mode = START_DRAGGING; + redraw_haptics_scene = true; + } + + /* wash before use */ + if(redraw_haptics_scene){ + point_set.clear(); + line_set.clear(); + point_id_map.clear(); + line_id_map.clear(); + } + + /* --- draw the edges --- */ + glPushAttrib(GL_ENABLE_BIT); + glColor3f(1.0f,0.0f,0.0f); // Red + glLineWidth(2.0); + glDisable(GL_LIGHTING); + glEnable(GL_COLOR_MATERIAL); + int numEdges = cm->getEdgesNum(); + for ( int i = 0; i < numEdges; i++){ + /* draw the edge in openGL */ + CollectionsManager::EdgeData & ed = cm->getEdgeData(i); + glPushAttrib(GL_ENABLE_BIT); + glLineStipple(1, ed.stipplePattern); + glEnable(GL_LINE_STIPPLE); + glBegin(GL_LINES); + for(unsigned int j = 0; j < ed.getSize(); j++){ + for(unsigned int k = j; k < ed.getSize(); k++){ + if(ed.adjMatrix[j][k]){ + glVertex3d(ed.x[j]*2,ed.y[j]*GRAPHIC_SCALE,0); + glVertex3d(ed.x[k]*2,ed.y[k]*GRAPHIC_SCALE,0); + } + } + } + glEnd(); + glPopAttrib(); + /* update haptics edge line set if a change in the collection occurred */ + if(redraw_haptics_scene){ + /* build the vector with the edges*/ + for(unsigned int j = 0; j < ed.getSize(); j++){ + for(unsigned int k = j; k < ed.getSize(); k++){ + if(ed.adjMatrix[j][k]){ + line_set.push_back(Collision::LineSegment ( + Vec3(ed.x[j],ed.y[j],0), + Vec3(ed.x[k],ed.y[k],0) + )); + } + } + } + + for(vector< HAPI::Collision::LineSegment>::iterator itr = line_set.begin(); itr != line_set.end(); itr++){ + line_id_map.insert(pair<Collision::LineSegment*,int>(&(*itr),ed.hapticId)); + } + } + } + glPopAttrib(); + + /* --- draw the nodes --- */ + int numNodes = cm->getNodesNum(); + glColor3f(1.0f, 1.0f, 1.0f); // white + for( int i = 0; i < numNodes; i++){ + /* draw the nodes in openGL */ + glPushMatrix(); + CollectionsManager::NodeData &nd = cm->getNodeData(i); + glTranslated(nd.x*GRAPHIC_SCALE,nd.y*GRAPHIC_SCALE, 0); + drawSphere(); + glPopMatrix(); + + /* update haptics node line set if a change in the collection occurred */ + if(redraw_haptics_scene){ + point_set.push_back(Vec3(nd.x,nd.y,0)); + point_id_map.insert(pair<Collision::Point*,int>(&(point_set.back()),nd.hapticId)) ; + } + } + + /* --- update the haptic force if a change in the collection occurred --- */ + if(redraw_haptics_scene){ + /* reset the effects and set up new one, otherwise they would accumulate */ + hd->clearEffects(); + /* first lines */ + if(!line_set.empty()){ + /* force according to the current attraction_mode */ + int force_factor = (magnetic_mode == STICKY) ? LINE_FORCE_FACTOR_STICKY : LINE_FORCE_FACTOR_LOOSE; + /* if the user is dragging or looking for an object, the force * + * factor is always loose until she drops or find the object */ + if(pickup_mode == DRAGGING || pickup_mode == START_DRAGGING || attraction_mode == ACTIVE){ + force_factor = LOOSE; + } + + /* update the force */ + force_effect.reset( new HapticShapeConstraint(new HapticLineSet( line_set, 0 ),force_factor)); + hd->addEffect(force_effect.get()); + + /* recalculate last_touched_line if it was != NULL as the pointed * + object is now destroyed after clear() and replaced with a new one */ + if(last_touched_line != NULL){ + last_touched_line = NULL; + vector<Collision::LineSegment>::iterator itr; + Vec3 closest_point, normal, tex_coord; + for( itr=line_set.begin(); itr != line_set.end(); itr++ ){ + itr->closestPoint(proxy_pos,closest_point,normal,tex_coord); + if(pointsDistance(closest_point,proxy_pos) < LINE_COLLISION_THRESHOLD){ + last_touched_line = &(*itr); + break; + } + } + if(last_touched_line == NULL){ + /* touched line was != NULL and now is NULL. It means it has been deleted * + * while being touched, therefore an unselect command must be issued */ + object_unselected = true; + } + } + }else if(last_touched_line != NULL){ + /* if last_touuched_line was != null it means an edge has been deleted * + * which was being touched, therefore un unselect command must be issued */ + object_unselected = true; + /* if there are no lines there cannot be a last touched one */ + last_touched_line = NULL; + } + + /* now points */ + if(!point_set.empty()){ + /* update the force */ + force_effect.reset( new HapticShapeConstraint(new HapticPointSet( point_set, 0 ),POINT_FORCE_FACTOR ) ); + hd->addEffect(force_effect.get()); + + /* recalculate lastTouchedNode if it was != NULL as the pointed object is now destroyed after clear() */ + if(last_touched_point != NULL){ + last_touched_point = NULL; + vector<Collision::Point>::iterator itr; + for(itr=point_set.begin(); itr != point_set.end(); itr++ ){ + if(pointsDistance(itr->position,proxy_pos) < POINT_COLLISION_THRESHOLD ){ + last_touched_point = &(*itr); + break; + } + } + if(last_touched_point == NULL){ + /* touched point was != NULL and now is NULL. It means it has been deleted * + * while being touched, therefore an unselect command must be issued */ + object_unselected = true; + } + } + }else if(last_touched_point != NULL){ + /* if last_touuched_point was != null it means a node has been deleted * + * which was being touched, therefore un unselect command must be issued */ + object_unselected = true; + /* if there are no points there cannot be a last touched one */ + last_touched_point = NULL; + } + + if(pickup_mode == START_DRAGGING && attraction_mode == NO_ATTRACTION){ + force_effect.reset(new HapticSpring(proxy_pos,SPRING_FORCE_FACTOR)); + hd->addEffect(force_effect.get()); + /* spring force is initialized not pickup_mode goes to DRAGGING*/ + pickup_mode = DRAGGING; + }else if(attraction_mode == ACTIVE){ + bool found = false; + /* let's look for the object into the nodes */ + for(map<HAPI::Collision::Point*,int>::iterator itr = point_id_map.begin();itr != point_id_map.end(); itr++){ + if( (*itr).second == attract_to ){ + force_effect.reset(new HapticSpring(((*itr).first)->position,SPRING_FORCE_FACTOR)); + hd->addEffect(force_effect.get()); + found = true; + break; + } + } + /* if not found look into the edges */ + if(!found){ + for(map<HAPI::Collision::LineSegment*,int>::iterator itr = line_id_map.begin();itr != line_id_map.end(); itr++){ + if( (*itr).second == attract_to ){ + HAPI::Collision::LineSegment* line_ptr = (*itr).first; + force_effect.reset(new HapticSpring( + midPoint(line_ptr->start, line_ptr->end), + SPRING_FORCE_FACTOR)); + hd->addEffect(force_effect.get()); + found = true; + break; + } + } + } + + if(!found){ + /* element has been deleted before user could reach it: go back to normal */ + attraction_mode = STOP_ATTRACTION; + } + + } + /* just deallocate the memory for the last force effect */ + force_effect.reset(); + /* tranfer all the forces to the device */ + hd->transferObjects(); + } +} + + +void HapticManager::drawCursor(){ + HAPIHapticsRenderer *hr = hd->getHapticsRenderer(); + if( hr ) { + /* save the proxy pos in a global variable */ + proxy_pos = static_cast< HAPIProxyBasedRenderer * >(hr)->getProxyPosition(); + + glPushMatrix(); + glPushAttrib(GL_CURRENT_BIT | GL_ENABLE_BIT | GL_LIGHTING_BIT); + glTranslatef( (GLfloat)proxy_pos.x*GRAPHIC_SCALE, + (GLfloat)proxy_pos.y*GRAPHIC_SCALE, + (GLfloat)proxy_pos.z ); + glEnable(GL_COLOR_MATERIAL); + glColor3f(0.0f, 0.5f, 1.0f);// Blue + drawSphere(); + glPopAttrib(); + glPopMatrix(); + } +} + +bool HapticManager::checkNodeCollision(){ + /* if the proxy goes far enough from the last touched node, then * + * lastTouched node can be set back to NULL */ + bool do_unselect = false; + if(last_touched_point != NULL){ + if(pointsDistance(last_touched_point->position, proxy_pos) > POINT_COLLISION_THRESHOLD){ + last_touched_point = NULL; + do_unselect = true; + } + } + + /* find the closest point among those closer than POINT_COLLISION_THRESHOLD */ + vector<Collision::Point>::iterator itr; + HAPI::Collision::Point* found_point = NULL; + double min_dist = POINT_COLLISION_THRESHOLD; + double dist; + for(itr=point_set.begin(); itr != point_set.end(); itr++ ){ + if((dist = pointsDistance(itr->position,proxy_pos)) < min_dist ){ + found_point = &(*itr); + min_dist = dist; + } + } + + if(found_point != NULL){ + /* if the last touched point is the same, it means the cursor was * + * already on it therefore do nothing as the node name is uttered only when * + * the proxy comes across it and not at each frame (it would make a mess) */ + if(last_touched_point != found_point){ + last_touched_point = found_point; + /* Setting last_touched_line to NULL, means untouching the eventual touching line. * + * When a node is touched, the connected lines are indeed untouched as only one object * + * at time can be touching the position proxy */ + last_touched_line = NULL; + int point_id = point_id_map[last_touched_point]; + /* select the node for eventual highlighting int the java tree */ + executeCommand(SELECT_CMD,point_id,0,0,0,0); + /* utter the name of the node */ + executeCommand(SPEAK_NAME_CMD,point_id,0,0,0,0); + if(attraction_mode == ACTIVE && point_id == attract_to){ + /* we reached the attracting node, stop the attraction force */ + attraction_mode = STOP_ATTRACTION; + } + } + return true; + } + + if(do_unselect) + executeCommand(UNSELECT_CMD,0,0,0,0,0); + + return false; +} + +bool HapticManager::checkEdgeCollision(){ + Vec3 closest_point, normal, tex_coord; + + /* id is taken from the last touched line is overwritten. To understand why suppose we have an edge * + * made of two lines A and B. When going from one line to another we don't have to utter the name (see * + * comment below. last_touched_line goes from a to NULL before being set to B. If lastTouchedLineId is * + * calculated before checking for NULL-ness it will become -1 and it won't be useful anymore to avoid * + * uttering the edge name when going from A to B. In this way instead proxy goes from A to B, the id is * + * to A's, last_touched_line becomes NULL and then it becomes B, but the name is not uttered because id * + * is the same. This is a very long comment. I want to finish the line to make it look better. bye bye */ + int lastTouchedLineId = (last_touched_line == NULL) ? - 1 : line_id_map[last_touched_line]; + + bool do_unselect = false; + /* if the proxy goes far enough from the last touched line, then last_touched_line can be set back to NULL */ + if(last_touched_line != NULL){ + last_touched_line->closestPoint(proxy_pos,closest_point,normal,tex_coord); + if(pointsDistance(closest_point, proxy_pos) > LINE_COLLISION_THRESHOLD){ + last_touched_line = NULL; + do_unselect = true; + } + } + + /* find the closest line to the proxy among those closer than LINE_COLLISION_THRESHOLD */ + vector<Collision::LineSegment>::iterator itr; + HAPI::Collision::LineSegment* found_line = NULL; + double min_dist = LINE_COLLISION_THRESHOLD; + double dist; + for( itr=line_set.begin(); itr != line_set.end(); itr++ ){ + itr->closestPoint(proxy_pos,closest_point,normal,tex_coord); + if((dist = pointsDistance(closest_point,proxy_pos)) < min_dist){ + found_line = &(*itr); + min_dist = dist; + } + } + + if(found_line != NULL){ + last_touched_line = found_line; + /* proxy can touch only one object at time either line or point */ + last_touched_point = NULL; + /* An edge can be broken into several lines. Such lines will map to the same edge id * + * If the proxy detouches from a line but immediately touches another line mapped to * + * the same id, it just means the proxy is going aling the line and therefore no * + * name must be uttered again (the name must be uttered when touching an edge and * + * not when touching each line. We keep track of the last touched id and if it's the * + * as te new touched line id, then nothing is uttered, for we're on the same edge */ + if(lastTouchedLineId != line_id_map[last_touched_line]){ + int line_id = line_id_map[last_touched_line]; + /* select the node for eventual highlighting int the java tree */ + executeCommand(SELECT_CMD,line_id,0,0,0,0); + /* utter the name of the edge */ + executeCommand(SPEAK_NAME_CMD,line_id,0,0,0,0); + if(attraction_mode == ACTIVE && line_id == attract_to){ + /* we reached the attracting node, stop the attraction force */ + attraction_mode = STOP_ATTRACTION; + } + } + return true; + } + + if(do_unselect){ + executeCommand(UNSELECT_CMD,0,0,0,0,0); + } + + return false; +} + +bool HapticManager::checkUnselection(void){ + if(object_unselected){ + object_unselected = false; + executeCommand(UNSELECT_CMD,0,0,0,0,0); + return true; + } + return false; +} + +bool HapticManager::checkButtons(void){ + HAPIInt32 status = hd->getButtonStatus(); + /* handle the buttons. Buttons can be pressed one at time. If a button is pressed all the * + * other buttons (either when pressed or released) are ignored untill the button is released */ + if(pressed_button == NO_BUTTON){ + if(status & FRONT_BUTTON){ + /* front button pressed, change how magnetic the lines are */ + pressed_button = FRONT_BUTTON; + /* switch from sticky to loose and vice versa */ + magnetic_mode = (magnetic_mode == STICKY) ? LOOSE : STICKY; + magnetic_mode_changed = true; + /* make a sound according to the new attraction mode */ + executeCommand(PLAY_SOUND_CMD, (magnetic_mode == STICKY) ? STICKY_MODE_SOUND : LOOSE_MODE_SOUND ,0,0,0,0); + return true; + }else if(status & REAR_BUTTON){ + pressed_button = REAR_BUTTON; + /* rear button pressed: First time it picks up an object, second time it drops it */ + if(pickup_mode == RELEASED){ // pickup mode was released, then now user picked up an object + if(last_touched_point != NULL){ + pickedup_point = last_touched_point; + last_dragging_pos = pickedup_point->position; // last_dragging_pos used in checkMotion + /* send a pickup command to the Java thread which will in turn issue another pickup * + * command to this thread (pickup = true in drawDiagram) if the lock could be granted */ + executeCommand(PICKUP_CMD,point_id_map[last_touched_point],0,0,0,0); + }else if(last_touched_line != NULL){ + pickedup_line = last_touched_line; + pickup_line_pos = proxy_pos; // the point where the line was picked up + last_dragging_pos = proxy_pos;// last_dragging_pos used in checkMotion + executeCommand(PICKUP_CMD,line_id_map[last_touched_line],0,0,0,0); + }/* else user picked up thin air. Do nothing. */ + }else if(pickup_mode == DRAGGING){ // pickup mode was dragging, then now user dropped an object + pickup_mode = RELEASED; + Vec3 & new_position = hapticToScreenSpace(Vec3(proxy_pos), + cm->getScreenWidth(), + cm->getScreenHeight()); + if(pickedup_point){ + executeCommand(MOVE_CMD,point_id_map[pickedup_point],new_position.x,new_position.y,0,0); + pickedup_point = NULL; + }else{ + hapticToScreenSpace(pickup_line_pos, + cm->getScreenWidth(), + cm->getScreenHeight()); + executeCommand(MOVE_CMD, + line_id_map[pickedup_line], + new_position.x, + new_position.y, + pickup_line_pos.x, + pickup_line_pos.y); + pickedup_line = NULL; + } + return true; + } + } else if(status & ( LEFT_BUTTON | RIGHT_BUTTON )){ // either left or right button pressed + pressed_button = (status == LEFT_BUTTON) ? LEFT_BUTTON : RIGHT_BUTTON; + if(last_touched_point != NULL){ // priority to nodes + executeCommand(SPEAK_INFO_CMD,point_id_map[last_touched_point], 0,0,0,0); + return true; + } + if(last_touched_line != NULL){ + executeCommand(SPEAK_INFO_CMD,line_id_map[last_touched_line], 0,0,0,0); + return true; + } + } + } else if ( /* deactivate the buttons */ + (pressed_button == FRONT_BUTTON && !(status & FRONT_BUTTON)) || + (pressed_button == REAR_BUTTON && !(status & REAR_BUTTON)) || + (pressed_button == LEFT_BUTTON && !(status & LEFT_BUTTON)) || + (pressed_button == RIGHT_BUTTON && !(status & RIGHT_BUTTON))) + { + pressed_button = NO_BUTTON; + } + return false; +} + +bool HapticManager::checkMotion(void){ + if(pickup_mode != DRAGGING) + return false; + if(pointsDistance(proxy_pos,last_dragging_pos) > DRAGGING_SOUND_THRESHOLD){ + last_dragging_pos = proxy_pos; + executeCommand(PLAY_SOUND_CMD,DRAGGING_SOUND,0,0,0,0); + return true; + } + return false; +} + +void HapticManager::dispose(void){ + hd->releaseDevice(); + delete hd; +} + + +/* static constants initialization */ +const int HapticManager::NO_ID = 0; + +const int HapticManager::GRAPHIC_SCALE = 2; +const int HapticManager::SPRING_FORCE_FACTOR = 200; +const int HapticManager::LINE_FORCE_FACTOR_STICKY = 2000; +const int HapticManager::LINE_FORCE_FACTOR_LOOSE = 100; +const int HapticManager::POINT_FORCE_FACTOR = 20; +const double HapticManager::POINT_COLLISION_THRESHOLD = 0.0025; +const double HapticManager::LINE_COLLISION_THRESHOLD = 0.0025; +const double HapticManager::DRAGGING_SOUND_THRESHOLD = 0.01; + +const int HapticManager::LOOSE_MODE_SOUND = 0; +const int HapticManager::STICKY_MODE_SOUND = 1; +const int HapticManager::DRAGGING_SOUND = 3; + +const char HapticManager::MOVE_CMD = 'm'; +const char HapticManager::PLAY_SOUND_CMD = 'g'; +const char HapticManager::SPEAK_NAME_CMD = 't'; +const char HapticManager::PICKUP_CMD = 'c'; +const char HapticManager::SPEAK_INFO_CMD = 'i'; +const char HapticManager::SELECT_CMD = 's'; +const char HapticManager::UNSELECT_CMD = 'u'; + +const HAPIInt32 HapticManager::FRONT_BUTTON = (1 << 2); +const HAPIInt32 HapticManager::LEFT_BUTTON = (1 << 1); +const HAPIInt32 HapticManager::RIGHT_BUTTON = (1 << 3); +const HAPIInt32 HapticManager::REAR_BUTTON = (1 << 0); +const HAPIInt32 HapticManager::NO_BUTTON = 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/HapticManager.h Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,130 @@ +/* + 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/>. +*/ +#pragma once +#include "CollectionsManager.h" +#include <HAPI/AnyHapticsDevice.h> +#include <HAPI/GodObjectRenderer.h> + +#include <HAPI/HapticPointSet.h> +#include <HAPI/HapticLineSet.h> +#include <HAPI/HapticSpring.h> +#include <HAPI/HapticShapeConstraint.h> + +#include <GL/gl.h> +#include <GL/glu.h> +#include <GL/glut.h> + + + +class HapticManager +{ + CollectionsManager *cm ; + void (*executeCommand)(const jchar cmd, const jint ID, const jdouble startx, const jdouble starty, const jdouble endx, const jdouble endy); + HAPI::AnyHapticsDevice *hd; + /* The force effect in use. Contains 0 if there is no force effect in use. */ + H3DUtil::AutoRef<HAPI::HAPIForceEffect> force_effect; + /* the current set of haptic lines. It's used as argument when adding a constraint effect to the hapitc * + * device and it's filled up with the edges of the current diagram through the collection manager */ + vector< HAPI::Collision::LineSegment> line_set; + vector<HAPI::Collision::Point> point_set; + map<HAPI::Collision::Point*,int> point_id_map; + map<HAPI::Collision::LineSegment*,int> line_id_map; + /* in order not to utter continuosly the touched node/edge name, keep track of the node/edge that was * + * last touched and avoid to utter its name unless the proxy touches another node/edge or goes far enough */ + HAPI::Collision::Point* last_touched_point; + HAPI::Collision::LineSegment* last_touched_line; + HAPI::Vec3 proxy_pos; + HAPI::HAPIInt32 pressed_button; + + enum {STICKY, LOOSE} magnetic_mode; + bool magnetic_mode_changed; + + enum {START_DRAGGING, DRAGGING, RELEASED} pickup_mode; + HAPI::Collision::Point *pickedup_point; + HAPI::Collision::LineSegment *pickedup_line; + HAPI::Vec3 pickup_line_pos; + HAPI::Vec3 last_dragging_pos; + + int attract_to; + enum {NO_ATTRACTION ,STOP_ATTRACTION, ACTIVE} attraction_mode; + + bool object_unselected; + + /* how bigger graphic coordinates are than haptic coordinates */ + static const int GRAPHIC_SCALE; + static const int SPRING_FORCE_FACTOR; + static const int LINE_FORCE_FACTOR_LOOSE; + static const int LINE_FORCE_FACTOR_STICKY; + static const int POINT_FORCE_FACTOR; + static const double POINT_COLLISION_THRESHOLD; + static const double LINE_COLLISION_THRESHOLD; + static const double DRAGGING_SOUND_THRESHOLD; + + /* commands sent to the java thread */ + static const char MOVE_CMD; + static const char PLAY_SOUND_CMD; + static const char SPEAK_NAME_CMD; + static const char PICKUP_CMD; + static const char SPEAK_INFO_CMD; + static const char SELECT_CMD; + static const char UNSELECT_CMD; + + /* arguments for PLAY_SOUND_CMD */ + static const int LOOSE_MODE_SOUND; + static const int STICKY_MODE_SOUND; + static const int DRAGGING_SOUND; + + static const HAPI::HAPIInt32 FRONT_BUTTON; + static const HAPI::HAPIInt32 LEFT_BUTTON; + static const HAPI::HAPIInt32 RIGHT_BUTTON; + static const HAPI::HAPIInt32 REAR_BUTTON; + static const HAPI::HAPIInt32 NO_BUTTON; +public: + HapticManager(CollectionsManager * cManager, void (*func)(const jchar cmd, const jint ID, const jdouble startx, const jdouble starty, const jdouble endx, const jdouble endy)); + + /* try and initialize the haptic device, return false if the initialization fails */ + bool init(void); + + /* drawing routines (both graphically and haptically) */ + void drawCursor(void); + void drawDiagram(bool redrawHapticsScene,bool pickup, int attract_to); + + /* collisions routines: check if the proxy is touching a node or an edge */ + bool checkNodeCollision(void); + bool checkEdgeCollision(void); + + /* check if an unselection command must be issued. Commands cannot be issued in the drawDiagram * + * as it might possibly result in a deadlock. Therefore in drawDiagram the inner flag variable * + * object_unselected is set if necessary, and then when this method is called, the command is * + * issued if the flad was set to true */ + bool checkUnselection(void); + + /* check buttons routine */ + bool checkButtons(void); + + /* routine that plays chain sound during motion if an object is being dragged */ + bool checkMotion(); + + /* release the allocated resources */ + void dispose(void); + + /* a null id value for Java nodes and edges. The id used for nodes and edges is * + * the hashCode of the Java object, therefore a zero value will work for NO_ID */ + static const int NO_ID; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,401 @@ +/* + 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/>. +*/ + +#include "uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.h" +#include "HapticManager.h" +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <setjmp.h> +#include <GL/glut.h> +#ifdef FREEGLUT +#include <GL/freeglut.h> +#endif +#include "utils.h" + +/* enum to match against when checking for the jump result */ +enum {JMP_OK =0, JMP_EXIT }; + +/* Functions prototypes. */ +/* callbacks */ +void display(void); +void reshape(int width, int height); +void idle(void); +void exitProcedure(void); +void commandCallback(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); + +void initJniVariables(void); + +/* variables */ +static HapticManager *hManager; +static CollectionsManager *cManager; +static JNIEnv *env; +static jobject *lock; +static jmp_buf jmpenv; +static int jmpval = 0; +static bool reshaped = false; +/* jni variables */ +static jclass hapticClass; +static jclass hapticListenerClass; +static jfieldID shutdownFieldId; +static jfieldID hapticInitFailedfieldId; +static jfieldID collectionsChangedFieldId; +static jfieldID pickUpFieldId; +static jfieldID attractToFieldId; +/* hapticListener jni variables */ +static jfieldID hapticListenerFieldId; +static jfieldID cmdFieldId; // belongs to the haptic listener +static jfieldID diagramElementFieldId; // belongs to the haptic listener +static jfieldID xFieldId; // belongs to the haptic listener +static jfieldID yFieldId; // belongs to the haptic listener +static jfieldID startXFieldId; // belongs to the haptic listener +static jfieldID startYFieldId; // belongs to the haptic listener +static jobject hapticListener; +/* synchronization methods ids */ +static jmethodID notifyMethodId; +static jmethodID notifyListenerMethodId; +static jmethodID waitMethodId; + +/* Native initialization method for the Falcon Haptic device. Upon successful initialization a loop * + * is started that communicates with the Java program in order to represent the diagram haptically */ +JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_initFalcon + (JNIEnv *environment, jobject obj, jint w, jint h){ + + env = environment; + lock = &obj; + /* fake main argv and argc as this is a dll */ + char *argv[1] = {"FalconHaptics"}; + int argc = 1; + initJniVariables(); + + cManager = new CollectionsManager(env,lock); + cManager->init(); + hManager = new HapticManager(cManager,commandCallback); + + /* try to initialize the haptic, tell the java thread about the success/failure. The endeavour * + * takes the lock on the haptics java object in order to access the nodes and edges concurrently */ + if(env->MonitorEnter(*lock) != JNI_OK){ + stopExecution("Could not allocate memory for haptic thread monitor"); + } + checkExceptions(env, "Could not enter monitor on Haptics"); + + bool mustSayGoodbye = false; + + if(!hManager->init()){ + /* set the field in the java class to comunicate the main thread (waiting * + * for the initialization to complete) that the initialization failed */ + env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE); + mustSayGoodbye = true; + } + + if(mustSayGoodbye) + std::cout << "Failed to initialize Falcon haptic device" << std::endl; + else + std::cout << "Falcon haptic device successfully initialized" << std::endl; + + /* notify the java thread */ + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env,"Could not call notify() on Haptics"); + + /* release the lock */ + if(env->MonitorExit(*lock) != JNI_OK){ + std::cerr << "Could not release memory for haptic thread monitor" << std::endl; + exit(-1); + } + + if(mustSayGoodbye) + /* initialization failed: return */ + return -1; + + /* haptic device is initialized, now build the openGL window */ + + /* glut initialization */ + glutInit(&argc, argv); + + glutInitWindowSize( w, h ); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); + glutCreateWindow( "Falcon Haptics Window" ); + + /* set up GLUT callback functions */ + glutDisplayFunc ( display ); + glutReshapeFunc ( reshape ); + glutIdleFunc( idle ); + + /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ + jmpval = setjmp(jmpenv); + + /* start the loop*/ + if(jmpval == JMP_OK){ + glutMainLoop(); + } + + /* jmpval = JMP_EXIT, we're coming from the glut loop */ + exitProcedure(); + + return 0; +} + +/* GLUT display callback */ +void display(){ + /* takes the lock on the haptics java obejct in order to access the nodes and edges concurrently */ + if(env->MonitorEnter(*lock) != JNI_OK){ + stopExecution("Could not allocate memory for haptic thread monitor"); + } + checkExceptions(env,"Could not enter monitor on Haptics"); + + /* check if there is a shutdown request */ + if( env->GetBooleanField(*lock,shutdownFieldId) == JNI_TRUE ){ + // notify the other thread that this thread is about to die + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env, "Could not call notify() on Haptics"); + // release the lock + if(env->MonitorExit(*lock) != JNI_OK){ + std::cerr << "Could not release memory for haptic thread monitor" << std::endl; + } + longjmp(jmpenv,JMP_EXIT); + } + + /* check if the node and edges collections have changed since the last frame or if * + * the window has been reshaped. In either case the haptic scene must be rendered again */ + bool collectionsChanged = false; + if(env->GetBooleanField(*lock,collectionsChangedFieldId) == JNI_TRUE || reshaped){ + if(reshaped) + reshaped = false; + /* remember that collections have changed (will be used later in hManager->drawDiagram) */ + collectionsChanged = true; + /* set the Java boolean variable back to false */ + env->SetBooleanField(*lock,collectionsChangedFieldId,JNI_FALSE); + } + + /* check if the user picked up an object */ + jboolean picked_up = env->GetBooleanField(*lock,pickUpFieldId); + if(picked_up == JNI_TRUE){ + env->SetBooleanField(*lock,pickUpFieldId,JNI_FALSE); + } + + /* check if the user asked to be attracted to a node */ + jint attract_to = env->GetIntField(*lock,attractToFieldId); + if(attract_to != HapticManager::NO_ID){ + env->SetIntField(*lock,attractToFieldId,HapticManager::NO_ID); + } + + /* --- start drawing --- */ + /* Set up OpenGL state. */ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + glDepthMask(GL_TRUE); + glEnable(GL_CULL_FACE); + glShadeModel(GL_SMOOTH); + glEnable( GL_LIGHTING ); + glEnable(GL_LIGHT0); + glLoadIdentity(); + + gluLookAt(0, 0, 0.1, // eye position .05 + 0, 0, 0, // center position + 0, 1, 0); // which direction is up + glEnable( GL_NORMALIZE ); + + /* draw the diagram graphicly and, if the collection has changed, haptically too */ + hManager->drawCursor(); + hManager->drawDiagram(collectionsChanged,(picked_up == JNI_TRUE),attract_to); + + + /* release lock. HapticManager methods from now on can execute commands without deadlock risk */ + if(env->MonitorExit(*lock) != JNI_OK){ + stopExecution("Could not release memory for haptic thread monitor"); + } + /* check button status */ + hManager->checkButtons(); + + /* check if un unselect command is to be issued, for an object being touched has been deleted */ + hManager->checkUnselection(); + + /* check collisions. priority to nodes */ + bool node_collision = hManager->checkNodeCollision(); + if(!node_collision) // only one object at time can be touched + hManager->checkEdgeCollision(); + /* check motion feedback */ + hManager->checkMotion(); + + /* swap graphic buffers. */ + glutSwapBuffers(); +} + +/* GLUT reshape callback */ +void reshape (int w, int h){ + cManager->setScreenHeight(h); + cManager->setScreenWidth(w); + reshaped = true; + + static const double ANGLE = 100; + glViewport(0, 0, w, h); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(ANGLE, // angle + (float)w / h, // aspect ratio + 0.01, // near clipping plane .01 + 500);// far clipping plane 1000 + glMatrixMode(GL_MODELVIEW); +} + +/* GLUT idle callback */ +void idle(){ + glutPostRedisplay(); +} + +/* called before exit */ +void exitProcedure(){ + hManager->dispose(); + delete hManager; + delete cManager; +} + +/* initialize all the variables needed for the jni access to the Falcon Haptics class by the Haptic thread*/ +void initJniVariables(void){ + /* --- CLASSES --- */ + //this class + hapticClass = env->GetObjectClass(*lock); + if(hapticClass == NULL){ + stopExecution("Could not find the Haptics class"); + } + // the haptic listener, member of this class + hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); + if(hapticListenerClass == NULL){ + stopExecution("Could not find the haptic listener class"); + } + + /* --- FIELD IDS --- */ + + // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device + hapticInitFailedfieldId = env->GetFieldID(hapticClass,"initFailed", "Z"); + if(hapticInitFailedfieldId == NULL){ + stopExecution("failed to find the initFailed field id"); + } + + // boolean set by the java thread to notify this thread the program has been shut down + shutdownFieldId = env->GetFieldID(hapticClass, "shutdown", "Z"); + if(shutdownFieldId == NULL){ + stopExecution("failed to find the shutdownfieldId field id"); + } + + collectionsChangedFieldId = env->GetFieldID(hapticClass, "collectionsChanged", "Z"); + if(collectionsChangedFieldId == NULL){ + stopExecution("failed to find the collectionsChangedFieldId field id"); + } + + attractToFieldId = env->GetFieldID(hapticClass, "attractTo" , "I"); + if(attractToFieldId == NULL){ + stopExecution("failed to find the attractTo field id"); + } + + pickUpFieldId = env->GetFieldID(hapticClass,"pickUp","Z"); + if(pickUpFieldId == NULL){ + stopExecution("failed to find the pickUp field id"); + } + + hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); + if(hapticListenerFieldId == NULL){ + stopExecution("failed to find the hapticListenerThread field id"); + } + + cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C"); + if(cmdFieldId == NULL){ + stopExecution("failed to find the cmd field id of the hapticListener class"); + } + + diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I"); + if(diagramElementFieldId == NULL){ + stopExecution("failed to find the diagramElement field id of the hapticListener class"); + } + + xFieldId = env->GetFieldID(hapticListenerClass, "x", "D"); + if(xFieldId == NULL){ + stopExecution("failed to find the x field id of the hapticListener class"); + } + + yFieldId = env->GetFieldID(hapticListenerClass, "y", "D"); + if(yFieldId == NULL){ + stopExecution("failed to find the y field id of the hapticListener class"); + } + + startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D"); + if(startXFieldId == NULL){ + stopExecution("failed to find the x field id of the hapticListener class"); + } + + startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D"); + if(startYFieldId == NULL){ + stopExecution("failed to find the y field id of the hapticListener class"); + } + + hapticListener = env->GetObjectField(*lock,hapticListenerFieldId); + /* --- SYNCHRONIZATION METHODS ID --- */ + // notify() + notifyMethodId = env->GetMethodID(hapticClass,"notify","()V"); + if(notifyMethodId == NULL){ + stopExecution("failed to find the notify method id"); + } + + notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V"); + if(notifyListenerMethodId == NULL){ + stopExecution("failed to find the notify method id"); + } + // wait() + waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); + if(waitMethodId == NULL){ + stopExecution("failed to find the wait method id"); + } +} + +void commandCallback(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){ + /* the haptic listener java thread is waiting for commands + first set the variable the Haptic Listener java thread will read after being notified, + then notify and get it awake. Thus wait for the java thread to notify that the command + has been accomplished. This is done as otherwise some commands might be neglected. as if the thread + scheduler decides to execute twice this routine without executing the java thread in the middle then + the former command gets overwritten by the latter. + Note the monitor is hapticListener and not haptics as for the draw function. + When in this routine, this thread does not hold the lock on haptics as if a command results in changing + the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics. + Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this + thread to release the lock on haptics, that would result in a deadlock. + */ + if(env->MonitorEnter(hapticListener) != JNI_OK){ + stopExecution("Could not allocate memory for haptic listener thread monitor"); + } + checkExceptions(env,"Could not enter monitor on the haptic listener"); + /* Fill all the shared variables for informaton for the command to be executed */ + env->SetCharField(hapticListener,cmdFieldId,cmd); + env->SetIntField(hapticListener,diagramElementFieldId,ID); + env->SetDoubleField(hapticListener,xFieldId,x); + env->SetDoubleField(hapticListener,yFieldId,y); + env->SetDoubleField(hapticListener,startXFieldId,startX); + env->SetDoubleField(hapticListener,startYFieldId,startY); + // wake the java thread up to execute the command + env->CallVoidMethod(hapticListener,notifyListenerMethodId); + checkExceptions(env, "Could not call notify() on HapticListener"); + /* wait for the commands to be executed. Here is actually where the monitor is * + * freed and the java thread starts to execute the command, having been notified */ + env->CallVoidMethod(hapticListener,waitMethodId); + checkExceptions(env, "Could not call wait() on HapticListener"); + if(env->MonitorExit(hapticListener) != JNI_OK){ + stopExecution("Could not release memory for haptic listener thread monitor"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics.h Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,45 @@ +/* + 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/>. +*/ + +#include <jni.h> +/* Header for class uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics */ + +#ifndef _Included_uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics +#define _Included_uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics +#ifdef __cplusplus +extern "C" { +#endif +#undef uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_MIN_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_MIN_PRIORITY 1L +#undef uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_NORM_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_NORM_PRIORITY 5L +#undef uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_MAX_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_MAX_PRIORITY 10L +/* + * Class: uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics + * Method: init + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_FalconHaptics_initFalcon + (JNIEnv *, jobject, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/utils.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,75 @@ +/* + 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/>. +*/ + +#include "utils.h" + +#define MAX(a,b) ((a) < (b) ? (b) : (a)) +#define MIN(a,b) ((a) > (b) ? (b) : (a)) + +void checkExceptions(JNIEnv *env, char* what){ + if(env->ExceptionOccurred()){ + std::cout << "Exception occurred!!!" << std::endl; + std::cout << what << std::endl; + env->ExceptionDescribe(); + exit(-1); + } +} + +void stopExecution(char* msg){ + std::cerr << msg << std::endl; + exit(-1); +} + +H3DUtil::ArithmeticTypes::Vec3d & screenToHapticSpace(H3DUtil::ArithmeticTypes::Vec3d & aPoint, int w, int h){ + int scale = MAX(w,h); + double x = (aPoint.x * .1 / scale) - .05; + /* in Java Swing (0,0) is the top-left corner, whereas in openGL and HAPI * + * (0,0) is the bottom-left corner. Therefore the y must be converted */ + aPoint.y = h - aPoint.y; + double y = (aPoint.y * .1 / scale) - .05; + + aPoint.x = x; + aPoint.y = y; + return aPoint; +} + +H3DUtil::ArithmeticTypes::Vec3d & hapticToScreenSpace(H3DUtil::ArithmeticTypes::Vec3d & aPoint, int w, int h){ + int scale = MAX(w,h); + double x = (aPoint.x + .05) * scale * 10; + double y = (aPoint.y + .05) * scale * 10; + + aPoint.x = x; + /* in Java Swing (0,0) is the top-left corner, whereas in openGL and HAPI * + * (0,0) is the bottom-left corner. Therefore the y must be converted */ + aPoint.y = h - y; + return aPoint; +} + + +double pointsDistance(const H3DUtil::ArithmeticTypes::Vec3d & p, const H3DUtil::ArithmeticTypes::Vec3d & q){ + /* 2D vectors are assumed */ + return sqrt( (p.x - q.x)*(p.x - q.x) + (p.y - q.y)*(p.y - q.y) ) ; +} + +H3DUtil::ArithmeticTypes::Vec3d midPoint(const H3DUtil::ArithmeticTypes::Vec3d & start, const H3DUtil::ArithmeticTypes::Vec3d & end){ + /* 2D vectors are assumed */ + double dx = abs((start.x - end.x)/2); + double dy = abs((start.y - end.y)/2); + return H3DUtil::ArithmeticTypes::Vec3d( MIN(start.x,end.x)+dx , MIN(start.y,end.y)+dy , 0); +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/Falcon/utils.h Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,45 @@ +/* + 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/>. +*/ + +#pragma once + +#include <jni.h> +#include <iostream> +#include <H3DUtil/Vec3d.h> +#include <math.h> + +/* check for Java exception */ +void checkExceptions(JNIEnv *env, char* what); + +/* exit the program printing a message on the sttandard error */ +void stopExecution(char* msg); + +/* transforms the coordinates of aPoint from the screen space (the system used in the graphic diagram) * + * to the haptic space which is used by the Falcon and goes from about -0.05 to 0.05 on each axis */ +H3DUtil::ArithmeticTypes::Vec3d & screenToHapticSpace(H3DUtil::ArithmeticTypes::Vec3d & aPoint, int w, int h); + +/* does the inverse of screenToHapticSpace */ +H3DUtil::ArithmeticTypes::Vec3d & hapticToScreenSpace(H3DUtil::ArithmeticTypes::Vec3d & aPoint, int w, int h); + +/* Calculates the distance between points on a plane whose normal is the Z-axis. This * + * means that the z coordinate in not taken into account for the distance calculation */ +double pointsDistance(const H3DUtil::ArithmeticTypes::Vec3d & p, const H3DUtil::ArithmeticTypes::Vec3d & q); + +/* find the midpoint of a segment foing from start to end. The z coordinate of start and end is not taken into account */ +H3DUtil::ArithmeticTypes::Vec3d midPoint(const H3DUtil::ArithmeticTypes::Vec3d & start, const H3DUtil::ArithmeticTypes::Vec3d & end);
--- a/native/PhantomOmni/CollectionsManager.h Tue May 29 15:32:19 2012 +0100 +++ b/native/PhantomOmni/CollectionsManager.h Tue Jul 10 22:39:37 2012 +0100 @@ -134,7 +134,7 @@ bool isEdge(const jint id) const throw(int); bool isNode(const jint id) const throw(int); void init(void); - int getScreenHeight() const { return screenHeight;} - void setScreenHeight(const int sh) { screenHeight = sh; } + inline int getScreenHeight() const { return screenHeight;} + inline void setScreenHeight(const int sh) { screenHeight = sh; } };
--- a/native/PhantomOmni/HapticManager.cpp Tue May 29 15:32:19 2012 +0100 +++ b/native/PhantomOmni/HapticManager.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -447,7 +447,7 @@ } } - /* draw lines. When a node is moved, edges are not drawn for the very first haptic frame after the shift. * + /* draw lines. When a node is moved, edges are not drawn for the very first haptic frame after the shift. * * This is to address the behaviour of the device which, after a n ode is moved cannot see the styilus * * touching the edge. As a consequence of that in hlMotionCB() when moving away from a node along an edge * * no edge name will be spoken and furthermore the pick up botton won't work as the device thinks it's not *
--- a/native/PhantomOmni/Haptics.vcproj Tue May 29 15:32:19 2012 +0100 +++ b/native/PhantomOmni/Haptics.vcproj Tue Jul 10 22:39:37 2012 +0100 @@ -2,7 +2,7 @@ <VisualStudioProject ProjectType="Visual C++" Version="9.00" - Name="Haptics" + Name="OmniHaptics" ProjectGUID="{3FAB66F5-BA54-470F-9D4B-3E73E58A76BC}" RootNamespace="Haptics" TargetFrameworkVersion="131072" @@ -49,7 +49,7 @@ Name="VCCLCompilerTool" Optimization="2" InlineFunctionExpansion="1" - AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_25\include";"C:\Program Files\Java\jdk1.6.0_25\include\win32";include;"$(3DTOUCH_BASE)\include";"$(3DTOUCH_BASE)\utilities\include"" + AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_25\include";"C:\Program Files\Java\jdk1.6.0_25\include\win32";"$(3DTOUCH_BASE)\include";"$(3DTOUCH_BASE)\utilities\include"" PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" StringPooling="true" RuntimeLibrary="2" @@ -137,7 +137,7 @@ <Tool Name="VCCLCompilerTool" Optimization="0" - AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_24\include\win32";"C:\Program Files\Java\jdk1.6.0_24\include";include;"$(3DTOUCH_BASE)\include";"$(3DTOUCH_BASE)\utilities\include"" + AdditionalIncludeDirectories=""C:\Program Files\Java\jdk1.6.0_25\include\win32";"C:\Program Files\Java\jdk1.6.0_25\include";"$(3DTOUCH_BASE)\include";"$(3DTOUCH_BASE)\utilities\include"" PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" BasicRuntimeChecks="3" RuntimeLibrary="3" @@ -410,7 +410,7 @@ > </File> <File - RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_Haptics.cpp" + RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.cpp" > <FileConfiguration Name="Release|Win32" @@ -485,7 +485,7 @@ > </File> <File - RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_Haptics.h" + RelativePath=".\uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.h" > </File> <File
--- a/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.cpp Tue May 29 15:32:19 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,518 +0,0 @@ -#include "uk_ac_qmul_eecs_ccmi_haptics_Haptics.h" -#include "stdafx.h" -#include "GraphicManager.h" -#include "HapticManager.h" -#include "HapticException.h" -#include <stdlib.h> -#include <math.h> -#include <assert.h> -#include <setjmp.h> -#include "utils.h" - -#define CURSOR_SIZE_PIXELS 20 - -enum {JMP_OK =0, JMP_EXIT }; - -/************************** - Function prototypes. - ***************************/ -/* callbacks */ -void displayCallback(void); -void reshapeCallback(int width, int height); -void idleCallback(void); -void exitProcedure(void); -void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); - -void drawCursor(); -void updateWorkspace(); - -void initJniVariables(void); -/************************* - global variables - **************************/ -GraphicManager *gManager; -HapticManager *hManager; -CollectionsManager *cManager; -JNIEnv *env; -jobject *lock; -int width; -int height; -jmp_buf jmpenv; -int jmpval = 0; -/* jni variables */ -jclass hapticClass; -jclass hapticListenerClass; -jfieldID shutdownfieldId; -jfieldID newHapticIdfieldId; -jfieldID dumpHapticIdfieldId; -jfieldID currentHapticIdfieldId; -jfieldID hapticInitFailedfieldId; -jfieldID attractTofieldId; -jfieldID attractToHapticIdFieldId; -jfieldID pickUpfieldId; -jfieldID pickUpHapticIdFieldId; -jfieldID hapticListenerFieldId; -jfieldID cmdFieldId; // belongs to the haptic listener -jfieldID diagramElementFieldId; // belongs to the haptic listener -jfieldID xFieldId; // belongs to the haptic listener -jfieldID yFieldId; // belongs to the haptic listener -jfieldID startXFieldId; // belongs to the haptic listener -jfieldID startYFieldId; // belongs to the haptic listener -jobject hapticListener; -jmethodID notifyMethodId; -jmethodID notifyListenerMethodId; -jmethodID waitMethodId; - -/******************************************************************************* - Initializes GLUT for displaying a simple haptic scene. -*******************************************************************************/ -JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_init - (JNIEnv *environment, jobject obj, jint w, jint h){ - env = environment; - lock = &obj; - /* fake main argv and argc as this is a dll */ - char *argv[1] = {"OmniHaptics"}; - int argc = 1; - - initJniVariables(); - - /* glut initialization */ - glutInit(&argc, argv); - glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); - glutInitWindowSize(w, h); - glutCreateWindow("CCmI Diagram Haptics"); - - /* glut callbacks */ - glutDisplayFunc(displayCallback); - glutReshapeFunc(reshapeCallback); - glutIdleFunc(idleCallback); - - cManager = new CollectionsManager(env,lock); - hManager = new HapticManager(cManager,hapticCommandCB); - gManager = new GraphicManager(cManager,hManager); - cManager->init(); - gManager->init(); - - // try to initialize the haptic, tell the java thread about the success/failure. The endeavour - // takes the lock on the haptics java object in order to access the nodes and edges concurrently - if(env->MonitorEnter(*lock) != JNI_OK){ - stopExecution("Could not allocate memory for haptic thread monitor"); - } - checkExceptions(env, "Could not enter monitor on Haptics"); - - bool mustSayGoodbye = false; - try{ - hManager->init(); - }catch (HapticException e){ - env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE); - mustSayGoodbye = true; - } - - if(mustSayGoodbye) - std::cout << "Failed to initialize haptic device" << std::endl; - else - std::cout << "Haptic device successfully initialized" << std::endl; - - // notify the other thread - env->CallVoidMethod(*lock,notifyMethodId); - checkExceptions(env,"Could not call notify() on Haptics"); - - //release the lock - if(env->MonitorExit(*lock) != JNI_OK){ - std::cerr << "Could not release memory for haptic thread monitor" << std::endl; - exit(-1); - } - - if(mustSayGoodbye) - /* initialization failed: return */ - return -1; - - /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ - jmpval = setjmp(jmpenv); - - /* star the loop*/ - if(jmpval == JMP_OK){ - glutMainLoop(); - }else{ - exitProcedure(); - } - return 0; -} - -/******************************************************************************* - GLUT callback for redrawing the view. -*******************************************************************************/ -void displayCallback(){ - - // takes the lock on the haptics java obejct in order to access the nodes and edges concurrently - if(env->MonitorEnter(*lock) != JNI_OK){ - stopExecution("Could not allocate memory for haptic thread monitor"); - } - checkExceptions(env,"Could not enter monitor on Haptics"); - - // check if there is a shutdown request - if( env->GetBooleanField(*lock,shutdownfieldId) == JNI_TRUE ){ - // notify the other thread that this thread is about to die - env->CallVoidMethod(*lock,notifyMethodId); - checkExceptions(env, "Could not call notify() on Haptics"); - // release the lock - if(env->MonitorExit(*lock) != JNI_OK){ - std::cerr << "Could not release memory for haptic thread monitor" << std::endl; - } - longjmp(jmpenv,JMP_EXIT); - } - - // check if the user asked to be attracted to a node - jboolean attractTo = env->GetBooleanField(*lock,attractTofieldId); - jint attractToDiagramId = 0; - if(attractTo == JNI_TRUE){ - env->SetBooleanField(*lock,attractTofieldId,JNI_FALSE); - jint attractToHapticId = env->GetIntField(*lock,attractToHapticIdFieldId); - if(cManager->isNode(attractToHapticId)){ - attractToDiagramId = cManager->getNodeDataFromID(attractToHapticId).diagramId; - }else{ - attractToDiagramId = cManager->getEdgeDataFromID(attractToHapticId).diagramId; - } - hManager->setAttractTo(attractToHapticId); - } - // check if the user picked up a node - jboolean pickUp = env->GetBooleanField(*lock,pickUpfieldId); - jint pickUpDiagramId = 0; - if(pickUp == JNI_TRUE){ - env->SetBooleanField(*lock,pickUpfieldId,JNI_FALSE); - jint pickUpHapticId = env->GetIntField(*lock,pickUpHapticIdFieldId); - if(cManager->isNode(pickUpHapticId)){ - pickUpDiagramId = cManager->getNodeDataFromID(pickUpHapticId).diagramId; - }else{ - pickUpDiagramId = cManager->getEdgeDataFromID(pickUpHapticId).diagramId; - } - hManager->pickUp(pickUpDiagramId); - } - - // draw the scene graphically and haptically - hManager->draw(); - gManager->draw(); - - // check whether the java thread needs to either create or dump an haptic id - jboolean needsNewHapticId = env->GetBooleanField(*lock,newHapticIdfieldId); - if(needsNewHapticId == JNI_TRUE){ - // set int currentHapticId of class Haptics with a new generated id - jint newHapticid = hlGenShapes(1); - env->SetIntField(*lock, currentHapticIdfieldId, newHapticid); - //set the boolean field to false as the other thread now has an id - env->SetBooleanField(*lock, newHapticIdfieldId, JNI_FALSE); - //notify the other thread - env->CallVoidMethod(*lock,notifyMethodId); - checkExceptions(env, "Could not call notify() on Haptics"); - } - jboolean needsDumpOldHapticId = env->GetBooleanField(*lock, dumpHapticIdfieldId); - if(needsDumpOldHapticId == JNI_TRUE){ - // get the id of the deleted element from the other thread - jint oldHapticId = env->GetIntField(*lock,currentHapticIdfieldId); - // free the old haptic id - hlDeleteShapes(oldHapticId,1); - // set the boolean field as the id has been cleaned up - env->SetBooleanField(*lock,dumpHapticIdfieldId,JNI_FALSE); - // notify the other thread - env->CallVoidMethod(*lock,notifyMethodId); - checkExceptions(env, "Could not call notify() on Haptics"); - } - - /* release lock */ - if(env->MonitorExit(*lock) != JNI_OK){ - stopExecution("Could not release memory for haptic thread monitor"); - } - - /* it's important that this call be outside the monitors, else a deadlock occurs */ - // Call any event callbacks that have been triggered. - if(attractTo == JNI_TRUE){ - if(hManager->wasAlreadyTouchingAttractingElement()){ - hapticCommandCB('t',attractToDiagramId,0,0,0,0); - } - } - - hlCheckEvents(); - HapticManager::ClickStatus click = hManager->getButton1Status(); - if(click == HapticManager::ONE_CLICK){ - hManager->doButton1Click(false); - }else if(click == HapticManager::TWO_CLICK){ - hManager->doButton1Click(true); - } - - glutSwapBuffers(); -} -/******************************************************************************* - GLUT callback for reshaping the window. This is the main place where the - viewing and workspace transforms get initialized. -*******************************************************************************/ -void reshapeCallback(int w, int h){ - static const double kPI = 3.1415926535897932384626433832795; - static const double kFovY = 40; - - static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */); - static const double farDist = nearDist + 2.0; - double aspect; - width = w; - height = h; - - cManager->setScreenHeight(h); - glViewport(0, 0, width, height); - // Compute the viewing parameters based on a fixed fov and viewing - // a canonical box centered at the origin. - aspect = (double) width/height ; - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(kFovY, aspect,nearDist, farDist); - // Place the camera down the Z axis looking at the origin. - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - gluLookAt(0, 0, nearDist + 1.0, - 0, 0, 0, - 0, 1, 0); - - hduVector3Dd origin; - fromScreen(hduVector3Dd(0, 0, 0),origin); - glLoadIdentity(); - gluLookAt(-origin[0],origin[1], nearDist + 1.0, - -origin[0],origin[1], 0, - 0, 1, 0); - - hduVector3Dd end; - fromScreen(hduVector3Dd(w, h, 0),end); - - //glTranslatef(end[0]-origin[0],-(end[1]-origin[1]),0); - - updateWorkspace(); -} - - -/******************************************************************************* - Use the current OpenGL viewing transforms to initialize a transform for the - haptic device workspace so that it's properly mapped to world coordinates. -*******************************************************************************/ -void updateWorkspace(){ - GLdouble modelview[16]; - GLdouble projection[16]; - GLint viewport[4]; - - glGetDoublev(GL_MODELVIEW_MATRIX, modelview); - glGetDoublev(GL_PROJECTION_MATRIX, projection); - glGetIntegerv(GL_VIEWPORT, viewport); - - hlMatrixMode(HL_TOUCHWORKSPACE); - hlLoadIdentity(); - - // Fit haptic workspace to view volume. - hluFitWorkspace(projection); - - // Compute cursor scale. - gManager->gCursorScale = hluScreenToModelScale(modelview, projection, viewport); - gManager->gCursorScale *= CURSOR_SIZE_PIXELS; - - hduVector3Dd p0, p1; - bool bNoError; - - bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0); - assert(bNoError); - - bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1); - assert(bNoError); - - double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0); - gManager->gWorldScale = m_windowTworldScale; -} -/******************************************************************************* - GLUT callback for idle state. Use this as an opportunity to request a redraw. - Checks for HLAPI errors that have occurred since the last idle check. -*******************************************************************************/ -void idleCallback(){ - HLerror error; - - while (HL_ERROR(error = hlGetError())){ - std::cerr << "HL Error: " << error.errorCode << std::endl <<error.errorInfo << std::endl; - - if (error.errorCode == HL_DEVICE_ERROR){ - hduPrintError(stderr, &error.errorInfo, "Device error\n"); - std::cout << "sending error message to haptic listener" << std::endl; - hapticCommandCB('e',0,0,0,0,0); - } - } - glutPostRedisplay(); -} - -/******************************************************************************* - This handler is called when the application is exiting. Deallocates any state - and cleans up. -*******************************************************************************/ -void exitProcedure(){ - - // Free up the haptic rendering context. - hlMakeCurrent(NULL); - - if (HapticManager::ghHLRC != NULL){ - hlDeleteContext(HapticManager::ghHLRC); - } - - // Free up the haptic device. - if (HapticManager::ghHD != HD_INVALID_HANDLE){ - hdDisableDevice(HapticManager::ghHD); - } - std::cout << "freeing haptic resources" << std::endl; -} - -/* initialize all the variable needed for the jni access to the Haptics class from the openGL thread*/ -void initJniVariables(void){ - /* --- CLASSES --- */ - //this class - hapticClass = env->GetObjectClass(*lock); - if(hapticClass == NULL){ - stopExecution("Could not find the Haptics class"); - } - // the haptic listener, member of this class - hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListener;"); - if(hapticListenerClass == NULL){ - stopExecution("Could not find the haptic listener class"); - } - - /* --- FIELD IDS --- */ - // boolean set by the java thread when an element is added and it needs a new id from the haptic library - newHapticIdfieldId = env->GetFieldID(hapticClass,"newHapticId", "Z"); - if(newHapticIdfieldId == NULL){ - stopExecution("failed to find the newHapticId field id"); - } - - // boolean set by the java thread when an element is added and it needs a new id from the haptic library - dumpHapticIdfieldId = env->GetFieldID(hapticClass,"dumpHapticId", "Z"); - if(dumpHapticIdfieldId == NULL){ - stopExecution("failed to find the dumpHapticId field id"); - } - - // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device - hapticInitFailedfieldId = env->GetFieldID(hapticClass,"hapticInitFailed", "Z"); - if(hapticInitFailedfieldId == NULL){ - stopExecution("failed to find the hapticInitFailedfieldId field id"); - } - - // boolean set by the java thread to notify this thread the program has been shut down - shutdownfieldId = env->GetFieldID(hapticClass, "shutdown", "Z"); - if(shutdownfieldId == NULL){ - stopExecution("failed to find the shutdownfieldId field id"); - } - - // boolean set by the java thread when the user asks to sna - attractTofieldId = env->GetFieldID(hapticClass, "attractTo" , "Z"); - if(shutdownfieldId == NULL){ - stopExecution("failed to find the attractTo field id"); - } - - attractToHapticIdFieldId = env->GetFieldID(hapticClass, "attractToHapticId", "I"); - if(attractToHapticIdFieldId == NULL){ - stopExecution("failed to find the attractToHapticId field id"); - } - - pickUpfieldId = env->GetFieldID(hapticClass,"pickUp","Z"); - if(pickUpfieldId == NULL){ - stopExecution("failed to find the pickUp field id"); - } - - pickUpHapticIdFieldId = env->GetFieldID(hapticClass,"pickUpHapticId","I"); - if(pickUpHapticIdFieldId == NULL){ - stopExecution("failed to find pickUpHapticId field id"); - } - - hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListener;"); - if(hapticListenerFieldId == NULL){ - stopExecution("failed to find the hapticListener field id"); - } - - // variable to exchange values between threads - currentHapticIdfieldId = env->GetFieldID(hapticClass,"currentHapticId","I"); - if(currentHapticIdfieldId == NULL){ - stopExecution("failed to find the currentHapticId field"); - } - - cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C"); - if(cmdFieldId == NULL){ - stopExecution("failed to find the cmd field id of the hapticListener class"); - } - - diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I"); - if(diagramElementFieldId == NULL){ - stopExecution("failed to find the diagramElement field id of the hapticListener class"); - } - - xFieldId = env->GetFieldID(hapticListenerClass, "x", "D"); - if(xFieldId == NULL){ - stopExecution("failed to find the x field id of the hapticListener class"); - } - - yFieldId = env->GetFieldID(hapticListenerClass, "y", "D"); - if(yFieldId == NULL){ - stopExecution("failed to find the y field id of the hapticListener class"); - } - - startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D"); - if(startXFieldId == NULL){ - stopExecution("failed to find the x field id of the hapticListener class"); - } - - startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D"); - if(startYFieldId == NULL){ - stopExecution("failed to find the y field id of the hapticListener class"); - } - - hapticListener = env->GetObjectField(*lock,hapticListenerFieldId); - /* --- METHOD IDs --- */ - // notify method - notifyMethodId = env->GetMethodID(hapticClass,"notify","()V"); - if(notifyMethodId == NULL){ - stopExecution("failed to find the notify method id"); - } - - notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V"); - if(notifyListenerMethodId == NULL){ - stopExecution("failed to find the notify method id"); - } - - waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); - if(waitMethodId == NULL){ - stopExecution("failed to find the wait method id"); - } -} - -void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){ - /* the haptic listener java thread is waiting for commands - first set the variable, the Haptic Listener java thread will read after being notified, - then notify and get it awake. Thus wait for the java thread to notify that the command - has been accomplished. This is done as otherwise some commands might be neglected. as if the thread - scheduler decides to execute twice this routine without executing the java thread in the middle then - the former command gets overwritten by the latter. - Note the monitor is hapticListener and not haptics as for the draw function. - When in this routine, this thread does not hold the lock on haptics as if a command results in changing - the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics. - Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this - thread to release the lock on haptics, that would result in a deadlock. - */ - /* now wake up the haptic listener */ - if(env->MonitorEnter(hapticListener) != JNI_OK){ - stopExecution("Could not allocate memory for haptic listener thread monitor"); - } - checkExceptions(env,"Could not enter monitor on the haptic listener"); - env->SetCharField(hapticListener,cmdFieldId,cmd); - env->SetIntField(hapticListener,diagramElementFieldId,ID); - env->SetDoubleField(hapticListener,xFieldId,x); - env->SetDoubleField(hapticListener,yFieldId,y); - env->SetDoubleField(hapticListener,startXFieldId,startX); - env->SetDoubleField(hapticListener,startYFieldId,startY); - // wake the java thread up to execute the command - env->CallVoidMethod(hapticListener,notifyListenerMethodId); - checkExceptions(env, "Could not call notify() on HapticListener"); - /* wait for the commands to be executed. Here is actually where the monitor is - * freed and the java thread starts to execute the command, having been notified */ - env->CallVoidMethod(hapticListener,waitMethodId); - checkExceptions(env, "Could not call wait() on HapticListener"); - if(env->MonitorExit(hapticListener) != JNI_OK){ - stopExecution("Could not release memory for haptic listener thread monitor"); - } -} \ No newline at end of file
--- a/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_Haptics.h Tue May 29 15:32:19 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,27 +0,0 @@ -/* DO NOT EDIT THIS FILE - it is machine generated */ -#include <jni.h> -/* Header for class uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics */ - -#ifndef _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics -#define _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics -#ifdef __cplusplus -extern "C" { -#endif -#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY -#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY 1L -#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY -#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY 5L -#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY -#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY 10L -/* - * Class: uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics - * Method: init - * Signature: (II)I - */ -JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_init - (JNIEnv *, jobject, jint, jint); - -#ifdef __cplusplus -} -#endif -#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.cpp Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,524 @@ +#include "uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.h" +#include "stdafx.h" +#include "GraphicManager.h" +#include "HapticManager.h" +#include "HapticException.h" +#include <stdlib.h> +#include <math.h> +#include <assert.h> +#include <setjmp.h> +#include "utils.h" + +#define CURSOR_SIZE_PIXELS 20 + +enum {JMP_OK =0, JMP_EXIT }; + +/************************** + Function prototypes. + ***************************/ +/* callbacks */ +void displayCallback(void); +void reshapeCallback(int width, int height); +void idleCallback(void); +void exitProcedure(void); +void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y,const jdouble startX, const jdouble startY); + +void drawCursor(); +void updateWorkspace(); + +void initJniVariables(void); +/************************* + global variables + **************************/ +static GraphicManager *gManager; +static HapticManager *hManager; +static CollectionsManager *cManager; +JNIEnv *env; +jobject *lock; +static int width; +static int height; +static jmp_buf jmpenv; +static int jmpval = 0; +/* jni variables */ +jclass hapticClass; +jclass hapticListenerClass; +jfieldID shutdownfieldId; +jfieldID newHapticIdfieldId; +jfieldID dumpHapticIdfieldId; +jfieldID currentHapticIdfieldId; +jfieldID hapticInitFailedfieldId; +jfieldID attractTofieldId; +jfieldID attractToHapticIdFieldId; +jfieldID pickUpfieldId; +jfieldID pickUpHapticIdFieldId; +jfieldID hapticListenerFieldId; +jfieldID cmdFieldId; // belongs to the haptic listener +jfieldID diagramElementFieldId; // belongs to the haptic listener +jfieldID xFieldId; // belongs to the haptic listener +jfieldID yFieldId; // belongs to the haptic listener +jfieldID startXFieldId; // belongs to the haptic listener +jfieldID startYFieldId; // belongs to the haptic listener +jobject hapticListener; +jmethodID notifyMethodId; +jmethodID notifyListenerMethodId; +jmethodID waitMethodId; + +/******************************************************************************* + Initializes GLUT for displaying a simple haptic scene. +*******************************************************************************/ +JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_initOmni + (JNIEnv *environment, jobject obj, jint w, jint h){ + env = environment; + lock = &obj; + /* fake main argv and argc as this is a dll */ + char *argv[1] = {"OmniHaptics"}; + int argc = 1; + + initJniVariables(); + + /* glut initialization */ + glutInit(&argc, argv); + glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); + glutInitWindowSize(w, h); + glutCreateWindow("CCmI Diagram Haptics"); + + /* glut callbacks */ + glutDisplayFunc(displayCallback); + glutReshapeFunc(reshapeCallback); + glutIdleFunc(idleCallback); + + cManager = new CollectionsManager(env,lock); + hManager = new HapticManager(cManager,hapticCommandCB); + gManager = new GraphicManager(cManager,hManager); + cManager->init(); + gManager->init(); + + // try to initialize the haptic, tell the java thread about the success/failure. The endeavour + // takes the lock on the haptics java object in order to access the nodes and edges concurrently + if(env->MonitorEnter(*lock) != JNI_OK){ + stopExecution("Could not allocate memory for haptic thread monitor"); + } + checkExceptions(env, "Could not enter monitor on Haptics"); + + bool mustSayGoodbye = false; + try{ + hManager->init(); + }catch (HapticException e){ + /* set the field in the java class to comunicate the main thread (waiting * + * for the initialization to complete) that the initialization failed */ + env->SetBooleanField(*lock,hapticInitFailedfieldId,JNI_TRUE); + mustSayGoodbye = true; + } + + if(mustSayGoodbye) + std::cout << "Failed to initialize Omni Haptic device" << std::endl; + else + std::cout << "Omni Haptic device successfully initialized" << std::endl; + + // notify the other thread + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env,"Could not call notify() on Haptics"); + + //release the lock + if(env->MonitorExit(*lock) != JNI_OK){ + std::cerr << "Could not release memory for haptic thread monitor" << std::endl; + exit(-1); + } + + if(mustSayGoodbye) + /* initialization failed: return */ + return -1; + + /* use setjmp to be able to jump off the glutMainLoop when the user shuts the program down */ + jmpval = setjmp(jmpenv); + + /* star the loop*/ + if(jmpval == JMP_OK){ + glutMainLoop(); + } + + exitProcedure(); + + return 0; +} + +/******************************************************************************* + GLUT callback for redrawing the view. +*******************************************************************************/ +void displayCallback(){ + + // takes the lock on the haptics java obejct in order to access the nodes and edges concurrently + if(env->MonitorEnter(*lock) != JNI_OK){ + stopExecution("Could not allocate memory for haptic thread monitor"); + } + checkExceptions(env,"Could not enter monitor on Haptics"); + + // check if there is a shutdown request + if( env->GetBooleanField(*lock,shutdownfieldId) == JNI_TRUE ){ + // notify the other thread that this thread is about to die + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env, "Could not call notify() on Haptics"); + // release the lock + if(env->MonitorExit(*lock) != JNI_OK){ + std::cerr << "Could not release memory for haptic thread monitor" << std::endl; + } + longjmp(jmpenv,JMP_EXIT); + } + + // check if the user asked to be attracted to a node + jboolean attractTo = env->GetBooleanField(*lock,attractTofieldId); + jint attractToDiagramId = 0; + if(attractTo == JNI_TRUE){ + env->SetBooleanField(*lock,attractTofieldId,JNI_FALSE); + jint attractToHapticId = env->GetIntField(*lock,attractToHapticIdFieldId); + if(cManager->isNode(attractToHapticId)){ + attractToDiagramId = cManager->getNodeDataFromID(attractToHapticId).diagramId; + }else{ + attractToDiagramId = cManager->getEdgeDataFromID(attractToHapticId).diagramId; + } + hManager->setAttractTo(attractToHapticId); + } + // check if the user picked up a node + jboolean pickUp = env->GetBooleanField(*lock,pickUpfieldId); + jint pickUpDiagramId = 0; + if(pickUp == JNI_TRUE){ + env->SetBooleanField(*lock,pickUpfieldId,JNI_FALSE); + jint pickUpHapticId = env->GetIntField(*lock,pickUpHapticIdFieldId); + if(cManager->isNode(pickUpHapticId)){ + pickUpDiagramId = cManager->getNodeDataFromID(pickUpHapticId).diagramId; + }else{ + pickUpDiagramId = cManager->getEdgeDataFromID(pickUpHapticId).diagramId; + } + hManager->pickUp(pickUpDiagramId); + } + + // draw the scene graphically and haptically + hManager->draw(); + gManager->draw(); + + // check whether the java thread needs to either create or dump an haptic id + jboolean needsNewHapticId = env->GetBooleanField(*lock,newHapticIdfieldId); + if(needsNewHapticId == JNI_TRUE){ + // set int currentHapticId of class Haptics with a new generated id + jint newHapticid = hlGenShapes(1); + env->SetIntField(*lock, currentHapticIdfieldId, newHapticid); + //set the boolean field to false as the other thread now has an id + env->SetBooleanField(*lock, newHapticIdfieldId, JNI_FALSE); + //notify the other thread + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env, "Could not call notify() on Haptics"); + } + jboolean needsDumpOldHapticId = env->GetBooleanField(*lock, dumpHapticIdfieldId); + if(needsDumpOldHapticId == JNI_TRUE){ + // get the id of the deleted element from the other thread + jint oldHapticId = env->GetIntField(*lock,currentHapticIdfieldId); + // free the old haptic id + hlDeleteShapes(oldHapticId,1); + // set the boolean field as the id has been cleaned up + env->SetBooleanField(*lock,dumpHapticIdfieldId,JNI_FALSE); + // notify the other thread + env->CallVoidMethod(*lock,notifyMethodId); + checkExceptions(env, "Could not call notify() on Haptics"); + } + + /* release lock */ + if(env->MonitorExit(*lock) != JNI_OK){ + stopExecution("Could not release memory for haptic thread monitor"); + } + + /* it's important that this call be outside the monitors, else a deadlock occurs */ + // Call any event callbacks that have been triggered. + if(attractTo == JNI_TRUE){ + if(hManager->wasAlreadyTouchingAttractingElement()){ + hapticCommandCB('t',attractToDiagramId,0,0,0,0); + } + } + + hlCheckEvents(); + /* button one is not based on events only , but also on time lapse as the single click is effective * + * after an amount of time (if another click is not issued). Therefore we need to continuosly for the * + * button status, hence the work must be done here and not in the click callbacks */ + HapticManager::ClickStatus click = hManager->getButton1Status(); + if(click == HapticManager::ONE_CLICK){ + hManager->doButton1Click(false); + }else if(click == HapticManager::TWO_CLICK){ + hManager->doButton1Click(true); + } + + glutSwapBuffers(); +} +/******************************************************************************* + GLUT callback for reshaping the window. This is the main place where the + viewing and workspace transforms get initialized. +*******************************************************************************/ +void reshapeCallback(int w, int h){ + static const double kPI = 3.1415926535897932384626433832795; + static const double kFovY = 40; + + static const double nearDist = 1.0 / tan((kFovY / 2.0) * kPI / 180.0 /* radiants for 1 degree */); + static const double farDist = nearDist + 2.0; + double aspect; + width = w; + height = h; + + cManager->setScreenHeight(h); + glViewport(0, 0, width, height); + // Compute the viewing parameters based on a fixed fov and viewing + // a canonical box centered at the origin. + aspect = (double) width/height ; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluPerspective(kFovY, aspect,nearDist, farDist); + // Place the camera down the Z axis looking at the origin. + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + gluLookAt(0, 0, nearDist + 1.0, + 0, 0, 0, + 0, 1, 0); + + hduVector3Dd origin; + fromScreen(hduVector3Dd(0, 0, 0),origin); + glLoadIdentity(); + gluLookAt(-origin[0],origin[1], nearDist + 1.0, + -origin[0],origin[1], 0, + 0, 1, 0); + + hduVector3Dd end; + fromScreen(hduVector3Dd(w, h, 0),end); + + //glTranslatef(end[0]-origin[0],-(end[1]-origin[1]),0); + + updateWorkspace(); +} + + +/******************************************************************************* + Use the current OpenGL viewing transforms to initialize a transform for the + haptic device workspace so that it's properly mapped to world coordinates. +*******************************************************************************/ +void updateWorkspace(){ + GLdouble modelview[16]; + GLdouble projection[16]; + GLint viewport[4]; + + glGetDoublev(GL_MODELVIEW_MATRIX, modelview); + glGetDoublev(GL_PROJECTION_MATRIX, projection); + glGetIntegerv(GL_VIEWPORT, viewport); + + hlMatrixMode(HL_TOUCHWORKSPACE); + hlLoadIdentity(); + + // Fit haptic workspace to view volume. + hluFitWorkspace(projection); + + // Compute cursor scale. + gManager->gCursorScale = hluScreenToModelScale(modelview, projection, viewport); + gManager->gCursorScale *= CURSOR_SIZE_PIXELS; + + hduVector3Dd p0, p1; + bool bNoError; + + bNoError = fromScreen(hduVector3Dd(0, 0, 0), p0); + assert(bNoError); + + bNoError = fromScreen(hduVector3Dd(1, 1, 0), p1); + assert(bNoError); + + double m_windowTworldScale = (p1 - p0).magnitude() / sqrt(2.0); + gManager->gWorldScale = m_windowTworldScale; +} +/******************************************************************************* + GLUT callback for idle state. Use this as an opportunity to request a redraw. + Checks for HLAPI errors that have occurred since the last idle check. +*******************************************************************************/ +void idleCallback(){ + HLerror error; + + while (HL_ERROR(error = hlGetError())){ + std::cerr << "HL Error: " << error.errorCode << std::endl <<error.errorInfo << std::endl; + + if (error.errorCode == HL_DEVICE_ERROR){ + hduPrintError(stderr, &error.errorInfo, "Device error\n"); + std::cout << "sending error message to haptic listener" << std::endl; + hapticCommandCB('e',0,0,0,0,0); + } + } + glutPostRedisplay(); +} + +/******************************************************************************* + This handler is called when the application is exiting. Deallocates any state + and cleans up. +*******************************************************************************/ +void exitProcedure(){ + + // Free up the haptic rendering context. + hlMakeCurrent(NULL); + + if (HapticManager::ghHLRC != NULL){ + hlDeleteContext(HapticManager::ghHLRC); + } + + // Free up the haptic device. + if (HapticManager::ghHD != HD_INVALID_HANDLE){ + hdDisableDevice(HapticManager::ghHD); + } + std::cout << "freeing haptic resources" << std::endl; +} + +/* initialize all the variable needed for the jni access to the Haptics class from the openGL thread*/ +void initJniVariables(void){ + /* --- CLASSES --- */ + //this class + hapticClass = env->GetObjectClass(*lock); + if(hapticClass == NULL){ + stopExecution("Could not find the Haptics class"); + } + // the haptic listener, member of this class + hapticListenerClass = env->FindClass("Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); + if(hapticListenerClass == NULL){ + stopExecution("Could not find the haptic listener class"); + } + + /* --- FIELD IDS --- */ + // boolean set by the java thread when an element is added and it needs a new id from the haptic library + newHapticIdfieldId = env->GetFieldID(hapticClass,"newHapticId", "Z"); + if(newHapticIdfieldId == NULL){ + stopExecution("failed to find the newHapticId field id"); + } + + // boolean set by the java thread when an element is added and it needs a new id from the haptic library + dumpHapticIdfieldId = env->GetFieldID(hapticClass,"dumpHapticId", "Z"); + if(dumpHapticIdfieldId == NULL){ + stopExecution("failed to find the dumpHapticId field id"); + } + + // boolean set to this thread to notify the java thread the unsuccessful initialization of haptic device + hapticInitFailedfieldId = env->GetFieldID(hapticClass,"hapticInitFailed", "Z"); + if(hapticInitFailedfieldId == NULL){ + stopExecution("failed to find the hapticInitFailedfieldId field id"); + } + + // boolean set by the java thread to notify this thread the program has been shut down + shutdownfieldId = env->GetFieldID(hapticClass, "shutdown", "Z"); + if(shutdownfieldId == NULL){ + stopExecution("failed to find the shutdownfieldId field id"); + } + + // boolean set by the java thread when the user asks to sna + attractTofieldId = env->GetFieldID(hapticClass, "attractTo" , "Z"); + if(attractTofieldId == NULL){ + stopExecution("failed to find the attractTo field id"); + } + + attractToHapticIdFieldId = env->GetFieldID(hapticClass, "attractToHapticId", "I"); + if(attractToHapticIdFieldId == NULL){ + stopExecution("failed to find the attractToHapticId field id"); + } + + pickUpfieldId = env->GetFieldID(hapticClass,"pickUp","Z"); + if(pickUpfieldId == NULL){ + stopExecution("failed to find the pickUp field id"); + } + + pickUpHapticIdFieldId = env->GetFieldID(hapticClass,"pickUpHapticId","I"); + if(pickUpHapticIdFieldId == NULL){ + stopExecution("failed to find pickUpHapticId field id"); + } + + hapticListenerFieldId = env->GetFieldID(hapticClass, "hapticListener", "Luk/ac/qmul/eecs/ccmi/haptics/HapticListenerThread;"); + if(hapticListenerFieldId == NULL){ + stopExecution("failed to find the hapticListenerThread field id"); + } + + // variable to exchange values between threads + currentHapticIdfieldId = env->GetFieldID(hapticClass,"currentHapticId","I"); + if(currentHapticIdfieldId == NULL){ + stopExecution("failed to find the currentHapticId field"); + } + + cmdFieldId = env->GetFieldID(hapticListenerClass,"cmd", "C"); + if(cmdFieldId == NULL){ + stopExecution("failed to find the cmd field id of the hapticListener class"); + } + + diagramElementFieldId = env->GetFieldID(hapticListenerClass, "diagramElementID", "I"); + if(diagramElementFieldId == NULL){ + stopExecution("failed to find the diagramElement field id of the hapticListener class"); + } + + xFieldId = env->GetFieldID(hapticListenerClass, "x", "D"); + if(xFieldId == NULL){ + stopExecution("failed to find the x field id of the hapticListener class"); + } + + yFieldId = env->GetFieldID(hapticListenerClass, "y", "D"); + if(yFieldId == NULL){ + stopExecution("failed to find the y field id of the hapticListener class"); + } + + startXFieldId = env->GetFieldID(hapticListenerClass, "startX", "D"); + if(startXFieldId == NULL){ + stopExecution("failed to find the x field id of the hapticListener class"); + } + + startYFieldId = env->GetFieldID(hapticListenerClass, "startY", "D"); + if(startYFieldId == NULL){ + stopExecution("failed to find the y field id of the hapticListener class"); + } + + hapticListener = env->GetObjectField(*lock,hapticListenerFieldId); + /* --- METHOD IDs --- */ + // notify method + notifyMethodId = env->GetMethodID(hapticClass,"notify","()V"); + if(notifyMethodId == NULL){ + stopExecution("failed to find the notify method id"); + } + + notifyListenerMethodId = env->GetMethodID(hapticListenerClass,"notify","()V"); + if(notifyListenerMethodId == NULL){ + stopExecution("failed to find the notify method id"); + } + + waitMethodId = env->GetMethodID(hapticListenerClass,"wait","()V"); + if(waitMethodId == NULL){ + stopExecution("failed to find the wait method id"); + } +} + +void hapticCommandCB(const jchar cmd, const jint ID, const jdouble x, const jdouble y, const jdouble startX, const jdouble startY){ + /* the haptic listener java thread is waiting for commands + first set the variable, the Haptic Listener java thread will read after being notified, + then notify and get it awake. Thus wait for the java thread to notify that the command + has been accomplished. This is done as otherwise some commands might be neglected. as if the thread + scheduler decides to execute twice this routine without executing the java thread in the middle then + the former command gets overwritten by the latter. + Note the monitor is hapticListener and not haptics as for the draw function. + When in this routine, this thread does not hold the lock on haptics as if a command results in changing + the elements collections (e.g. moveNode) all those methods are synchronized and require to acquire the lock on haptics. + Since this thread would wait for the command to be executed by the java thread, which in turns would wait for this + thread to release the lock on haptics, that would result in a deadlock. + */ + /* now wake up the haptic listener */ + if(env->MonitorEnter(hapticListener) != JNI_OK){ + stopExecution("Could not allocate memory for haptic listener thread monitor"); + } + checkExceptions(env,"Could not enter monitor on the haptic listener"); + env->SetCharField(hapticListener,cmdFieldId,cmd); + env->SetIntField(hapticListener,diagramElementFieldId,ID); + env->SetDoubleField(hapticListener,xFieldId,x); + env->SetDoubleField(hapticListener,yFieldId,y); + env->SetDoubleField(hapticListener,startXFieldId,startX); + env->SetDoubleField(hapticListener,startYFieldId,startY); + // wake the java thread up to execute the command + env->CallVoidMethod(hapticListener,notifyListenerMethodId); + checkExceptions(env, "Could not call notify() on HapticListener"); + /* wait for the commands to be executed. Here is actually where the monitor is + * freed and the java thread starts to execute the command, having been notified */ + env->CallVoidMethod(hapticListener,waitMethodId); + checkExceptions(env, "Could not call wait() on HapticListener"); + if(env->MonitorExit(hapticListener) != JNI_OK){ + stopExecution("Could not release memory for haptic listener thread monitor"); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/native/PhantomOmni/uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics.h Tue Jul 10 22:39:37 2012 +0100 @@ -0,0 +1,27 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include <jni.h> +/* Header for class uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics */ + +#ifndef _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics +#define _Included_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics +#ifdef __cplusplus +extern "C" { +#endif +#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MIN_PRIORITY 1L +#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_NORM_PRIORITY 5L +#undef uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY +#define uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_MAX_PRIORITY 10L +/* + * Class: uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics + * Method: init + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_uk_ac_qmul_eecs_ccmi_haptics_OmniHaptics_initOmni + (JNIEnv *, jobject, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif