Mercurial > hg > accesspd
view java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java @ 0:78b7fc5391a2
first import, outcome of NIME 2014 hackaton
author | Fiore Martin <f.martin@qmul.ac.uk> |
---|---|
date | Tue, 08 Jul 2014 16:28:59 +0100 |
parents | |
children |
line wrap: on
line source
/* CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package uk.ac.qmul.eecs.ccmi.simpletemplate; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.ResourceBundle; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.DefaultComboBoxModel; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.SpinnerModel; import jwizardcomponent.FinishAction; import jwizardcomponent.JWizardComponents; import jwizardcomponent.JWizardPanel; import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties; import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers; import uk.ac.qmul.eecs.ccmi.gui.Diagram; import uk.ac.qmul.eecs.ccmi.gui.Edge; import uk.ac.qmul.eecs.ccmi.gui.LineStyle; import uk.ac.qmul.eecs.ccmi.gui.LoopComboBox; import uk.ac.qmul.eecs.ccmi.gui.LoopSpinnerNumberModel; import uk.ac.qmul.eecs.ccmi.gui.Node; import uk.ac.qmul.eecs.ccmi.gui.SpeechSummaryPane; import uk.ac.qmul.eecs.ccmi.simpletemplate.SimpleShapeNode.ShapeType; import uk.ac.qmul.eecs.ccmi.sound.SoundEvent; import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; import uk.ac.qmul.eecs.ccmi.speech.SpeechUtilities; import uk.ac.qmul.eecs.ccmi.utils.GridBagUtilities; /** * * A Wizard-like sequence of screens prompted to the user to let they input (e.g. which shape a node will have or how many nodes an edge can connect at most) * 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 {@code Narrator} through a text to speech synthesizer. * */ public class Wizard { public Wizard(Frame frame, Collection<String> existingDiagrams, Diagram diagramToEdit){ dialog = new SpeechWizardDialog(frame); resources = ResourceBundle.getBundle(SpeechWizardDialog.class.getName()); model = createModel(diagramToEdit); node = new Model.Node(); edge = new Model.Edge(); property = new Model.Property(); modifier = new Model.Modifier(); initWizardComponents(existingDiagrams,diagramToEdit); /* if the user is editing from an existing diagram, they have to choose a new name. They're switched * * directly to the diagram name panel so they have to enter a new name as they would otherwise * * not be allowed to proceed. */ if(diagramToEdit != null) dialog.getWizardComponents().setCurrentIndex(DIAGRAM_NAME); /* when the user clicks on the finish button they'll be prompted with a summary text area dialog * * describing what they have created so far and asking for a confirmation to proceed with the actual * * creation of the template. */ dialog.getWizardComponents().setFinishAction(new FinishAction(dialog.getWizardComponents()){ @Override public void performAction(){ String[] options = { resources.getString("dialog.summary.ok_button_label"), resources.getString("dialog.summary.cancel_button_label")}; int result = SpeechSummaryPane.showDialog( dialog, resources.getString("dialog.summary.title"), model.toString(), SpeechSummaryPane.OK_CANCEL_OPTION, options); if(result == SpeechSummaryPane.CANCEL){ // user wants to continue editing /* null arg will avoid default playerListener which speaks out the focused component */ SoundFactory.getInstance().play(SoundEvent.CANCEL,null); return; } /* create the actual diagram (it will be return to the client class by execute()) */ diagram = createDiagram(model); dialog.dispose(); } }); } public Wizard(Frame frame, Collection<String> existingDiagrams){ this(frame,existingDiagrams,null); } public Diagram execute(){ diagram = null; dialog.show(); if(diagram == null) SoundFactory.getInstance().play(SoundEvent.CANCEL); return diagram; } @SuppressWarnings("serial") private void initWizardComponents(Collection<String> existingDiagrams, final Diagram diagramToEdit){ /* --- MAIN PANEL --- */ String[] choices = { resources.getString("panel.home.choice.diagram_name"), resources.getString("panel.home.choice.nodes"), resources.getString("panel.home.choice.edges"), }; int[] nexts = {DIAGRAM_NAME,NODES,EDGES}; /* panel for the selection of main tasks when creating the diagram: enter diagram name, create node and * * create edge. When a name is assigned an item is added to the selection which allows the user to finish * * the template creation, much as they would do by pressing the finish button. If the user edits an existing * * diagram they're prompted with a message to enter a new diagram name (as there cannot be two diagrams * * with the same name. When the user enters the name the message goes away */ add(HOME,new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.home.title.new"), Arrays.asList(choices), nexts, SpeechWizardPanel.DISABLE_SWITCH ){ @Override public void update(){ if(!model.diagramName.value.isEmpty()){ dialog.setFinishButtonEnabled(true); /* if the diagram has a name the template creation can finish. So add a selection item to the * * comboBox unless it's already there from a previous update (item count < 4 ) */ if(comboBox.getItemCount() < 4) ((DefaultComboBoxModel)comboBox.getModel()).addElement(resources.getString("panel.home.choice.finish")); } super.update(); } @Override public void next(){ if(comboBox.getSelectedIndex() == 3) dialog.getWizardComponents().getFinishButton().doClick(); else super.next(); } }); /* --- DIAGRAM NAME INPUT PANEL --- */ add(DIAGRAM_NAME, new TextWizardPanel( dialog.getWizardComponents(), resources.getString(diagramToEdit == null ? "panel.diagram_name.title" : "panel.diagram_name.title.editing_existing_diagram"), existingDiagrams, HOME, HOME, model.diagramName ){ @Override public void update(){ /* this is a little nasty trick to achieve the following: when the user creates a new diagram out of an already existing * one they're directly prompted with this panel. We want the name of the diagram to be there for awareness * but at the same time it must not be accepted by the program as it would otherwise clash with * with the starting diagam's. As the program accepts it when the text entered in the text field is equal to * model.diagramName.value (for when the user wants to re-edit the name of a diagram they're creating) we must * fill model.DiagramName.value with the name of the starting diagram to get it shown and spoken out but then * it's assigned the empty string not to let the user to go forward */ if(diagramToEdit != null && model.diagramName.value.isEmpty()){ model.diagramName.value = diagramToEdit.getName(); super.update(); model.diagramName.value = ""; }else{ super.update(); } } }); /* --- NODE ACTION SELECTION PANEL --- */ /* decide whether to add a new node or to edit/delete an existing node */ String[] nodeOptions = { resources.getString("panel.nodes.actions.add"), resources.getString("panel.nodes.actions.edit"), resources.getString("panel.nodes.actions.del"), resources.getString("panel.nodes.actions.finish")}; int[] nodeNexts = {NODE_TYPE,NODE_EDIT,NODE_DEL,HOME}; add(NODES, new ActionChooserPanel( dialog.getWizardComponents(), model.nodes.getNames(), resources.getString("panel.nodes.title"), nodeOptions, nodeNexts, HOME, node )); /* --- NODE TYPE NAME INPUT PANEL --- */ add(NODE_TYPE,new TextWizardPanel( dialog.getWizardComponents(), resources.getString("panel.node_name.title"), model.nodes.getNames(), NODE_SHAPE, NODES, node.type )); /* --- NODE TO DELETE SELECTION PANEL*/ add(NODE_DEL, new DeletePanel( dialog.getWizardComponents(), resources.getString("panel.node_del.title"), NODES, NODES, model.nodes)); /* -- NODE TO EDIT SELECTION PANEL */ add(NODE_EDIT, new EditPanel( dialog.getWizardComponents(), resources.getString("panel.node_edit.title"), NODE_TYPE, NODES, model.nodes, node )); ShapeType[] shapeTypes = ShapeType.values(); ArrayList<String> shapeTypeNames = new ArrayList<String>(shapeTypes.length); for(int i=0; i<shapeTypes.length;i++) if(shapeTypes[i] != ShapeType.Transparent) shapeTypeNames.add(shapeTypes[i].toString()); /* -- NODE SHAPE SELECTION PANEL --- */ add(NODE_SHAPE, new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.node_shape.title"), shapeTypeNames, NODE_YESNO_PROPERTIES, NODE_TYPE, node.shape )); /* --- SELECT WHETHER THE THE NODE HAS TO HAVE PROPERTIES --- */ String[] yesnoPropertyOptions = { resources.getString("panel.yesno_properties.add"), resources.getString("panel.yesno_properties.finish") }; int[] yesnoPropertyNexts = {PROPERTIES,NODES}; add(NODE_YESNO_PROPERTIES,new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.yesno_properties.title"), Arrays.asList(yesnoPropertyOptions), yesnoPropertyNexts, NODE_SHAPE ){ @Override public void next(){ if(comboBox.getSelectedIndex() == 1){ Model.Node newNode = new Model.Node(); Model.copy(node, newNode); model.nodes.put(newNode.id,newNode); } super.next(); } }); /* --- PROPERTIES ACTION SELECTION PANEL --- */ String[] propertyOptions = { resources.getString("panel.properties.actions.add"), resources.getString("panel.properties.actions.edit"), resources.getString("panel.properties.actions.del"), resources.getString("panel.properties.action.finish")}; int[] propertyNexts = {PROPERTY_TYPE,PROPERTY_EDIT,PROPERTY_DEL,NODES}; add(PROPERTIES, new ActionChooserPanel( dialog.getWizardComponents(), node.properties.getNames(), resources.getString("panel.properties.title"), propertyOptions, propertyNexts, NODE_SHAPE, property ){ @Override public void next(){ /* if the user selects finish, create a new node put in it the values of */ /* the temporary property and store it in the model */ if( (comboBox.getSelectedIndex() == 1 && comboBox.getItemCount() == 2)|| (comboBox.getSelectedIndex() == 3 && comboBox.getItemCount() == 4)){ Model.Node newNode = new Model.Node(); Model.copy(node, newNode); model.nodes.put(newNode.id,newNode); } super.next(); } }); /* --- PROPERTY TYPE NAME INPUT PANEL --- */ add(PROPERTY_TYPE,new TextWizardPanel( dialog.getWizardComponents(), resources.getString("panel.property_name.title"), node.properties.getNames(), PROPERTY_POSITION, PROPERTIES, property.type )); /* --- PROPERTY TO DELETE SELECTION PANEL --- */ add(PROPERTY_DEL, new DeletePanel( dialog.getWizardComponents(), resources.getString("panel.property_del.title"), PROPERTIES, PROPERTIES, node.properties )); /* --- PROPERTY TO EDIT SELECTION PANEL --- */ add(PROPERTY_EDIT, new EditPanel( dialog.getWizardComponents(), resources.getString("panel.property_edit.title"), PROPERTY_TYPE, PROPERTIES, node.properties, property )); /* --- PROPERTY POSITION SELECTION DIALOG --- */ SimpleShapeNode.Position positions[] = SimpleShapeNode.Position.values(); ArrayList<String> positionNames = new ArrayList<String>(positions.length); for(int i=0; i<positions.length;i++) positionNames.add(positions[i].toString()); int[] positionNexts = {PROPERTY_YESNO_MODIFIER,PROPERTY_SHAPE}; add(PROPERTY_POSITION, new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.property_position.title"), positionNames, positionNexts, PROPERTY_TYPE, property.position )); /* --- PROPERTY SHAPE SELECTION DIALOG --- */ shapeTypeNames.add(ShapeType.Transparent.toString()); add(PROPERTY_SHAPE, new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.property_shape.title"), shapeTypeNames, PROPERTY_YESNO_MODIFIER, PROPERTY_POSITION, property.shape )); /* --- SELECT WHETHER THE THE PROPERTY HAS TO HAVE MODIFIERS --- */ String[] yesnoModifierOptions = { resources.getString("panel.yesno_modifiers.add"), resources.getString("panel.yesno_modifiers.finish") }; int[] yesnoModifierNexts = {MODIFIERS,PROPERTIES}; add(PROPERTY_YESNO_MODIFIER,new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.yesno_modifiers.title"), Arrays.asList(yesnoModifierOptions), yesnoModifierNexts, PROPERTY_POSITION ){ @Override public void next(){ if(comboBox.getSelectedIndex() == 1){ Model.Property newProperty = new Model.Property(); Model.copy(property, newProperty); node.properties.put(newProperty.id,newProperty); } super.next(); } }); /* --- MODIFIERS ACTION SELECTION PANE --- */ String[] modifierOptions = { resources.getString("panel.modifiers.actions.add"), resources.getString("panel.modifiers.actions.edit"), resources.getString("panel.modifiers.actions.del"), resources.getString("panel.modifiers.actions.finish") }; int[] modifiersNexts = {MODIFIER_TYPE,MODIFIER_EDIT,MODIFIER_DEL,PROPERTIES}; add(MODIFIERS, new ActionChooserPanel( dialog.getWizardComponents(), property.modifiers.getNames(), resources.getString("panel.modifiers.title"), modifierOptions, modifiersNexts, PROPERTY_POSITION, modifier){ @Override public void next(){ /* if the user selects finish, create a new property put in it the values of */ /* the temporary property and store it in the model */ if( (comboBox.getSelectedIndex() == 1 && comboBox.getItemCount() == 2)|| (comboBox.getSelectedIndex() == 3 && comboBox.getItemCount() == 4)){ Model.Property newProperty = new Model.Property(); Model.copy(property, newProperty); node.properties.put(newProperty.id,newProperty); } super.next(); } }); /* --- MODIFIER TYPE PANEL --- */ add(MODIFIER_TYPE, new TextWizardPanel( dialog.getWizardComponents(), resources.getString("panel.modifier_type.title"), property.modifiers.getNames(), MODIFIER_FORMAT, MODIFIERS, modifier.type )); add(MODIFIER_DEL, new DeletePanel( dialog.getWizardComponents(), resources.getString("panel.modifier_del.title"), MODIFIERS, MODIFIERS, property.modifiers)); add(MODIFIER_EDIT, new EditPanel( dialog.getWizardComponents(), resources.getString("panel.modifier_edit.title"), MODIFIER_TYPE, MODIFIERS, property.modifiers, modifier )); add(MODIFIER_FORMAT, new FormatWizardPanel()); /* --- EDGE ACTION SELECTION PANEL --- */ /* decide whether to add a new edge or to edit/delete an existing edge */ String[] edgeOptions = { resources.getString("panel.edges.actions.add"), resources.getString("panel.edges.actions.edit"), resources.getString("panel.edges.actions.del"), resources.getString("panel.edges.actions.finish")}; int[] edgeNexts = {EDGE_TYPE,EDGE_EDIT,EDGE_DEL,HOME}; add(EDGES, new ActionChooserPanel( dialog.getWizardComponents(), model.edges.getNames(), resources.getString("panel.edges.title"), edgeOptions, edgeNexts, HOME, edge )); /* --- EDGE TYPE NAME INPUT PANEL --- */ add(EDGE_TYPE,new TextWizardPanel( dialog.getWizardComponents(), resources.getString("panel.edge_name.title"), model.edges.getNames(), EDGE_LINE_STYLE, EDGES, edge.type )); /* --- EDGE TO DELETE SELECTION PANEL --- */ add(EDGE_DEL, new DeletePanel( dialog.getWizardComponents(), resources.getString("panel.edge_del.title"), EDGES, EDGES, model.edges)); /* --- EDGE TO EDIT SELECTION PANEL --- */ add(EDGE_EDIT, new EditPanel( dialog.getWizardComponents(), resources.getString("panel.edge_edit.title"), EDGE_TYPE, EDGES, model.edges, edge )); /* --- LINE STYLE SELECTION PANEL --- */ LineStyle[] lineStyles = LineStyle.values(); String[] lineStyleNames = new String[lineStyles.length]; for(int i=0; i<lineStyles.length;i++) lineStyleNames[i] = lineStyles[i].toString(); add(EDGE_LINE_STYLE, new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.edge_linestyle.title"), Arrays.asList(lineStyleNames), EDGE_MIN_NODES, EDGE_TYPE, edge.lineStyle )); /* --- MIN NODES SELECTION PANEL --- */ SpinnerModel minNodesSpinnerModel = new LoopSpinnerNumberModel(2,2,4); add(EDGE_MIN_NODES,new SpinnerWizardPanel( dialog.getWizardComponents(), resources.getString("panel.edge_min_nodes.title"), minNodesSpinnerModel, EDGE_MAX_NODES, EDGE_LINE_STYLE, edge.minNodes )); /* --- MAX NODES SELECTION PANEL --- */ SpinnerModel maxNodesSpinnerModel = new LoopSpinnerNumberModel(2,2,4); add(EDGE_MAX_NODES, new SpinnerWizardPanel( dialog.getWizardComponents(), resources.getString("panel.edge_max_nodes.title"), maxNodesSpinnerModel, EDGE_YESNO_ARROW_HEAD, EDGE_MIN_NODES, edge.maxNodes ){ @Override public void next(){ int min = Integer.parseInt(edge.minNodes.value); int max = Integer.parseInt(spinner.getValue().toString()); if(min > max){ NarratorFactory.getInstance().speak(resources.getString("dialog.error.min_max")); }else{ super.next(); } } }); /* --- SELECT WHETHER THE EDGE MUST HAVE ARROW HEADS OR NOT --- */ String[] arrowHeadOptions = { resources.getString("panel.edge_yesno_arrow_head.actions.add"), resources.getString("panel.edge_yesno_arrow_head.actions.finish") }; int[] arrowHeadNexts = {EDGE_ARROW_HEAD,EDGES}; add(EDGE_YESNO_ARROW_HEAD,new SelectWizardPanel( dialog.getWizardComponents(), resources.getString("panel.edge_yesno_arrow_head.title"), Arrays.asList(arrowHeadOptions), arrowHeadNexts, EDGE_MAX_NODES ){ @Override public void next(){ if(comboBox.getSelectedIndex() == 1){ Model.Edge newEdge = new Model.Edge(); Model.copy(edge, newEdge); model.edges.put(newEdge.id,newEdge); } super.next(); } }); /* --- ARROW HEAD SELECTION PANEL --- */ add(EDGE_ARROW_HEAD, new ArrowHeadPanel()); add(LAST_PANEL, new DummyWizardPanel(dialog.getWizardComponents())); SpeechUtilities.changeTabListener((JComponent)dialog.getContentPane(), dialog); } private void add(int index, JWizardPanel panel){ dialog.getWizardComponents().addWizardPanel(index,panel); } private Diagram createDiagram(Model model){ /* create the node prototypes */ Node[] nodes = new Node[model.nodes.size()]; int i = 0; for(Model.Node n : model.nodes.values()){ nodes[i] = createDiagramNode(n); i++; } /* create the edge prototypes */ Edge[] edges = new Edge[model.edges.size()]; i = 0; for(Model.Edge e : model.edges.values()){ edges[i] = createDiagramEdge(e); i++; } return Diagram.newInstance(model.diagramName.value, nodes, edges, new SimpleShapePrototypePersistenceDelegate()); } private Node createDiagramNode(Model.Node n){ /* set up the properties */ LinkedHashMap<String,Set<String>> propertiesTypeDefinition = new LinkedHashMap<String,Set<String>>(); /* create the property type definition */ for(Model.Property modelProperty : n.properties.values()){ Set<String> modifiersTypeDefinition = new LinkedHashSet<String>(); for(Model.Modifier modifier : modelProperty.modifiers.values()) modifiersTypeDefinition.add(modifier.type.value); propertiesTypeDefinition.put(modelProperty.type.value, modifiersTypeDefinition); } NodeProperties properties = new NodeProperties(propertiesTypeDefinition); /* now that properties object is created attach the views on it */ for(Model.Property modelProperty : n.properties.values()){ PropertyView propertyView = new PropertyView( SimpleShapeNode.Position.valueOf(modelProperty.position.value), modelProperty.shape.value.isEmpty() ? /* doesn't really matter as position is inside and shape won't be taken into account */ SimpleShapeNode.ShapeType.Rectangle : SimpleShapeNode.ShapeType.valueOf(modelProperty.shape.value) ); properties.setView(modelProperty.type.value, propertyView); /* modifier view */ for(Model.Modifier modelModifier : modelProperty.modifiers.values()){ boolean bold = false; boolean italic = false; boolean underline = false; String prefix = ""; String suffix = ""; for(String value : modelModifier.format.values){ if(value.equals(resources.getString("modifier.format.bold"))){ bold = true; }else if(value.equals(resources.getString("modifier.format.underline"))){ underline = true; }else if(value.equals(resources.getString("modifier.format.italic"))){ italic = true; }else if(value.equals(resources.getString("modifier.format.prefix"))){ prefix = modelModifier.affix.values[PREFIX_INDEX]; }else if(value.equals(resources.getString("modifier.format.suffix"))){ suffix = modelModifier.affix.values[SUFFIX_INDEX]; } } ModifierView modifierView = new ModifierView(underline,bold,italic,prefix,suffix); properties.getModifiers(modelProperty.type.value).setView(modelModifier.type.value, modifierView); } } return SimpleShapeNode.getInstance( SimpleShapeNode.ShapeType.valueOf(n.shape.value), n.type.value, properties); } private Edge createDiagramEdge(Model.Edge e){ /* create the arrow head array out of the string stored in the model */ ArrowHead[] arrowHeads = new ArrowHead[e.arrowHeads.values.length]; for(int i=0; i<e.arrowHeads.values.length;i++){ try { arrowHeads[i] = ArrowHead.getArrowHeadFromString(e.arrowHeads.values[i]); } catch (IOException ioe) { throw new RuntimeException(ioe);// the wizard mustn't allow the user to enter different strings } } return new SimpleShapeEdge( e.type.value, LineStyle.valueOf(e.lineStyle.value), arrowHeads, e.arrowHeadsDescriptions.values, Integer.parseInt(e.minNodes.value), Integer.parseInt(e.maxNodes.value) ); } private Model createModel(Diagram diagram) { Model model = new Model(); if(diagram == null) return model; /* the name isn't copied as the user as to find a new one */ /* model.diagramName.value = diagram.getName();*/ /* nodes */ for(Node n : diagram.getNodePrototypes()){ if(!(n instanceof SimpleShapeNode)) continue; Model.Node modelNode = createModelNode((SimpleShapeNode)n); model.nodes.put(modelNode.id,modelNode); } /* edges */ for(Edge e : diagram.getEdgePrototypes()){ if(!(e instanceof SimpleShapeEdge)) continue; Model.Edge modelEdge = createModelEdge((SimpleShapeEdge)e); model.edges.put(modelEdge.id, modelEdge); } return model; } /** * fills up the model node object with informations from the real diagram node * @param n * @return */ private Model.Node createModelNode(SimpleShapeNode n){ Model.Node modelNode = new Model.Node(); modelNode.type.value = n.getType(); modelNode.shape.value = n.getShapeType().toString(); NodeProperties properties = n.getProperties(); for(String propertyType : properties.getTypes()){ Model.Property modelProperty = new Model.Property(); modelProperty.type.value = propertyType; /* if the view is not a PropertyView or is null then assign a default value */ /* it should never happen but it's just to keep it safer and more forward compliant */ if(! (properties.getView(propertyType) instanceof PropertyView)){ modelProperty.position.value = SimpleShapeNode.Position.Inside.toString(); }else{ PropertyView propertyView = (PropertyView)properties.getView(propertyType); modelProperty.position.value = propertyView.getPosition().toString(); modelProperty.shape.value = propertyView.getShapeType().toString(); } Modifiers modifiers = properties.getModifiers(propertyType); for(String modifierType : modifiers.getTypes()){ Model.Modifier modelModifier = new Model.Modifier(); modelModifier.type.value = modifierType; if(modifiers.getView(modifierType) instanceof ModifierView){ ModifierView modifierView = (ModifierView)modifiers.getView(modifierType); /* the string array with the modifier values must be created, so the size must be known before */ int numModifierValues = 0; if(modifierView.isBold()) numModifierValues++; if(modifierView.isItalic()) numModifierValues++; if(modifierView.isUnderline()) numModifierValues++; if(!modifierView.getPrefix().isEmpty()) numModifierValues++; if(!modifierView.getSuffix().isEmpty()) numModifierValues++; /* create the string array and fill it up with values */ modelModifier.format.values = new String[numModifierValues]; numModifierValues = 0; if(modifierView.isBold()) modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.bold"); if(modifierView.isItalic()) modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.italic"); if(modifierView.isUnderline()) modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.underline"); if(!modifierView.getPrefix().isEmpty()){ modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.prefix"); modelModifier.affix.values[PREFIX_INDEX] = modifierView.getPrefix(); } if(!modifierView.getSuffix().isEmpty()){ modelModifier.format.values[numModifierValues++] = resources.getString("modifier.format.suffix"); modelModifier.affix.values[SUFFIX_INDEX] = modifierView.getSuffix(); } } modelProperty.modifiers.put(modelModifier.id, modelModifier); } modelNode.properties.put(modelProperty.id, modelProperty); } return modelNode; } private Model.Edge createModelEdge(SimpleShapeEdge e){ Model.Edge modelEdge = new Model.Edge(); modelEdge.type.value = e.getType(); modelEdge.lineStyle.value = e.getStyle().toString(); modelEdge.maxNodes.value = Integer.toString(e.getMaxAttachedNodes()); modelEdge.minNodes.value = Integer.toString(e.getMinAttachedNodes()); /* arrow heads and arrowheads descriptions */ modelEdge.arrowHeadsDescriptions.values = e.getAvailableEndDescriptions(); modelEdge.arrowHeads.values = new String[e.getHeads().length]; for(int i =0; i<e.getHeads().length;i++){ modelEdge.arrowHeads.values[i] = e.getHeads()[i].toString(); } return modelEdge; } private SpeechWizardDialog dialog; private Diagram diagram; private ResourceBundle resources; private Model model; /* these are the temporary variables where the data are stored during the wizard * * when a sub task is completed ( node creation, edge creation, property creation)* * the data stored in the temporary variables are saved in the model */ private Model.Node node; private Model.Property property; private Model.Modifier modifier; private Model.Edge edge; static int HOME = 0; static int DIAGRAM_NAME = 1; static int NODES = 2; static int NODE_TYPE = 3; static int NODE_DEL = 4; static int NODE_EDIT = 5; static int NODE_SHAPE = 6; static int NODE_YESNO_PROPERTIES = 7; static int PROPERTIES = 8; static int PROPERTY_TYPE = 9; static int PROPERTY_DEL = 10; static int PROPERTY_EDIT = 11; static int PROPERTY_POSITION = 12; static int PROPERTY_SHAPE = 13; static int PROPERTY_YESNO_MODIFIER = 14; static int MODIFIERS = 15; static int MODIFIER_TYPE = 16; static int MODIFIER_DEL = 17; static int MODIFIER_EDIT = 18; static int MODIFIER_FORMAT = 19; static int EDGES = 20; static int EDGE_TYPE = 21; static int EDGE_DEL = 22; static int EDGE_EDIT = 23; static int EDGE_LINE_STYLE = 24; static int EDGE_MIN_NODES = 25; static int EDGE_MAX_NODES = 26; static int EDGE_YESNO_ARROW_HEAD = 27; static int EDGE_ARROW_HEAD = 28; static int LAST_PANEL = 29; private static int PREFIX_INDEX = 0; private static int SUFFIX_INDEX = 1; /* the abstract class from which the panels for Nodes, edges and Modifiers inherit * It displays the actions (add,edit,delete,finish) on a comboBox. if elementNames is empty * it means that no element has been created yet and therefore edit and delete actions are disabled */ @SuppressWarnings("serial") private static class ActionChooserPanel extends SpeechWizardPanel{ ActionChooserPanel(JWizardComponents wizardComponents,Collection<String> elementNames, String title, String[] options, int[] nexts, int previous, Model.Element temporaryElement){ super(wizardComponents,title,OWN_SWITCH, previous); this.options = options; comboBoxModel = new DefaultComboBoxModel(); comboBoxModel.addElement(options[0]); comboBoxModel.addElement(options[3]); comboBox = new LoopComboBox(comboBoxModel); comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener()); layoutComponents(comboBox); this.elementNames = elementNames; this.temporaryElement = temporaryElement; this.nexts = nexts; } @Override public void update(){ if(elementNames.isEmpty() && comboBoxModel.getSize() == 4){ comboBoxModel.removeElement(options[1]); comboBoxModel.removeElement(options[2]); }else if(!elementNames.isEmpty() && comboBoxModel.getSize() == 2){ comboBoxModel.insertElementAt(options[1],1); comboBoxModel.insertElementAt(options[2],2); } super.update(); } @Override public void next(){ /* if the selection was add element, then we clear the temporary holder */ if(comboBox.getSelectedIndex() == 0) temporaryElement.clear(); /* jump to the selected next step, works both when it's only add/finish and when it's add/delete/edit/finish */ for(int i=0; i<options.length; i++) if(comboBox.getSelectedItem().equals(options[i])){ switchPanel(nexts[i]); return; } } JComboBox comboBox; Collection<String> elementNames; DefaultComboBoxModel comboBoxModel; String[] options; Model.Element temporaryElement; int[] nexts; } @SuppressWarnings("serial") private static class DeletePanel extends SpeechWizardPanel { DeletePanel(JWizardComponents wizardComponents,String title, int next, int previous, ModelMap<? extends Model.Element> elements){ super(wizardComponents,title,next, previous); this.elements = elements; comboBox = new LoopComboBox(); comboBox.addItemListener(SpeechUtilities.getSpeechComboBoxItemListener()); layoutComponents(comboBox); } @Override public void update(){ String[] options = new String[elements.values().size()]; options = elements.getNames().toArray(options); comboBox.setModel(new DefaultComboBoxModel(options)); super.update(); } /** * the default behaviour is to delete the selected element */ @Override public void next(){ Model.Element elementToDelete = null; for(Model.Element element : elements.values()){ if(element.type.value.equals(comboBox.getSelectedItem())){ elementToDelete = element; break; } } Object o = elements.remove(elementToDelete.id); assert(o != null); super.next(); } JComboBox comboBox; ModelMap<? extends Model.Element> elements; } @SuppressWarnings("serial") private static class EditPanel extends DeletePanel { EditPanel(JWizardComponents wizardComponents, String title, int next, int previous, ModelMap<? extends Model.Element> elements, Model.Element temporaryHolder){ super(wizardComponents, title,next, previous,elements); this.temporaryHolder = temporaryHolder; this.next = next; } @Override public void next(){ Model.Element selected = null; for(Model.Element e : elements.values()){ if(e.type.value.equals(comboBox.getSelectedItem())){ selected = e; break; } } Model.copy(selected, temporaryHolder); switchPanel(next); } int next; Model.Element temporaryHolder; } @SuppressWarnings("serial") private class FormatWizardPanel extends SpeechWizardPanel{ FormatWizardPanel(){ super(dialog.getWizardComponents(),resources.getString("panel.modifier_format.title"),MODIFIERS,MODIFIER_TYPE); String values[] = { resources.getString("modifier.format.bold"), resources.getString("modifier.format.underline"), resources.getString("modifier.format.italic"), resources.getString("modifier.format.prefix"), resources.getString("modifier.format.suffix"), }; checkBoxes = new JCheckBox[values.length]; checkBoxPanel = new JPanel(new GridLayout(0, 1)); for(int i=0; i<values.length;i++){ String value = values[i]; checkBoxes[i] = new JCheckBox(value); checkBoxes[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); checkBoxes[i].getActionMap().put("enter", new AbstractAction(){ @Override public void actionPerformed(ActionEvent arg0) { getWizardComponents().getNextButton().doClick(); } }); checkBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener()); /* prefix and suffix check boxes must have a JText area for the user to enter the String */ if(i == 3 || i == 4){ JPanel panel = new JPanel(); panel.setLayout(new FlowLayout(FlowLayout.LEFT,0,0)); panel.add(checkBoxes[i]); if(i == 3){ prefixTextField = new JTextField(); prefixTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); prefixTextField.getActionMap().put("enter", new AbstractAction(){ @Override public void actionPerformed(ActionEvent arg0) { getWizardComponents().getNextButton().doClick(); } }); prefixTextField.getAccessibleContext().setAccessibleName(checkBoxes[i].getText()); prefixTextField.setColumns(5); prefixTextField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true)); panel.add(prefixTextField); }else{ suffixTextField = new JTextField(); suffixTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); suffixTextField.getActionMap().put("enter", new AbstractAction(){ @Override public void actionPerformed(ActionEvent arg0) { getWizardComponents().getNextButton().doClick(); } }); suffixTextField.getAccessibleContext().setAccessibleName(checkBoxes[i].getText()); suffixTextField.setColumns(5); suffixTextField.addKeyListener(SpeechUtilities.getSpeechKeyListener(true)); panel.add(suffixTextField); } checkBoxPanel.add(panel); }else{ checkBoxPanel.add(checkBoxes[i]); } } JScrollPane scrollPane = new JScrollPane(checkBoxPanel); scrollPane.setFocusable(false); layoutComponents(scrollPane); dialog.getWizardComponents().getFinishButton().setEnabled(true); } /* store the checks into the StrArrayRecord, it doesn't call super.next thus */ /* sub classes have to implement call the switch panel on their own */ public void next(){ int numCheckedBoxes = 0; for(JCheckBox check : checkBoxes){ if(check.isSelected()) numCheckedBoxes++; } String[] result = new String[numCheckedBoxes]; numCheckedBoxes = 0; for(int i=0; i<checkBoxes.length;i++){ /* store the text value of the check boxes, if it's the prefix or suffix */ /* append the text entered by the user in the text areas */ if(checkBoxes[i].isSelected()){ String text = checkBoxes[i].getText(); if(i == 3) modifier.affix.values[PREFIX_INDEX] = prefixTextField.getText(); else if(i == 4) modifier.affix.values[SUFFIX_INDEX] = suffixTextField.getText(); result[numCheckedBoxes++] = text; } } modifier.format.values = result; Model.Modifier newModifier = new Model.Modifier(); Model.copy(modifier,newModifier); property.modifiers.put(newModifier.id,newModifier); super.next(); } @Override public void update(){ /* set the check boxes and text field according to the modifier.format. so if we are editing an existing * * modifier we find the old values, else if it's a new modifier we find everything blank */ if(modifier.format != null){ prefixTextField.setText(""); suffixTextField.setText(""); for(JCheckBox check : checkBoxes){ /* temporarily remove the speech Item listener in order to avoid bla bla bla not triggered by user */ check.removeItemListener(SpeechUtilities.getCheckBoxSpeechItemListener()); check.setSelected(false); for(String checkedValue : modifier.format.values){ if(checkedValue.equals(check.getText())){//for bold,italic,underline check.setSelected(true); if(checkedValue.equals(resources.getString("modifier.format.prefix"))){ prefixTextField.setText(modifier.affix.values[PREFIX_INDEX]); }else if(checkedValue.equals(resources.getString("modifier.format.suffix"))){ suffixTextField.setText(modifier.affix.values[SUFFIX_INDEX]); } break; } } check.addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener()); } } super.update(); } @Override protected Component assignFocus(){ /* focus on the first item */ checkBoxes[0].requestFocus(); return checkBoxes[0]; } JTextField prefixTextField; JTextField suffixTextField; JPanel checkBoxPanel; JCheckBox checkBoxes[]; } @SuppressWarnings("serial") private class ArrowHeadPanel extends SpeechWizardPanel { ArrowHeadPanel(){ super(dialog.getWizardComponents(),resources.getString("panel.edge_arrow_head.title"),EDGES,EDGE_YESNO_ARROW_HEAD); JPanel panel = new JPanel(new GridBagLayout()); final JScrollPane scrollPane = new JScrollPane(panel); scrollPane.setFocusable(false); GridBagUtilities gridBagUtils = new GridBagUtilities(); int numArrowHeads = ArrowHead.values().length; arrowsCheckBoxes = new JCheckBox[numArrowHeads]; arrowsTextDescriptions = new JTextField[numArrowHeads]; for(int i=0; i<numArrowHeads; i++){ /* set up the key bindings for all the check boxes and text fields */ /* by pressing enter the wizard switches the next panel */ arrowsCheckBoxes[i] = new JCheckBox(ArrowHead.values()[i].toString()); arrowsCheckBoxes[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); arrowsCheckBoxes[i].getActionMap().put("enter", new AbstractAction(){ @Override public void actionPerformed(ActionEvent arg0) { getWizardComponents().getNextButton().doClick(); } }); arrowsTextDescriptions[i] = new JTextField(); arrowsTextDescriptions[i].getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), "enter"); arrowsTextDescriptions[i].getActionMap().put("enter", new AbstractAction(){ @Override public void actionPerformed(ActionEvent arg0) { getWizardComponents().getNextButton().doClick(); } }); /* add the speech to the check boxes */ arrowsCheckBoxes[i].addItemListener(SpeechUtilities.getCheckBoxSpeechItemListener()); arrowsTextDescriptions[i].setPreferredSize(new Dimension(TEXTFIELD_SIZE, arrowsTextDescriptions[i].getPreferredSize().height)); arrowsTextDescriptions[i].getAccessibleContext().setAccessibleName(arrowsCheckBoxes[i].getText()); arrowsTextDescriptions[i].addKeyListener(SpeechUtilities.getSpeechKeyListener(true)); panel.add(arrowsCheckBoxes[i], gridBagUtils.label()); panel.add(arrowsTextDescriptions[i],gridBagUtils.field()); } layoutComponents(scrollPane); } @Override public void update(){ /* restore the values (checkbox + text) currently in edge.arrowHeads into the panel components */ if(edge.arrowHeads != null){ for(int i=0; i<arrowsCheckBoxes.length;i++){ arrowsCheckBoxes[i].setSelected(false); arrowsTextDescriptions[i].setText(""); for(int j=0; j< edge.arrowHeads.values.length; j++){ if(arrowsCheckBoxes[i].getText().equals(edge.arrowHeads.values[j])){ arrowsCheckBoxes[i].setSelected(true); arrowsTextDescriptions[i].setText(edge.arrowHeadsDescriptions.values[j]); break; } } } } super.update(); } @Override public void next(){ /* check that the user has entered a text for all of the selected check boxes */ int numChecked = 0;//this is to keep count of the checked boxes, used after the check for(int i=0; i<arrowsCheckBoxes.length;i++){ JCheckBox checkBox = arrowsCheckBoxes[i]; if(checkBox.isSelected()){ numChecked++; /* there cannot be a checked check box without the related textField filled in */ if(arrowsTextDescriptions[i].getText().trim().isEmpty()){ NarratorFactory.getInstance().speak( MessageFormat.format( resources.getString("dialog.error.empty_desc"), checkBox.getText()) ); return; } } } /* copy the label of the checked boxes and the text of the JTextField into the edge fields */ edge.arrowHeads.values = new String[numChecked]; edge.arrowHeadsDescriptions.values = new String[numChecked]; numChecked = 0; for(int i=0; i<arrowsCheckBoxes.length;i++){ if(arrowsCheckBoxes[i].isSelected()){ edge.arrowHeads.values[numChecked] = arrowsCheckBoxes[i].getText(); edge.arrowHeadsDescriptions.values[numChecked] = arrowsTextDescriptions[i].getText().trim(); numChecked++; } } /* put the edge (copy of) into the model */ Model.Edge newEdge = new Model.Edge(); Model.copy(edge, newEdge); model.edges.put(newEdge.id,newEdge); super.next(); } @Override protected Component assignFocus(){ /* focus on the first item */ arrowsCheckBoxes[0].requestFocus(); return arrowsCheckBoxes[0]; } JCheckBox arrowsCheckBoxes[]; JTextField arrowsTextDescriptions[]; final int TEXTFIELD_SIZE = 100; } }