view java/src/uk/ac/qmul/eecs/ccmi/simpletemplate/Wizard.java @ 0:9418ab7b7f3f

Initial import
author Fiore Martin <fiore@eecs.qmul.ac.uk>
date Fri, 16 Dec 2011 17:35:51 +0000
parents
children 4b2f975e35fa
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 {@link 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  
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
					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;
	}
}