Mercurial > hg > accesspd
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java Tue Jul 08 16:28:59 2014 +0100 @@ -0,0 +1,1210 @@ +/* + 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; + } +}