view java/src/uk/ac/qmul/eecs/ccmi/gui/EditorFrame.java @ 1:e3935c01cde2 tip

moved license of PdPersistenceManager to the beginning of the file
author Fiore Martin <f.martin@qmul.ac.uk>
date Tue, 08 Jul 2014 19:52:03 +0100
parents 78b7fc5391a2
children
line wrap: on
line source
/*  
 CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool

 Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com)
 Copyright (C) 2011  Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package uk.ac.qmul.eecs.ccmi.gui;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.KeyboardFocusManager;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.channels.SocketChannel;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;

import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException;
import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement;
import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers;
import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyMutableTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyTypeMutableTreeNode;
import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode;
import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter;
import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter;
import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager;
import uk.ac.qmul.eecs.ccmi.haptics.Haptics;
import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory;
import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage;
import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager;
import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.RmDiagramRequest;
import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.SendAwarenessRequest;
import uk.ac.qmul.eecs.ccmi.network.Command;
import uk.ac.qmul.eecs.ccmi.network.DiagramDownloader;
import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource;
import uk.ac.qmul.eecs.ccmi.network.DiagramShareException;
import uk.ac.qmul.eecs.ccmi.network.NetDiagram;
import uk.ac.qmul.eecs.ccmi.network.ProtocolFactory;
import uk.ac.qmul.eecs.ccmi.network.Server;
import uk.ac.qmul.eecs.ccmi.pdsupport.PdDiagram;
import uk.ac.qmul.eecs.ccmi.pdsupport.PdPersistenceManager;
import uk.ac.qmul.eecs.ccmi.sound.PlayerListener;
import uk.ac.qmul.eecs.ccmi.sound.SoundEvent;
import uk.ac.qmul.eecs.ccmi.sound.SoundFactory;
import uk.ac.qmul.eecs.ccmi.speech.Narrator;
import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory;
import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler;
import uk.ac.qmul.eecs.ccmi.utils.InteractionLog;
import uk.ac.qmul.eecs.ccmi.utils.PreferencesService;

/**
 *  The main frame of the editor which contains diagram panes that show graphs and 
 *  tree representations of diagrams.
 */
@SuppressWarnings("serial")
public class EditorFrame extends JFrame { 
	/**
	 * Creates a new {@code EditorFrame}
	 * 
	 * @param haptics an instance of {@code Haptics} handling the haptic device during this 
	 * @param templateFiles an array of template files. New diagrams can be created from template files by clonation 
	 * @param backupDirPath the path of a folder where all the currently open diagrams will be saved if 
	 * the haptic device crashes  
	 * @param templateEditors the template editors for this instance of the program
	 * @param additionalTemplate additional diagram templates. An entry will be created in {@code File->New Diagram}
	 * for each template  
	 */
	public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors, Diagram[] additionalTemplate){  
		this.backupDirPath = backupDirPath; 
		/* load resources */ 
		resources = ResourceBundle.getBundle(this.getClass().getName());      

		/* haptics */
		this.haptics = haptics;
		hapticTrigger = new HapticTrigger();

		/* read editor related preferences */
		preferences = PreferencesService.getInstance();

		URL url = getClass().getResource("ccmi_favicon.gif");
		setIconImage(new ImageIcon(url).getImage());
		changeLookAndFeel(preferences.get("laf", null));

		recentFiles = new ArrayList<String>(); 
		File lastDir = new File(".");
		String recent = preferences.get("recent", "").trim();
		if (recent.length() > 0){
			recentFiles.addAll(Arrays.asList(recent.split("[|]")));         
			lastDir = new File(recentFiles.get(0)).getParentFile();
		}
		fileService = new FileService.ChooserService(lastDir);

		/* set up extensions */
		defaultExtension = resources.getString("files.extension");
		extensionFilter = new ExtensionFilter(
				resources.getString("files.name"), 
				new String[] { defaultExtension });
		exportFilter = new ExtensionFilter(
				resources.getString("files.image.name"), 
				resources.getString("files.image.extension"));

		/* start building the GUI */
		editorTabbedPane = new EditorTabbedPane(this);
		setContentPane(editorTabbedPane);


		setTitle(resources.getString("app.name"));
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();

		int screenWidth = (int)screenSize.getWidth();
		int screenHeight = (int)screenSize.getHeight();

		setLocation(screenWidth / 16, screenHeight / 16);
		editorTabbedPane.setPreferredSize(new Dimension(
				screenWidth * 5 / 8, screenHeight * 5 / 8));

		/* install the player listener (a narrator speech) for CANCEL, EMPTY and MESSAGE ok event. They are  *  
		 * not depending on what the user is doing and the speech is therefore always the same. We only need *  
		 * to set the listener once and it will do the job each time the sound is triggered                  */
		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
			@Override
			public void playEnded() {
				DiagramPanel dPanel = getActiveTab();
				if(dPanel != null){// we can cancel a dialog even when no diagram is open (e.g. for tcp connections)
					speakFocusedComponent("");
				}else{
					NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.cancelled"),""));
				}
			}
		}, SoundEvent.CANCEL);
		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
			@Override
			public void playEnded() {
				DiagramPanel dPanel = getActiveTab();
				DiagramTree tree = dPanel.getTree();
				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.empty_property"),tree.currentPathSpeech()));
			}
		}, SoundEvent.EMPTY);
		SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){
			@Override
			public void playEnded() {
				DiagramPanel dPanel = getActiveTab();
				if(dPanel != null){
					DiagramTree tree = dPanel.getTree();
					NarratorFactory.getInstance().speak(tree.currentPathSpeech());
				}
			}
		}, SoundEvent.MESSAGE_OK);

		/* setup listeners */ 
		initListeners();
		/* set up menus */  
		existingTemplateNames = new ArrayList<String>(10);
		existingTemplates = new ArrayList<Diagram>(10);
		int extensionLength = resources.getString("template.extension").length();
		for(File file : templateFiles){
			existingTemplateNames.add(file.getName().substring(0, file.getName().length()-extensionLength));
		}
		initMenu(templateEditors);
		/* read template files. this call must be placed after menu creation as it adds menu items to file->new-> */
		boolean someTemplateFilesNotRead = readTemplateFiles(templateFiles);
		
		for(Diagram additionalDiagram : additionalTemplate){
			addDiagramType(additionalDiagram);
		}
		
		/* become visible */
		pack();
		setVisible(true);
		/* if some templates were not read successfully, warn the user with a message */
		if(someTemplateFilesNotRead){
			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.filesnotread"), SpeechOptionPane.WARNING_MESSAGE);
		}
	}

	private void initListeners(){
		/* window closing */
		setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
		addWindowListener(new  WindowAdapter(){
			@Override
			public void windowClosing(WindowEvent event){
				exit();
			}
			@Override
			public void	windowOpened(WindowEvent e) {
				// bring the window to front, else the openGL window would have higher priority
				e.getWindow().toFront();
			}
		});

		addWindowFocusListener(new WindowFocusListener(){
			@Override
			public void windowGainedFocus(WindowEvent evt) {
				if(evt.getOppositeWindow() == null) 
					NarratorFactory.getInstance().speak(resources.getString("window.focus"));
			}

			@Override
			public void windowLostFocus(WindowEvent evt) {
				if(evt.getOppositeWindow() == null)
					NarratorFactory.getInstance().speak(resources.getString("window.unfocus"));
			}
		});

		/* set up listeners reacting to change of selection in the tree and tab */
		tabChangeListener = new ChangeListener(){
			@Override
			public void stateChanged(ChangeEvent evt) {
				DiagramPanel diagramPanel = getActiveTab();
				if (diagramPanel != null){ 
					/* give the focus to the Content Pane, otherwise it's grabbed by the rootPane */
					getContentPane().requestFocusInWindow();
					TreePath path = diagramPanel.getTree().getSelectionPath();
					treeEnabledMenuUpdate(path);
					NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex())));

					// updated the haptics
					HapticsFactory.getInstance().switchDiagram(diagramPanel.getDiagram().getName());
					iLog("diagram tab changed to "+editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex()));
				}else{
					treeEnabledMenuUpdate(null);
				}
				/* if we change tab, the haptic highlight must be set again */
				selectHapticHighligh(null);
				/* so do the menu depending on the panel */
				diagramPanelEnabledMenuUpdate(diagramPanel);
			}
		};
		editorTabbedPane.addChangeListener(tabChangeListener);

		treeSelectionListener = new TreeSelectionListener(){
			@Override
			public void valueChanged(TreeSelectionEvent evt) {
				treeEnabledMenuUpdate(evt.getPath());
			}
		};

		netLocalDiagramExceptionHandler = new ExceptionHandler(){
			@Override
			public void handleException(Exception e) {
				SwingUtilities.invokeLater(new Runnable(){
					@Override
					public void run() {
						SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.local_server"));
					}
				});
			}
		};
	}

	private void initMenu(TemplateEditor[] templateEditors){
		ResourceFactory factory = new ResourceFactory(resources);

		JMenuBar menuBar = SpeechMenuFactory.getMenuBar();
		setJMenuBar(menuBar);

		/* --- FILE MENU --- */
		JMenu fileMenu = factory.createMenu("file");
		menuBar.add(fileMenu);

		/* menu items and listener added by addDiagramType function */
		newMenu = factory.createMenu("file.new");
		fileMenu.add(newMenu);

		JMenuItem fileOpenItem = factory.createMenuItem(
				"file.open", this, "openFile"); 
		fileMenu.add(fileOpenItem);      

		recentFilesMenu = factory.createMenu("file.recent");
		buildRecentFilesMenu();
		fileMenu.add(recentFilesMenu);

		fileSaveItem = factory.createMenuItem("file.save", this, "saveFile");
		fileMenu.add(fileSaveItem);

		fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs");
		fileMenu.add(fileSaveAsItem);
		
		fileSaveCopyItem = factory.createMenuItem("file.save_copy", this, "saveCopy");
		fileMenu.add(fileSaveCopyItem);

		fileCloseItem = factory.createMenuItem("file.close",this,"closeFile");
		fileMenu.add(fileCloseItem);

		graphExportItem = factory.createMenuItem("file.export_image", this, "exportImage"); 
		fileMenu.add(graphExportItem);

		JMenuItem fileExitItem = factory.createMenuItem(
				"file.exit", this, "exit");
		fileMenu.add(fileExitItem);

		/* --- EDIT MENU --- */ 
		JMenu editMenu = factory.createMenu("edit");
		menuBar.add(editMenu);

		locateMenuItem = factory.createMenuItem("edit.locate", this,"locate");
		editMenu.add(locateMenuItem);

		highlightMenuItem = factory.createMenuItem("edit.highlight", this, "hHighlight");
		highlightMenuItem.setEnabled(false);
		editMenu.add(highlightMenuItem);

		jumpMenuItem = factory.createMenuItem("edit.jump",this,"jump");
		editMenu.add(jumpMenuItem);

		insertMenuItem = factory.createMenuItem("edit.insert", this, "insert");
		editMenu.add(insertMenuItem);

		deleteMenuItem = factory.createMenuItem("edit.delete",this,"delete");
		editMenu.add(deleteMenuItem);

		renameMenuItem = factory.createMenuItem("edit.rename",this,"rename");
		editMenu.add(renameMenuItem);

		selectMenuItem = factory.createMenuItem("edit.select", this, "selectNode");
		editMenu.add(selectMenuItem);

		bookmarkMenuItem = factory.createMenuItem("edit.bookmark",this,"editBookmarks"); 
		editMenu.add(bookmarkMenuItem);  

		editNotesMenuItem = factory.createMenuItem("edit.edit_note",this,"editNotes"); 
		editMenu.add(editNotesMenuItem);

		/* --- VIEW MENU --- */
		JMenu viewMenu = factory.createMenu("view");
		menuBar.add(viewMenu);

		viewMenu.add(factory.createMenuItem(
				"view.zoom_out", new
				ActionListener(){
					public void actionPerformed(ActionEvent event){
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						dPanel.getGraphPanel().changeZoom(-1);
					}
				}));

		viewMenu.add(factory.createMenuItem(
				"view.zoom_in", new
				ActionListener(){
					public void actionPerformed(ActionEvent event){
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						dPanel.getGraphPanel().changeZoom(1);
					}
				}));

		viewMenu.add(factory.createMenuItem(
				"view.grow_drawing_area", new
				ActionListener(){
					public void actionPerformed(ActionEvent event){
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						GraphPanel gPanel = dPanel.getGraphPanel();
						Rectangle2D bounds = gPanel.getGraphBounds();
						bounds.add(gPanel.getBounds());
						gPanel.setMinBounds(new Rectangle2D.Double(0, 0, 
								GROW_SCALE_FACTOR * bounds.getWidth(), 
								GROW_SCALE_FACTOR * bounds.getHeight()));
						gPanel.revalidate();
						gPanel.repaint();
					}
				}));

		viewMenu.add(factory.createMenuItem(
				"view.clip_drawing_area", new
				ActionListener(){
					public void actionPerformed(ActionEvent event){
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						GraphPanel gPanel = dPanel.getGraphPanel();
						gPanel.setMinBounds(null); 
						gPanel.revalidate();
						gPanel.repaint();
					}
				}));

		viewMenu.add(factory.createMenuItem(
				"view.smaller_grid", new
				ActionListener()
				{
					public void actionPerformed(ActionEvent event)
					{
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						dPanel.getGraphPanel().changeGridSize(-1);
					}
				}));

		viewMenu.add(factory.createMenuItem(
				"view.larger_grid", new
				ActionListener(){
					public void actionPerformed(ActionEvent event){
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						dPanel.getGraphPanel().changeGridSize(1);
					}
				}));

		final JCheckBoxMenuItem hideGridItem;
		viewMenu.add(hideGridItem = (JCheckBoxMenuItem) factory.createCheckBoxMenuItem(
				"view.hide_grid", new
				ActionListener()
				{
					public void actionPerformed(ActionEvent event)
					{
						DiagramPanel dPanel = getActiveTab(); 
						if (dPanel == null) 
							return;
						JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) event.getSource();               
						dPanel.getGraphPanel().setHideGrid(menuItem.isSelected());
					}
				}));

		viewMenu.addMenuListener(new
				MenuListener(){   
			/* changes the checkbox according to the diagram selected */
			public void menuSelected(MenuEvent event){
				DiagramPanel dPanel = getActiveTab(); 
				if (dPanel == null) 
					return;
				hideGridItem.setSelected(dPanel.getGraphPanel().getHideGrid());
			}
			public void menuDeselected(MenuEvent event){}
			public void menuCanceled(MenuEvent event){}
		});

		JMenu lafMenu = factory.createMenu("view.change_laf");
		viewMenu.add(lafMenu);

		UIManager.LookAndFeelInfo[] infos =
			UIManager.getInstalledLookAndFeels();
		for (int i = 0; i < infos.length; i++){
			final UIManager.LookAndFeelInfo info = infos[i];
			JMenuItem item = SpeechMenuFactory.getMenuItem(info.getName());
			lafMenu.add(item);
			item.addActionListener(new
					ActionListener(){
				public void actionPerformed(ActionEvent event){
					String laf = info.getClassName();
					changeLookAndFeel(laf);
					preferences.put("laf", laf);
				}
			});
		}

		/* --- TEMPLATE --- */
		if(templateEditors.length > 0){
			JMenu templateMenu = factory.createMenu("template");
			menuBar.add(templateMenu);

			for(final TemplateEditor templateEditor : templateEditors){
				JMenuItem newDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForNew());
				newDiagramItem.addActionListener(new ActionListener(){
					@Override
					public void actionPerformed(ActionEvent evt) {
						Diagram diagram = templateEditor.createNew(EditorFrame.this, existingTemplateNames);
						if(diagram == null)
							return;
						try{
							saveDiagramTemplate(diagram);
						}catch(IOException ioe){
							SpeechOptionPane.showMessageDialog(
									EditorFrame.this, 
									resources.getString("dialog.error.save_template"));
							return;
						}
						addDiagramType(diagram);
						SpeechOptionPane.showMessageDialog(
								EditorFrame.this,
								MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
								SpeechOptionPane.INFORMATION_MESSAGE);
						SoundFactory.getInstance().play(SoundEvent.OK,null);
					}
				});
				templateMenu.add(newDiagramItem);

				JMenuItem editDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForEdit());
				editDiagramItem.addActionListener(new ActionListener(){
					@Override
					public void actionPerformed(ActionEvent evt){
						if(existingTemplates.isEmpty()){
							NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_template_to_edit"));
							return;
						}
						Diagram[] diagrams = new Diagram[existingTemplates.size()];
						diagrams = existingTemplates.toArray(diagrams);
						Diagram selectedDiagram = (Diagram)SpeechOptionPane.showSelectionDialog(
								EditorFrame.this, 
								resources.getString("dialog.input.edit_diagram_template"), 
								diagrams, 
								diagrams[0]);

						if(selectedDiagram == null){
							SoundFactory.getInstance().play(SoundEvent.CANCEL);
							return;
						}

						Diagram diagram = templateEditor.edit(EditorFrame.this, existingTemplateNames,selectedDiagram);
						if(diagram == null)
							return;
						try{
							saveDiagramTemplate(diagram);
						}catch(IOException ioe){
							SpeechOptionPane.showMessageDialog(
									EditorFrame.this, 
									resources.getString("dialog.error.save_template"));
							return;
						}
						addDiagramType(diagram);
						SpeechOptionPane.showMessageDialog(
								EditorFrame.this,
								MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()),
								SpeechOptionPane.INFORMATION_MESSAGE);
						SoundFactory.getInstance().play(SoundEvent.OK,null);
					}
				}
				);
				templateMenu.add(editDiagramItem);
			}
		}

		/* --- COLLABORATION ---- */
		JMenu collabMenu = factory.createMenu("collab"); 
		menuBar.add(collabMenu);

		startServerMenuItem = factory.createMenuItem("collab.start_server", this, "startServer");
		collabMenu.add(startServerMenuItem);

		stopServerMenuItem = factory.createMenuItem("collab.stop_server", this, "stopServer");
		collabMenu.add(stopServerMenuItem);
		stopServerMenuItem.setEnabled(false);

		shareDiagramMenuItem = factory.createMenuItem("collab.share_diagram", this, "shareDiagram");
		collabMenu.add(shareDiagramMenuItem);

		collabMenu.add(factory.createMenuItem("collab.open_shared_diagram", this, "openSharedDiagram"));

		showAwarenessPanelMenuItem = factory.createMenuItem("collab.show_awareness_panel", this, "showAwarenessPanel");
		collabMenu.add(showAwarenessPanelMenuItem);

		hideAwarenessPanelMenuItem = factory.createMenuItem("collab.hide_awareness_panel", this, "hideAwarenessPanel");
		collabMenu.add(hideAwarenessPanelMenuItem);

		awarenessPanelListener = new AwarenessPanelEnablingListener(){
			@Override
			public void awarenessPanelEnabled(boolean enabled) {
				if(enabled){
					showAwarenessPanelMenuItem.setEnabled(true);
					hideAwarenessPanelMenuItem.setEnabled(false);
				}else{
					showAwarenessPanelMenuItem.setEnabled(false);
					hideAwarenessPanelMenuItem.setEnabled(false);
				}
			}

			@Override
			public void awarenessPanelVisible(boolean visible) {
				if(visible){
					showAwarenessPanelMenuItem.setEnabled(false);
					hideAwarenessPanelMenuItem.setEnabled(true);
				}else{
					showAwarenessPanelMenuItem.setEnabled(true);
					hideAwarenessPanelMenuItem.setEnabled(false);
				}
			}
		};

		/* --- PREFERENCES --- */
		JMenu preferencesMenu = factory.createMenu("preferences");
		menuBar.add(preferencesMenu);

		/* show haptic window menu item only unless it's a actual haptic device thread * 
		 * that has its own window run by a native dll                                 */
		if(!HapticsFactory.getInstance().isAlive()){
			preferencesMenu.add(factory.createCheckBoxMenuItem("preferences.show_haptics", new ActionListener(){
				@Override
				public void actionPerformed(ActionEvent evt){
					JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
					haptics.setVisible(menuItem.isSelected());
					NarratorFactory.getInstance().speakWholeText(resources.getString(
							menuItem.isSelected() ? "speech.haptic_window_open" : "speech.haptic_window_close"));
				}
			}));
		}
		
		preferencesMenu.add(factory.createMenuItem("preferences.change_haptics", new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent evt){
				String [] hapticDevices = {
						HapticsFactory.PHANTOM_ID,
						HapticsFactory.FALCON_ID,
						HapticsFactory.TABLET_ID};
				String selection = (String)SpeechOptionPane.showSelectionDialog(
						EditorFrame.this, 
						resources.getString("dialog.input.haptics.select"), 
						hapticDevices, 
						hapticDevices[0]);
				if(selection == null)
					return;
				preferences.put("haptic_device", selection);
				SpeechOptionPane.showMessageDialog(EditorFrame.this, 
						MessageFormat.format(resources.getString("dialog.feedback.haptic_init"),selection),
						SpeechOptionPane.WARNING_MESSAGE);
			}
		}));
		
		// awareness menu 
		JMenu awarenessMenu = factory.createMenu("preferences.awareness");
		preferencesMenu.add(awarenessMenu);

		awarenessMenu.add(factory.createMenuItem("preferences.awareness.username", this, "showAwarnessUsernameDialog")); 
		awarenessMenu.add(factory.createMenuItem("preferences.awareness.broadcast", this, "showAwarenessBroadcastDialog"));
		awarenessMenu.add(factory.createMenuItem("preferences.awareness.display", this, "showAwarenessDisplayDialog"));

		JMenuItem enableAwarenessVoiceMenuItem = factory.createCheckBoxMenuItem("preferences.awareness.enable_voice",
				new ActionListener(){
					@Override
					public void actionPerformed(ActionEvent evt) {
						JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
						NarratorFactory.getInstance().setMuted(!menuItem.isSelected(),Narrator.SECOND_VOICE);
						PreferencesService.getInstance().put("second_voice_enabled", Boolean.toString(menuItem.isSelected()));
					}
				});
		NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE);
		enableAwarenessVoiceMenuItem.setSelected(Boolean.parseBoolean(
				PreferencesService.getInstance().get("second_voice_enabled", Boolean.toString(true))));
		awarenessMenu.add(enableAwarenessVoiceMenuItem);
		NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE);

		//  sound 
		JMenu soundMenu = factory.createMenu("preferences.sound");
		preferencesMenu.add(soundMenu);

		JMenuItem muteMenuItem = factory.createCheckBoxMenuItem("preferences.sound.mute", new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();               
				NarratorFactory.getInstance().setMuted(menuItem.isSelected(),Narrator.FIRST_VOICE);
				SoundFactory.getInstance().setMuted(menuItem.isSelected());
			}
		});
		soundMenu.add(muteMenuItem);

		JMenuItem rateMenuItem = factory.createMenuItem("preferences.sound.rate", new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				Integer newRate = SpeechOptionPane.showNarratorRateDialog(
						EditorFrame.this, 
						resources.getString("dialog.input.sound_rate"),
						NarratorFactory.getInstance().getRate(), 
						Narrator.MIN_RATE,
						Narrator.MAX_RATE);
				if(newRate != null){
					NarratorFactory.getInstance().setRate(newRate);
					NarratorFactory.getInstance().speak(
							MessageFormat.format(
									resources.getString("dialog.feedback.speech_rate"),
									newRate));
				}else{
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
				}
			}
		});
		soundMenu.add(rateMenuItem);

		//server ports
		JMenu networkingMenu = factory.createMenu("preferences.server");
		preferencesMenu.add(networkingMenu);

		JMenuItem localPortMenuItem = factory.createMenuItem("preferences.local_server.port", this, "showLocalServerPortDialog");
		networkingMenu.add(localPortMenuItem);

		JMenuItem remotePortMenuItem = factory.createMenuItem("preferences.remote_server.port", this, "showRemoteServerPortDialog");
		networkingMenu.add(remotePortMenuItem);

		// accessible file chooser
		JMenuItem fileChooserMenuItem = factory.createCheckBoxMenuItem("preferences.file_chooser", new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
				PreferencesService.getInstance().put("use_accessible_filechooser", Boolean.toString(menuItem.isSelected()));
			}
		});
		
		JMenuItem enableLogMenuItem = factory.createCheckBoxMenuItem("preferences.enable_log", new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent evt) {
				JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource();
				PreferencesService preferences = PreferencesService.getInstance();
				preferences.put("enable_log", Boolean.toString(menuItem.isSelected()));
				if(menuItem.isSelected()){
					try{
						InteractionLog.enable(preferences.get("dir.log", ""));
					}catch(IOException ioe){
						SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.log_enable"));
					}
				}else{
					InteractionLog.disable();
				}
			}
		});

		/* temporarily mute the narrator to select the menus without triggering a speech */ 
		NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE);
		fileChooserMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true")));
		enableLogMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("enable_log", "false")));
		NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE);
		preferencesMenu.add(fileChooserMenuItem);
		preferencesMenu.add(enableLogMenuItem);


		/* --- HELP --- */
		JMenu helpMenu = factory.createMenu("help");
		menuBar.add(helpMenu);

		helpMenu.add(factory.createMenuItem(
				"help.about", this, "showAboutDialog"));

		helpMenu.add(factory.createMenuItem(
				"help.license", this, "showLicense"));

		treeEnabledMenuUpdate(null);
		diagramPanelEnabledMenuUpdate(null);
	}

	private void changeLookAndFeel(String lafName){
		if(lafName == null)
			lafName = UIManager.getSystemLookAndFeelClassName();
		try{
			UIManager.setLookAndFeel(lafName);
			SwingUtilities.updateComponentTreeUI(EditorFrame.this);
		}
		catch (ClassNotFoundException ex) {}
		catch (InstantiationException ex) {}
		catch (IllegalAccessException ex) {}
		catch (UnsupportedLookAndFeelException ex) {}
	}

	/**
	 * Adds a file name to the "recent files" list and rebuilds the "recent files" menu. 
	 * @param newFile the file name to add
	 */
	private void addRecentFile(final String newFile){
		recentFiles.remove(newFile);
		if (newFile == null || newFile.equals("")) return;
		recentFiles.add(0, newFile);
		buildRecentFilesMenu();
	}

	/* speaks out the selected tree node if the tree is focused or the tab label if the tab is focused */
	private void speakFocusedComponent(String message){
		message = (message == null) ? "" : message+"; ";//add a dot to pause the TTS
		DiagramPanel dPanel = getActiveTab();
		if(dPanel != null){
			String focusedComponent = null;
			if(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTree)
				focusedComponent = dPanel.getTree().currentPathSpeech();
			else 
				focusedComponent = MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getComponentTabTitle(dPanel));
			NarratorFactory.getInstance().speak(message+focusedComponent);
		}
	}
	/**
	 * Rebuilds the "recent files" menu.
	 */
	private void buildRecentFilesMenu(){
		recentFilesMenu.removeAll();
		for (int i = 0; i < recentFiles.size(); i++){
			final String file =  recentFiles.get(i); 
			String name = new File(file).getName();
			JMenuItem item = SpeechMenuFactory.getMenuItem(name);
			item.setToolTipText(file);
			recentFilesMenu.add(item);
			item.addActionListener(new
					ActionListener(){
				public void actionPerformed(ActionEvent event){
					try {
						FileService.Open open = new FileService.DirectService().open(new File(((JMenuItem)event.getSource()).getToolTipText()));
						InputStream in = open.getInputStream();
						String path = open.getPath();
						for(int i=0; i<editorTabbedPane.getTabCount();i++){
							if(path.equals(editorTabbedPane.getToolTipTextAt(i))){
								editorTabbedPane.setSelectedIndex(i);
								return;
							}
						}
						Diagram diagram = path.endsWith(PdPersistenceManager.PD_EXTENSION) ? 
								PdPersistenceManager.getInstance().decodeDiagramInstance(in) : 
								PersistenceManager.decodeDiagramInstance(in);
						addTab(open.getPath(), diagram);
					} catch (IOException exception) {
						SpeechOptionPane.showMessageDialog(
								editorTabbedPane, 
								exception.getLocalizedMessage()); 
					}
				}
			});
		}      
	}

	/**
      Asks the user to open a graph file.
	 */
	public void openFile(){  
		InputStream in = null; 
		try{
			FileService.Open open = fileService.open(null,null, extensionFilter,this);
			in = open.getInputStream();
			if(in != null){ // open.getInputStream() == null -> user clicked on cancel
				String path = open.getPath();
				int index = editorTabbedPane.getPathTabIndex(path);
				if(index != -1){ //diagram is already open
					editorTabbedPane.setSelectedIndex(index);
					speakFocusedComponent("");
					return;
				}
				/* every opened diagram must have a unique name */
				if(editorTabbedPane.getDiagramNameTabIndex(open.getName()) != -1)
					throw new IOException(resources.getString("dialog.error.same_file_name"));
				iLog("START READ LOCAL DIAGRAM "+open.getName());
				Diagram diagram = path.endsWith(PdPersistenceManager.PD_EXTENSION) ? 
						PdPersistenceManager.getInstance().decodeDiagramInstance(in) :
						PersistenceManager.decodeDiagramInstance(in);
				iLog("END READ LOCAL DIAGRAM "+open.getName());
				/* force the name of the diagram to be the same as the file name       *
				 * it should never be useful, unless the .ccmi file is edited manually */
				diagram.setName(open.getName());
				addTab(open.getPath(), diagram);
				addRecentFile(open.getPath());
			}
		}
		catch (IOException exception)      {
			SpeechOptionPane.showMessageDialog(
					editorTabbedPane, 
					exception.getLocalizedMessage()); 
		}finally{
			if(in != null)
				try{in.close();}catch(IOException ioe){ioe.printStackTrace();}
		}
	}

	/**
	 * Close a diagram tab. If the diagram has not been saved the user will be 
	 * asked if they want to save the diagram.
	 */
	public void closeFile(){
		DiagramPanel dPanel = getActiveTab();
		if(dPanel.isModified()||dPanel.getFilePath() == null){
			int answer = SpeechOptionPane.showConfirmDialog(
					EditorFrame.this, 
					resources.getString("dialog.confirm.close"), 
					SpeechOptionPane.YES_NO_OPTION);

			if(answer == SpeechOptionPane.YES_OPTION) // save file only if the user decides to
				if(!saveFile()) 
					return; /* if the user closes the save dialog do nothing */
		}
		iLog("diagram closed :"+dPanel.getDiagram().getName());
		NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.diagram_closed"),dPanel.getDiagram().getName()));
		if(dPanel.getDiagram() instanceof NetDiagram){
			if(clientConnectionManager != null && clientConnectionManager.isAlive())
				clientConnectionManager.addRequest(new RmDiagramRequest(dPanel.getDiagram().getName()));
		}
		editorTabbedPane.remove(dPanel);
		//getActiveTab, after removing, returns the new selected panel after the remotion, if any  
		String newFocusedTabName = null;
		if(getActiveTab() != null){
			newFocusedTabName = getActiveTab().getDiagram().getName();
		}
		haptics.removeDiagram(dPanel.getDiagram().getName(), newFocusedTabName);
	}

	/**
	 * Saves the currently open tab diagram into a file. If the diagram has no file path associated
	 * (it has never been saved before), {@link #saveFileAs()} is called.  
	 * 
	 * @return {@code true} if the file is successfully saved, or {@code false} otherwise.
	 */
	public boolean saveFile(){  
		DiagramPanel diagramPanel = getActiveTab();
		if (diagramPanel == null) // no tabs open  
			return false;
		String filePath = diagramPanel.getFilePath();
		if (filePath == null) { 
			return saveFileAs(); 
		}

		OutputStream out = null;
		try{
			File file = new File(filePath);
			out = new BufferedOutputStream(new FileOutputStream(file));
			Diagram d = diagramPanel.getDiagram();
			if(d instanceof PdDiagram){
				PdPersistenceManager.getInstance().encodeDiagramInstance(d, out);
			}else {
				PersistenceManager.encodeDiagramInstance(d, out);
			}
			/* we saved the diagram, therefore there are no more pending changes */
			diagramPanel.setModified(false);
			speakFocusedComponent(resources.getString("dialog.file_saved"));
			return true;
		}catch(IOException ioe){
			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
			return false;
		}finally{
			try {
				out.close();
			}catch(IOException ioe){ /*can't do anything */ }
		}        
	}

	/**
	 * Prompts the user with a dialog for saving a diagram on disk. The current diagram is not affected 
	 * by this call and it keeps to be displayed in the editor. 
	 *    
	 * @return {@code true} if the file is successfully saved, or {@code false} otherwise.
	 */
	public boolean saveCopy(){
		DiagramPanel diagramPanel = getActiveTab();
		if (diagramPanel == null) // no tabs open  
			return false;
		OutputStream out = null;
		FileService.Save save;
		try {
			save = fileService.save(
					PreferencesService.getInstance().get("dir.diagrams", "."),
					diagramPanel.getFilePath(), //default file to save to 
					extensionFilter, 
					null, 
					defaultExtension,
					null);
			out = save.getOutputStream();
			if (out == null)   /* user didn't select any file for saving */
				return false;
			if(diagramPanel.getDiagram() instanceof PdDiagram){
				PdPersistenceManager.getInstance().encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
			}else{
				PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
			}
			speakFocusedComponent(resources.getString("dialog.file_saved"));
			return true;
		} catch (IOException ioe) {
			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
			return false;
		} finally {
			if(out != null)
				try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
		}
	}
	
	/**
     * Saves the current diagram as a new file. The user is prompter with a {@code FileChooser}
     * to chose a file to save the diagram to.
     * 
     * @return {@code true} if the file is successfully saved, or {@code false} otherwise.
	 */
	public boolean saveFileAs() { 
		DiagramPanel diagramPanel = getActiveTab();
		if (diagramPanel == null) // no tabs open  
			return false;
		String oldName = diagramPanel.getDiagram().getName();
		OutputStream out = null;
		try {
			String[] currentTabs = new String[editorTabbedPane.getTabCount()];
			for(int i=0; i<editorTabbedPane.getTabCount();i++){
				currentTabs[i] = editorTabbedPane.getTitleAt(i);
			}
			FileService.Save save = fileService.save(
					PreferencesService.getInstance().get("dir.diagrams", "."),
					diagramPanel.getFilePath(), 
					extensionFilter, 
					null, 
					defaultExtension,
					currentTabs);
			out = save.getOutputStream();
			if (out == null)   /* user didn't select any file for saving */
				return false;

			if(diagramPanel.getDiagram() instanceof PdDiagram){
				PdPersistenceManager.getInstance().encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
			}else{
				PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out);
			}
			
			/* update the diagram and the diageam panel, after the saving */
			diagramPanel.getDiagram().setName(save.getName());
			diagramPanel.setFilePath(save.getPath());
			diagramPanel.setModified(false);
			speakFocusedComponent(resources.getString("dialog.file_saved"));
			/* update the haptics, remove first the diagram that is going o be renamed * 
			 * and then re-add the diagram under the new name (avoids haptics          *  
			 * IllegalArgumentException when switching tab)                            */ 
			haptics.removeDiagram(oldName, null);
			haptics.addNewDiagram(diagramPanel.getDiagram().getName());
			for(Node n : diagramPanel.getDiagram().getCollectionModel().getNodes())
				haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null);
			for(Edge e : diagramPanel.getDiagram().getCollectionModel().getEdges()){
				Edge.PointRepresentation pr = e.getPointRepresentation();
				haptics.addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
			}
			return true;
		}catch(IOException ioe){
			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage());
			return false;
		}finally{
			if(out != null)
				try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
		}
	}

	/**
      Exits the program if no graphs have been modified
      or if the user agrees to abandon modified graphs.
	 */
	public void exit(){
		/* check first whether there are modified diagrams */
		int diagramsToSave = 0;
		for(int i=0; i<editorTabbedPane.getTabCount();i++){
			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
			if(dPanel.isModified()||dPanel.getFilePath() == null){
				diagramsToSave++;
			}
		}

		if(diagramsToSave > 0){
			int answer = SpeechOptionPane.showConfirmDialog(
					EditorFrame.this, 
					MessageFormat.format(resources.getString("dialog.confirm.exit"), diagramsToSave),
					SpeechOptionPane.YES_NO_OPTION);
			// if the doesn't want to save changes, veto the close
			if(answer != SpeechOptionPane.NO_OPTION){
				if(answer == SpeechOptionPane.YES_OPTION){ // user clicked on yes button we just get them back to the editor
					speakFocusedComponent("");
				}else{// user pressed the ESC button
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
				}
				return;
			}
		}

		NarratorFactory.getInstance().dispose();
		SoundFactory.getInstance().dispose();
		haptics.dispose();
		if(server != null)
			server.shutdown();
		if(clientConnectionManager != null)
			clientConnectionManager.shutdown();
		BroadcastFilter broadcastFilter = BroadcastFilter.getInstance();
		if(broadcastFilter != null)
			broadcastFilter.saveProperties(this);
		DisplayFilter displayFilter = DisplayFilter.getInstance();
		if(displayFilter != null)
			displayFilter.saveProperties(this);

		while(haptics.isAlive()){/* wait */}
		/* closes the logger's handlers */
		iLog("PROGRAM EXIT");
		InteractionLog.dispose();

		savePreferences();
		System.exit(0);
	}

	/**
	 * Changes the selection path of the diagram tree to a specific destination.   
	 * The user with a selection dialog to choose the destination from the following 
	 * choices : the root of the diagram, one of the element types, a bookmarked 
	 * node or a node/edge reference. 
	 */
	public void jump(){
		String[] options = new String[canJumpRef ? 4 : 3];
		options[0] = resources.getString("options.jump.type");
		options[1] = resources.getString("options.jump.diagram");
		options[2] = resources.getString("options.jump.bookmark");
		if(canJumpRef){
			options[3] = resources.getString("options.jump.reference");
		}
		iLog("open jump to dialog","");
		String result = (String)SpeechOptionPane.showSelectionDialog(
				EditorFrame.this,
				resources.getString("dialog.input.jump.select"),
				options, 
				options[0]);
		DiagramPanel dPanel = getActiveTab(); 
		DiagramTree tree = dPanel.getTree(); 
		if(result != null){
			if(result.equals(options[0])){ // jump type
				tree.jump(DiagramTree.JumpTo.SELECTED_TYPE);
			}else if(result.equals(options[1])){// diagram
				tree.jump(DiagramTree.JumpTo.ROOT);
			}else if(result.equals(options[2])){// bookmark
				tree.jump(DiagramTree.JumpTo.BOOKMARK);
			}else if(result.equals(options[3])){ 
				tree.jump(DiagramTree.JumpTo.REFERENCE);
			}
		}else{
			SoundFactory.getInstance().play(SoundEvent.CANCEL);
			iLog("cancel jump to dialog","");
		}
	}

	/**
	 * Locates on the haptic device the node or edge currently selected on the diagram tree. A command
	 * is sent to the haptic device which in turns drag the user to the node/edge location.  
	 */
	public void locate(){
		DiagramPanel dPanel = getActiveTab();
		DiagramTree tree = dPanel.getTree();
		DiagramElement de = (DiagramElement)tree.getSelectionPath().getLastPathComponent();
		HapticsFactory.getInstance().attractTo(System.identityHashCode(de));
		iLog("locate " +((de instanceof Node)? "node" : "edge"),DiagramElement.toLogString(de));
	}

	/**
	 * Selects on the diagram tree the node or edge that's currently being touched by the haptic
	 * device. 
	 */
	public void hHighlight() {
		getActiveTab().getTree().jumpTo(hapticHighlightDiagramElement);
		iLog("highlight " +((hapticHighlightDiagramElement instanceof Node)? "node" : "edge"),DiagramElement.toLogString(hapticHighlightDiagramElement));
	}

	/**
	 * Sends a command to the haptic device to pick up an element (node or edge) for 
	 * moving it.
	 * 
	 * @param de the diagram element to be picked up 
	 */
	public void hPickUp(DiagramElement de) {
		HapticsFactory.getInstance().pickUp(System.identityHashCode(de));
	}

	/**
	 * Prompts the user for an object insertion. The object can be a node, an edge, a property,
	 * a modifier, an edge label, an edge arrow head. Which object is to be inserted depends on 
	 * the currently selected tree node on the tree.
	 */
	public void insert(){
		DiagramPanel dPanel = getActiveTab();
		final DiagramTree tree = dPanel.getTree();
		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
		if(treeNode instanceof TypeMutableTreeNode){ //adding a diagram Element
			TypeMutableTreeNode typeNode = (TypeMutableTreeNode)treeNode;
			final DiagramElement diagramElement = (DiagramElement)typeNode.getPrototype().clone();
			try {
				if(diagramElement instanceof Edge){
					Edge edge = (Edge)diagramElement; 
					edge.connect(Arrays.asList(tree.getSelectedNodes()));
					iLog("insert edge",DiagramElement.toLogString(edge));
					modelUpdater.insertInTree(diagramElement);

					/* remove the selections on the edge's nodes and release their lock */
					tree.clearNodeSelections();
					for(int i=0; i<edge.getNodesNum();i++){
						modelUpdater.yieldLock(edge, Lock.MUST_EXIST,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.INSERT_EDGE,edge.getId(),edge.getName()));
					}
				}else{ // adding a Node 
					iLog("insert node ",DiagramElement.toLogString(diagramElement));
					modelUpdater.insertInTree(diagramElement);
				}
			} catch (ConnectNodesException cne) {
				final String message = cne.getLocalizedMessage();
				SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
					@Override
					public void playEnded() {
						NarratorFactory.getInstance().speak(message);
					}
				});
				SoundFactory.getInstance().play(SoundEvent.ERROR);
				iLog("insert edge error",message);
			}
		}else if(treeNode instanceof PropertyTypeMutableTreeNode){ //adding a property
			PropertyTypeMutableTreeNode propTypeNode = (PropertyTypeMutableTreeNode)treeNode;
			Node n = (Node)propTypeNode.getNode();
			if(!modelUpdater.getLock(n, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.ADD_PROPERTY,n.getId(),n.getName()))){
				iLog("Could not get lock on node for add properties",DiagramElement.toLogString(n));
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}

			iLog("open insert property dialog","");
			final String propertyValue = SpeechOptionPane.showInputDialog(EditorFrame.this, 
					MessageFormat.format(resources.getString("dialog.input.property.text"),	propTypeNode.getName()),
					""
			);
			if(propertyValue != null){
				if(!propertyValue.isEmpty()){ //check that the user didn't enter an empty string
					iLog("insert property ", propTypeNode.getType()+" "+propertyValue);
					modelUpdater.addProperty(n, propTypeNode.getType(), propertyValue, DiagramEventSource.TREE);
				}else{
					SoundFactory.getInstance().play(SoundEvent.EMPTY);
					iLog("insert property", "");
				}
			}else{ 
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				iLog("cancel insert property dialog","");
			}
			modelUpdater.yieldLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.ADD_PROPERTY,n.getId(),n.getName()));
		}else if(treeNode instanceof PropertyMutableTreeNode){ // edit modifiers
			iLog("open modifiers dialog","");
			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
			Node n = (Node)typeNode.getNode();
			Modifiers modifiers = n.getProperties().getModifiers(typeNode.getType());
			if(modifiers.isNull()){
				iLog("error:no modifiers for this property","");
				NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("dialog.warning.null_modifiers"),typeNode.getType()));
			}else{
				if(!modelUpdater.getLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_MODIFIERS,n.getId(),n.getName()))){
					iLog("Could not get lock on node for set modifiers",DiagramElement.toLogString(n));
					SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
					SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
					return;
				}

				int index = typeNode.getIndex(treeNode);
				Set<Integer> result = SpeechOptionPane.showModifiersDialog(EditorFrame.this, 
						MessageFormat.format(resources.getString("dialog.input.check_modifiers"),
								n.getProperties().getValues(typeNode.getType()).get(index)) , 
						modifiers.getTypes(), 
						modifiers.getIndexes(index)
				);
				if(result == null){
					iLog("cancel modifiers dialog","");
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
				}else{
					iLog("edit modifiers",Arrays.toString(result.toArray()));
					modelUpdater.setModifiers(n, typeNode.getType(), index, result,DiagramEventSource.TREE);
				}
				modelUpdater.yieldLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_MODIFIERS,n.getId(),n.getName()));
			}
		}else{ //NodeReferenceMutableTreeNode = edit label and arrow head 
			NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)treeNode;
			Node n = (Node)nodeRef.getNode();
			Edge e = (Edge)nodeRef.getEdge();
			if(!modelUpdater.getLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()))){
				iLog("Could not get lock on edge for end label",DiagramElement.toLogString(e));
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.end_label"), SpeechOptionPane.INFORMATION_MESSAGE);
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}
			iLog("open edge operation selection dialog","");

			boolean hasAvailArrowHeads = (e.getAvailableEndDescriptions().length > 0);
			String[] operations = new String[hasAvailArrowHeads ? 2 : 1];
			operations[0] = resources.getString("dialog.input.edge_operation.label");
			if(hasAvailArrowHeads)
				operations[1] = resources.getString("dialog.input.edge_operation.arrow_head");
			String choice = (String)SpeechOptionPane.showSelectionDialog(
					EditorFrame.this,
					resources.getString("dialog.input.edge_operation.select"),
					operations,
					operations[0]);

			if(choice == null){
				iLog("cancel edge operation selection dialog","");
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				modelUpdater.yieldLock(e, Lock.EDGE_END,
						new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()));
				return;
			}
			if(choice.equals(operations[0])){  //operations[0] = edit edge end-label
				iLog("open edge label dialog","");
				String label = SpeechOptionPane.showInputDialog(
						EditorFrame.this,
						MessageFormat.format(resources.getString("dialog.input.edge_label"),n.getType(), n.getName()),
						e.getEndLabel(n) );
				if(label != null){
					modelUpdater.setEndLabel(e, n, label,DiagramEventSource.TREE);
					SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){
						@Override
						public void playEnded() {
							NarratorFactory.getInstance().speak(tree.currentPathSpeech());
						}
					});
				}else{
					iLog("cancel edge label dialog","");
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
				}
			}else{//operations[1] = edit edge arrow head 
				String[] endDescriptions = new String[e.getAvailableEndDescriptions().length+1];
				for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
					endDescriptions[i] = e.getAvailableEndDescriptions()[i];
				endDescriptions[endDescriptions.length-1] = Edge.NO_ENDDESCRIPTION_STRING;

				iLog("open edge arrow head dialog","");
				final String endDescription = (String)SpeechOptionPane.showSelectionDialog(
						EditorFrame.this,
						MessageFormat.format(resources.getString("dialog.input.edge_arrowhead"),n.getType(), n.getName()),
						endDescriptions,
						endDescriptions[0]
				);
				if(endDescription != null){
					int index = Edge.NO_END_DESCRIPTION_INDEX;
					for(int i=0;i<e.getAvailableEndDescriptions().length;i++)
						if(endDescription.equals(e.getAvailableEndDescriptions()[i])){
							index = i;
							break;
						}
					modelUpdater.setEndDescription(e, n, index,DiagramEventSource.TREE);
				}else{
					iLog("cancel edge arrow head dialog","");
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
				}
			}
			modelUpdater.yieldLock(e, Lock.EDGE_END,
					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()));
		}
	}

	/**
	 * Prompts the user for an object deletion. The object can be a node, an edge, a property,
	 * a modifier, an edge label, an edge arrow head. Which object is to be deleted depends on 
	 * the currently selected tree node on the tree.
	 */
	public void delete(){
		DiagramPanel dPanel = getActiveTab();
		final DiagramTree tree = dPanel.getTree();
		final DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
		if(treeNode instanceof DiagramElement){ //delete a diagram element
			final DiagramElement element = (DiagramElement)treeNode;
			boolean isNode = element instanceof Node;
			if(!modelUpdater.getLock(element, 
					Lock.DELETE, 
					new DiagramEventActionSource(DiagramEventSource.TREE,
							isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
									element.getId(),element.getName()))){
				iLog("Could not get lock on element for deletion",DiagramElement.toLogString(element));
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.delete"), SpeechOptionPane.INFORMATION_MESSAGE);
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}
			iLog("open delete "+ (isNode ? "node" : "edge") +" dialog","");
			int choice = SpeechOptionPane.showConfirmDialog(
					EditorFrame.this, 
					MessageFormat.format(resources.getString("dialog.confirm.deletion"),element.getType(), element.getName()), 
					SpeechOptionPane.OK_CANCEL_OPTION);
			if(choice != SpeechOptionPane.OK_OPTION){
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				iLog("cancel delete " + (isNode ? "node" : "edge") +" dialog","");
				modelUpdater.yieldLock(element, 
						Lock.DELETE,
						new DiagramEventActionSource(DiagramEventSource.TREE,
								isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
										element.getId(),
										element.getName()));
				return;
			}
			modelUpdater.takeOutFromTree(element);
			/* don't need to unlock because the object doesn't exist any more, but        *
			 * still need to make other users aware that the deletion process is finished */
			modelUpdater.sendAwarenessMessage(
					AwarenessMessage.Name.STOP_A,
					new DiagramEventActionSource(DiagramEventSource.TREE,
							isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE,
									element.getId(),element.getName())
			);
		}else if(treeNode.getParent() instanceof PropertyTypeMutableTreeNode){ //deleting a property
			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
			Node n = (Node)typeNode.getNode();
			if(!modelUpdater.getLock(n, 
					Lock.PROPERTIES,
					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.REMOVE_PROPERTY,n.getId(),n.getName()))){
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
				iLog("Could not get lock for properties for deletion",DiagramElement.toLogString(n));
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}
			iLog("open delete property dialog","");
			int choice = SpeechOptionPane.showConfirmDialog(
					EditorFrame.this, 
					MessageFormat.format(resources.getString("dialog.confirm.deletion"),typeNode.getType(),treeNode.getName()), 
					SpeechOptionPane.OK_CANCEL_OPTION);
			if(choice != SpeechOptionPane.OK_OPTION){
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				iLog("cancel delete property dialog","");
				modelUpdater.yieldLock(n, 
						Lock.PROPERTIES,
						new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.REMOVE_PROPERTY,n.getId(),n.getName()));
				return;
			}
			modelUpdater.removeProperty(n, typeNode.getType(), typeNode.getIndex(treeNode),DiagramEventSource.TREE);
		}else
			throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
	}

	/**
	 * Prompts the user for an object deletion. The object can be a node, an edge or a property.
	 * Which object is to be renamed depends on the currently selected tree node on the tree.
	 */
	public void rename(){
		DiagramPanel dPanel = getActiveTab();
		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
		final DiagramTree tree = dPanel.getTree();
		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent();
		MessageFormat formatter = new MessageFormat(resources.getString("dialog.input.rename"));
		if(treeNode instanceof DiagramElement){
			DiagramElement element = (DiagramElement)dPanel.getTree().getSelectionPath().getLastPathComponent();
			Object arg[] = {element.getName()};
			boolean isNode = element instanceof Node;
			if(!modelUpdater.getLock(element, 
					Lock.NAME,
					new DiagramEventActionSource(DiagramEventSource.TREE,
							isNode ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME,element.getId(),element.getName()))){
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.name"),SpeechOptionPane.INFORMATION_MESSAGE);
				iLog("Could not get lock on element for renaming",DiagramElement.toLogString(element));
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}
			iLog("open rename "+(isNode ? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
			String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
					formatter.format(arg),
					element.getName());
			if(name != null){
				modelUpdater.setName(element,name,DiagramEventSource.TREE);
			}else{
				iLog("cancel rename "+(isNode ? "node" : "edge")+" dialog",DiagramElement.toLogString(element));
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
			}
			modelUpdater.yieldLock(element, 
					Lock.NAME,
					new DiagramEventActionSource(
							DiagramEventSource.TREE,
							isNode ? Command.Name.SET_NODE_NAME : Command.Name.SET_EDGE_NAME,
									element.getId(),
									element.getName()));
		}else if(treeNode instanceof PropertyMutableTreeNode){
			PropertyTypeMutableTreeNode typeNode = (PropertyTypeMutableTreeNode)treeNode.getParent(); 
			Node n = (Node)typeNode.getNode();
			if(!modelUpdater.getLock(n, 
					Lock.PROPERTIES, 
					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_PROPERTY,n.getId(),n.getName()))){
				SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.properties"), SpeechOptionPane.INFORMATION_MESSAGE);
				iLog("Could not get lock on properties for renaming",DiagramElement.toLogString(n));
				SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
				return;
			}
			Object arg[] = {treeNode.getName()};
			iLog("open rename property dialog",treeNode.getName());
			String name = SpeechOptionPane.showInputDialog(EditorFrame.this,
					formatter.format(arg),
					treeNode.getName());
			if(name == null){
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				iLog("cancel rename property dialog",treeNode.getName());
				return;
			}
			modelUpdater.setProperty(n, typeNode.getType(), typeNode.getIndex(treeNode), name,DiagramEventSource.TREE);
			modelUpdater.yieldLock(n, 
					Lock.PROPERTIES,
					new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_PROPERTY,n.getId(),n.getName()));
		}else
			throw new IllegalStateException("Cannot delete a tree node of type: "+treeNode.getClass().getName());
	}

	/**
	 * Prompts the user with a dialog to add or remove bookmarks on a tree node. Which node is to be 
	 * (un)bookmarked depends on the currently selected tree node on the tree.
	 */
	public void editBookmarks(){
		boolean addBookmark = true;
		DiagramPanel dPanel = getActiveTab(); 
		final DiagramTree tree = dPanel.getTree();
		DiagramTreeNode treeNode = (DiagramTreeNode)tree.getLastSelectedPathComponent();
		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();

		if(!modelUpdater.getLock(treeNode, 
				Lock.BOOKMARK, 
				DiagramEventActionSource.NULL)){
			iLog("Cannot get lock on tree node for bookmark", treeNode.getName());
			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.bookmark"), SpeechOptionPane.INFORMATION_MESSAGE);
			SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
			return;
		}

		if(!treeNode.getBookmarkKeys().isEmpty()){
			/* the are already bookmarks, thus we let the user chose whether they want to   */
			/* add a new one or remove an old one                                           */
			String[] options = {
					resources.getString("dialog.input.bookmark.select.add"),
					resources.getString("dialog.input.bookmark.select.remove")
			};

			iLog("open select add/remove bookmark dialog","");
			String result = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this,
					resources.getString("dialog.input.bookmark.select.add_remove"), 
					options, 
					options[0]);

			if(result == null){
				iLog("cancel select add/remove bookmark dialog","");
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				modelUpdater.yieldLock(treeNode, Lock.BOOKMARK, DiagramEventActionSource.NULL);
				return;
			}
			if(result.equals(options[1]))
				addBookmark = false;
		}

		if(addBookmark){
			boolean uniqueBookmarkChosen = false;
			while(!uniqueBookmarkChosen){
				iLog("open add bookmark dialog","");
				String bookmark = SpeechOptionPane.showInputDialog(EditorFrame.this, resources.getString("dialog.input.bookmark.text"),"");
				if(bookmark != null){
					if("".equals(bookmark)){
						iLog("error: entered empty bookmark","");
						SoundFactory.getInstance().play(SoundEvent.ERROR);// without listeners in order not to overwrite the speechdialog popping up again
						NarratorFactory.getInstance().speakWholeText(resources.getString("dialog.input.bookmark.text.empty"));
					}else if(tree.getModel().getBookmarks().contains(bookmark)){
						iLog("error: entered bookmark already existing",bookmark);
						SoundFactory.getInstance().play(SoundEvent.ERROR);
						NarratorFactory.getInstance().speakWholeText(resources.getString("dialog.input.bookmark.text.already_existing"));
					}else{
						tree.getModel().putBookmark(bookmark, treeNode,DiagramEventSource.TREE);
						uniqueBookmarkChosen = true;
					}
				}else{
					SoundFactory.getInstance().play(SoundEvent.CANCEL);
					iLog("cancel add bookmark dialog","");
					break; //user no longer wants to choose, exit the dialog thus
				}
			}
		}else{ // removing a bookmark
			String[] bookmarksArray = new String[treeNode.getBookmarkKeys().size()];
			bookmarksArray = treeNode.getBookmarkKeys().toArray(bookmarksArray);

			iLog("open remove bookmark dialog","");
			final String bookmark = (String)SpeechOptionPane.showSelectionDialog(
					EditorFrame.this, 
					resources.getString("dialog.input.bookmark.delete"),
					bookmarksArray,
					bookmarksArray[0]
			);

			if(bookmark != null){
				tree.getModel().removeBookmark(bookmark,DiagramEventSource.TREE);
			}else{
				iLog("cancel remove bookmark dialog","");
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
			}
		}
		modelUpdater.yieldLock(treeNode, Lock.BOOKMARK, DiagramEventActionSource.NULL);
	}

	/**
	 * Prompts the user with a dialog edit notes on a tree node. Which node is to be 
	 * noted depends on the currently selected tree node on the tree.
	 */
	public void editNotes(){
		DiagramPanel dPanel = getActiveTab();
		DiagramTreeNode treeNode = (DiagramTreeNode)dPanel.getTree().getLastSelectedPathComponent();
		DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater();
		if(!modelUpdater.getLock(treeNode, Lock.NOTES, DiagramEventActionSource.NULL)){
			iLog("Could not get lock on tree node for notes",treeNode.getName());
			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.notes"), SpeechOptionPane.INFORMATION_MESSAGE);
			SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK);
			return;
		}

		String typeString = "";
		/* if the note is for a diagram element the dialog message is changed so that the type precedes the name */
		if(treeNode instanceof DiagramElement){
			typeString = ((DiagramElement)treeNode).getType() + " ";
		}
		/* if the note is for a property tree node the dialog message is changed so that the type precedes the name */
		if(treeNode instanceof PropertyMutableTreeNode){
			PropertyTypeMutableTreeNode parent = (PropertyTypeMutableTreeNode)treeNode.getParent();
			typeString = parent.getType() + " ";
		}
		iLog("open edit note dialog","");
		String result = SpeechOptionPane.showTextAreaDialog(EditorFrame.this, resources.getString("dialog.input.notes.text")+typeString+treeNode.getName() ,treeNode.getNotes());
		if(result != null){
			modelUpdater.setNotes(treeNode, result,DiagramEventSource.TREE);
		}else{
			iLog("cancel edit note dialog","");
			SoundFactory.getInstance().play(SoundEvent.CANCEL);
		}
		modelUpdater.yieldLock(treeNode, Lock.NOTES,DiagramEventActionSource.NULL);
	}

	/**
	 * Starts the server on a background thread.
	 */
	public void startServer(){
		iLog("server started","");
		/* If the awareness filter has not been created yet (by opening the awareness filter dialog) then create it */
		BroadcastFilter filter = BroadcastFilter.getInstance();
		if(filter == null)
			try{
				filter = BroadcastFilter.createInstance();
			}catch(IOException ioe){
				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
			}
			/* create the server */
			server = Server.createServer();
			try{
				server.init();
			}catch(IOException ioe){
				SpeechOptionPane.showMessageDialog(
						editorTabbedPane, 
						ioe.getLocalizedMessage());
				iLog("error: starting server",ioe.getLocalizedMessage());
				return;
			}
			server.start();
			startServerMenuItem.setEnabled(false);
			stopServerMenuItem.setEnabled(true);
			if(getActiveTab() != null && (!(getActiveTab().getDiagram() instanceof NetDiagram)))
				shareDiagramMenuItem.setEnabled(true);
	}

	/**
	 * Stops the running server
	 */
	public void stopServer(){
		/* those network diagrams which are connected to the local server are reverted,       *
		 * that is the diagram panel is set with the delegate diagram of the network diagram  */
		for(int i=0; i < editorTabbedPane.getTabCount(); i++){
			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i);
			if(dPanel.getDiagram() instanceof NetDiagram){
				NetDiagram netDiagram = (NetDiagram)dPanel.getDiagram();
				if(netDiagram.getSocketChannel().equals(localSocket)){
					dPanel.setAwarenessPanelEnabled(false);
					dPanel.setDiagram(netDiagram.getDelegate());
				}
			}
		}
		server.shutdown(resources.getString("server.shutdown_msg"));
		server = null;
		if(localSocket != null){
			try{localSocket.close();}catch(IOException ioe){ioe.printStackTrace();}
			localSocket = null;
		}
		startServerMenuItem.setEnabled(true);
		stopServerMenuItem.setEnabled(false);
		shareDiagramMenuItem.setEnabled(false);
		if(getActiveTab() != null)
			fileCloseItem.setEnabled(true);
		iLog("server stopped","");
	}

	/**
	 * Makes a diagram shared on the server. When a diagram is shared, remote users can connect to the 
	 * server and edit it collaboratively with the local and the other connected users. 
	 */
	public void shareDiagram(){
		try{
			if(server == null)
				throw new DiagramShareException(resources.getString("server.not_running_exc"));

			DiagramPanel dPanel = getActiveTab(); 
			Diagram diagram = dPanel.getDiagram();
			try {
				iLog("share diagram",diagram.getName());
				/* check if it's already connected to the local server (a.k.a. another diagram has been shared previously */
				if(localSocket == null){
					int port = Integer.parseInt(PreferencesService.getInstance().get("server.local_port",Server.DEFAULT_REMOTE_PORT));
					InetSocketAddress address = new InetSocketAddress("127.0.0.1",port);
					localSocket = SocketChannel.open(address);
				}

				server.share(diagram);
				ProtocolFactory.newInstance().send(localSocket, new Command(Command.Name.LOCAL,diagram.getName(),DiagramEventSource.NONE));
				dPanel.setDiagram(NetDiagram.wrapLocalHost(diagram,localSocket,server.getLocalhostQueue(diagram.getName()),netLocalDiagramExceptionHandler)); 
				/* share a diagram once is enought :) */
				shareDiagramMenuItem.setEnabled(false);
				/* no close, there might be clients connected. unshare first. */
				fileCloseItem.setEnabled(false);
				/* only enabled for local diagram, otherwise changing the name * 
				 * of the diagram would messes up the network protocol         */
				fileSaveAsItem.setEnabled(false);
				dPanel.setAwarenessPanelEnabled(true);
			} catch (IOException e) {
				iLog("error sharing diagram",diagram.getName()+" "+e.getLocalizedMessage());
				SpeechOptionPane.showMessageDialog(EditorFrame.this, e.getLocalizedMessage());
				return;
			}
		}catch(DiagramShareException dse){
			SpeechOptionPane.showMessageDialog(EditorFrame.this, dse.getLocalizedMessage());
		}
	}

	/**
	 * Prompts the user for a server address and connect to the server. The server is queried for the list 
	 * of the shared diagrams and the user is prompted  with a selection dialog to chose a diagram to download.
	 * The diagram is then downloaded and loaded into the diagram editor, ready for shared editing.  
	 * 
	 */
	public void openSharedDiagram(){
		iLog("open open share diagram dialog","");
		/* open the window prompting for the server address and make checks on the user input */
		String addr = SpeechOptionPane.showInputDialog(
				EditorFrame.this, 
				resources.getString("dialog.share_diagram.enter_address"),
				PreferencesService.getInstance().get("server.address", ""));
		if(addr == null){
			SoundFactory.getInstance().play(SoundEvent.CANCEL);
			iLog("cancel open share diagram dialog","");
			return;
		}else if(!ProtocolFactory.validateIPAddr(addr)){
			iLog("error:invalid IP address",addr);
			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.share_diagram.wrong_ip"));
			return;
		}else{
			PreferencesService.getInstance().put("server.address", addr);
			/* open the channel for the new diagram */
			SocketChannel channel = null;
			try {
				channel = SocketChannel.open();
			} catch (IOException e) {
				iLog("error:could not connect to the server","");
				SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.no_connection_to_server"));
				return;
			}
			/* download the diagram list */
			DiagramDownloader downloader = new DiagramDownloader(
					channel,
					addr, 
					DiagramDownloader.CONNECT_AND_DOWNLOAD_LIST_TASK
			);
			iLog("open download diagram list dialog","");
			int option = SpeechOptionPane.showProgressDialog(EditorFrame.this, resources.getString("dialog.downloading_diagram_list"), downloader,500);
			if(option == SpeechOptionPane.CANCEL_OPTION){
				iLog("cancel download diagram list dialog","");
				SoundFactory.getInstance().play(SoundEvent.CANCEL);
				try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
			}else{
				try{
					/* show the available diagram list */
					String result = downloader.get();
					if(result == null)
						throw new Exception(resources.getString("dialog.error.no_diagrams_on_server")); // go to the catch block 
					String[] diagramsList = result.split("\n");

					iLog("open select diagram to download dialog","");
					String diagramName = (String)SpeechOptionPane.showSelectionDialog(EditorFrame.this, "Select diagram to download", diagramsList, diagramsList[0]);
					if(diagramName == null){
						iLog("cancel select diagram to download dialog","");
						SoundFactory.getInstance().play(SoundEvent.CANCEL);
						try{channel.close();}catch(IOException ioe){ioe.printStackTrace();}
						return;
					}
					/* there cannot be two diagrams with the same name open at the same time */
					if(editorTabbedPane.getDiagramNameTabIndex(diagramName) != -1)
						throw new IOException(resources.getString("dialog.error.same_file_name"));
					/* download the chosen diagram */
					downloader = new DiagramDownloader(channel,diagramName,DiagramDownloader.DOWNLOAD_DIAGRAM_TASK);
					iLog("open downloading diagram dialog",diagramName);
					option = SpeechOptionPane.showProgressDialog(EditorFrame.this, MessageFormat.format(resources.getString("dialog.downloading_diagram"), diagramName), downloader,500);
					if(option == SpeechOptionPane.CANCEL_OPTION){
						iLog("cancel downloading diagram dialog",diagramName);
						SoundFactory.getInstance().play(SoundEvent.CANCEL); 
						try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
					}else{
						result = downloader.get();

						if(clientConnectionManager == null || !clientConnectionManager.isAlive()){
							clientConnectionManager = new ClientConnectionManager(editorTabbedPane);
							clientConnectionManager.start();
						}

						iLog("START READ NETWORK DIAGRAM "+diagramName);
						// FIXME no pd patches supported 
						Diagram diagram = PersistenceManager.decodeDiagramInstance(new BufferedInputStream(new ByteArrayInputStream(result.getBytes("UTF-8"))));
						iLog("END READ NETWORK DIAGRAM "+diagramName);
						/* remove all the bookmarks in the server diagram model instance */
						for(String bookmarkKey : diagram.getTreeModel().getBookmarks())
							diagram.getTreeModel().removeBookmark(bookmarkKey,DiagramEventSource.TREE);
						Diagram newDiagram = NetDiagram.wrapRemoteHost(diagram,clientConnectionManager,channel);
						DiagramPanel dPanel = addTab(null,newDiagram);
						/* enable awareness on the new diagram */
						dPanel.setAwarenessPanelEnabled(true);
						/* make the network thread aware of the new shared diagram, from here on the messages received from the server will take effect */
						clientConnectionManager.addRequest(new ClientConnectionManager.AddDiagramRequest(channel, diagram));
						clientConnectionManager.addRequest(new SendAwarenessRequest(channel, new AwarenessMessage(
								AwarenessMessage.Name.USERNAME_A,
								newDiagram.getName(),
								AwarenessMessage.getDefaultUserName()
						)));
					}
				}catch(RuntimeException rte){
					throw new RuntimeException(rte);
				}catch(ExecutionException ee){
					try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
					/* if the exception happened in the DiagramDownloader then it's wrapped into an   *
					 * ExecutionException and we have to unwrap it to get a neat message for the user */
					SpeechOptionPane.showMessageDialog(
							editorTabbedPane, 
							ee.getCause().getLocalizedMessage());
					iLog("error: "+ee.getCause().getMessage(),"");
				}catch(Exception exception){
					try{channel.close();}catch(IOException ioe){ioe.printStackTrace();};
					SpeechOptionPane.showMessageDialog(
							editorTabbedPane, 
							exception.getLocalizedMessage()); 
					iLog("error: "+exception.getMessage(),"");
				}
			}
		}
	}

	/**
	 * Shows the awareness panel, a text pane where all the awareness informations received by the server
	 * are displayed. 
	 */
	public void showAwarenessPanel(){
		DiagramPanel dPanel = getActiveTab();
		if(dPanel != null){
			dPanel.setAwarenessPanelVisible(true);
			NarratorFactory.getInstance().speak(resources.getString("speech.awareness_panel.open"));
		}
	}

	/**
	 * Hides the awareness panel, a text pane where all the awareness informations received by the server
	 * are displayed. 
	 */
	public void hideAwarenessPanel(){
		DiagramPanel dPanel = getActiveTab();
		if(dPanel != null){
			dPanel.setAwarenessPanelVisible(false);
			NarratorFactory.getInstance().speak(resources.getString("speech.awareness_panel.close"));
			dPanel.getTree().requestFocus();
		}
	}

	/**
	 * Saves all the open diagram which have been modified since the last time they were saved into the 
	 * <i>backup</i> folder in the ccmi_editor_data directory.  
	 */
	public void backupOpenDiagrams(){
		SimpleDateFormat dateFormat = new SimpleDateFormat("EEE_d_MMM_yyyy_HH_mm_ss");
		String date = dateFormat.format(new Date());
		File backupDir = new File(new StringBuilder(backupDirPath)
		.append(System.getProperty("file.separator"))
		.append(date)
		.toString());
		backupDir.mkdir();
		for(int i=0; i<editorTabbedPane.getTabCount();i++){
			DiagramPanel dPanel = editorTabbedPane.getComponentAt(i); 
			if(dPanel.isModified()||dPanel.getFilePath() == null){
				Diagram diagram = dPanel.getDiagram();
				File file = new File(backupDir,diagram.getName()+".ccmi");
				try {
					FileService.Save save = new FileService.DirectService().save((file));
					if(diagram instanceof PdDiagram){
						PdPersistenceManager.getInstance().encodeDiagramInstance(diagram, save.getOutputStream());
					}else {
						PersistenceManager.encodeDiagramInstance(diagram, save.getOutputStream());
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
      Exports the current graph to an image file.
	 */
	public void exportImage(){  
		DiagramPanel dPanel = getActiveTab(); 
		if (dPanel == null) 
			return;
		OutputStream out = null;
		try{
			String imageExtensions = resources.getString("files.image.extension");
			/* default save dir is the same as the diagram's or home/images otherwise */
			String path = dPanel.getFilePath();
			if(path == null)
				path = PreferencesService.getInstance().get("dir.images", ".");
			FileService.Save save = fileService.save(path, dPanel.getDiagram().getName(), exportFilter, 
					defaultExtension, imageExtensions,null);
			out = save.getOutputStream();
			if (out != null){
				/* if the diagram has a name (has already been saved) then prompt the user with the name of 
				 * the diagram with a jpg extension. */
				String fileName = FileService.getFileNameFromPath(save.getPath(),true);
				String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
				if (!ImageIO.getImageWritersByFormatName(extension).hasNext()){
					throw new IOException(MessageFormat.format(
							resources.getString("dialog.error.unsupported_image"),
							extension
					));
				}
				GraphPanel gPanel = dPanel.getGraphPanel();
				try{
					saveImage(gPanel, out, extension);
					speakFocusedComponent(resources.getString("dialog.file_saved"));
				}catch(IOException ioe){
					throw new IOException(resources.getString("dialog.error.save_image"),ioe);
				}
			}
		}
		catch (IOException ioe){
			SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getMessage());
		}finally{
			if(out != null)
				try{out.close();}catch(IOException ioe){ioe.printStackTrace();}
		}
	}

	/**
      Exports a current graph to an image file.
      @param graph the graph
      @param out the output stream
      @param format the image file format
      
      @throws IOException if something goes wrong during the I/O operations 
	 */
	public static void saveImage(GraphPanel graph, OutputStream out, String format)
	throws IOException { 
		// need a dummy image to get a Graphics to measure the size
		Rectangle2D bounds = graph.getBounds();
		BufferedImage image 
		= new BufferedImage((int)bounds.getWidth() + 1,
				(int)bounds.getHeight() + 1, 
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g2 = (Graphics2D)image.getGraphics();
		g2.translate(-bounds.getX(), -bounds.getY());
		g2.setColor(Color.WHITE);
		g2.fill(new Rectangle2D.Double(
				bounds.getX(),
				bounds.getY(), 
				bounds.getWidth() + 1,
				bounds.getHeight() + 1));
		g2.setColor(Color.BLACK);
		g2.setBackground(Color.WHITE);
		boolean hideGrid = graph.getHideGrid();
		graph.setHideGrid(true);
		graph.paintComponent(g2);
		graph.setHideGrid(hideGrid);
		ImageIO.write(image, format, out);
	}

	/**
	 * Shows the configuration dialog of the broadcast filter. The broadcast filter affects
	 * which awareness informations are broadcasted from the server to the other clients. 
	 * If the local editor is not running the server, changes to the broadcast filter 
	 * will have no effect.  
	 */
	public void showAwarenessBroadcastDialog(){
		BroadcastFilter filter = BroadcastFilter.getInstance();
		if(filter == null)
			try{
				filter = BroadcastFilter.createInstance();
			}catch(IOException ioe){
				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
			}
			filter.showDialog(this);   
	}

	/**
	 * Shows the configuration dialog of the display filter. The display filter affects
	 * which awareness informations are received from the server are actually displayed
	 * to the user. 
	 */
	public void showAwarenessDisplayDialog(){
		DisplayFilter filter = DisplayFilter.getInstance();
		if(filter == null)
			try{
				filter = DisplayFilter.createInstance();
			}catch(IOException ioe){
				SpeechOptionPane.showMessageDialog(this, ioe.getLocalizedMessage());
			}
			filter.showDialog(this); 
	}

	/**
	 * Prompts the user with a dialog to choose he awareness username. The username is used in the 
	 * awareness information to identify which client is doing the actions that are being notified.  
	 */
	public void showAwarnessUsernameDialog(){
		String oldName = AwarenessMessage.getDefaultUserName();
		String newName = SpeechOptionPane.showInputDialog(
				this, 
				resources.getString("dialog.input.awerness_username"), 
				oldName
		);
		if(newName == null){
			SoundFactory.getInstance().play(SoundEvent.CANCEL);
			return;
		}

		if(newName.trim().isEmpty()){
			SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
				@Override
				public void playEnded() {
					NarratorFactory.getInstance().speak(resources.getString("speech.empty_userame"));
				}
			});
			return;
		}

		if(!newName.equals(oldName)){//if the name hasn't changed don't issue any message 
			PreferencesService.getInstance().put("user.name", newName);
			AwarenessMessage.setDefaultUserName(newName);
			NarratorFactory.getInstance().speak(MessageFormat.format(
					resources.getString("dialog.feedback.awareness_username"),
					newName
			));
			for(int i=0; i<editorTabbedPane.getTabCount(); i++){
				Diagram diagram = editorTabbedPane.getComponentAt(i).getDiagram(); 
				diagram.getModelUpdater().sendAwarenessMessage(
						AwarenessMessage.Name.USERNAME_A,
						newName);// send the new name only, the old name will be added by the server 
			}
		}
	}

	/**
	 * Prompts the user with a dialog to choose the port number the local server will listen on. 
	 */
	public void showLocalServerPortDialog(){
		showServerPortDialog("server.local_port","dialog.input.local_server_port","dialog.feedback.local_server_port");
	}

	/**
	 * Prompts the user with a dialog to choose the remote server port number to connect to.  
	 */
	public void showRemoteServerPortDialog(){
		showServerPortDialog("server.remote_port","dialog.input.remote_server_port","dialog.feedback.remote_server_port");
	}

	private void showServerPortDialog(String preferenceKey,String dialogMessage,String feedbackMessage){
		String oldPort = PreferencesService.getInstance().get(preferenceKey,Server.DEFAULT_LOCAL_PORT);
		String newPort = SpeechOptionPane.showInputDialog(this, resources.getString(dialogMessage), oldPort);
		if(newPort == null){
			SoundFactory.getInstance().play(SoundEvent.CANCEL);
			return;
		}

		boolean badFormat = false;
		try {
			int port = Integer.parseInt(newPort);
			if(port <= 0 || port > 65535)
				badFormat = true;
		}catch(NumberFormatException nfe){
			badFormat = true;
		}

		if(badFormat){
			SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){
				@Override
				public void playEnded() {
					NarratorFactory.getInstance().speak(resources.getString("speech.bad_format_port"));
				}
			});
			return;
		}
		PreferencesService.getInstance().put(preferenceKey,newPort);
		NarratorFactory.getInstance().speak(MessageFormat.format(
				resources.getString(feedbackMessage),newPort));
	}

	/**
      Displays the About dialog box.
	 */
	public void showAboutDialog(){
		String options[] = {resources.getString("dialog.ok_button")};
		SpeechSummaryPane.showDialog(this, 
				resources.getString("dialog.about.title"), 
				MessageFormat.format(resources.getString("dialog.about"),
						resources.getString("app.name"),
						resources.getString("app.version"),
						resources.getString("dialog.about.description"),
						resources.getString("dialog.about.license")), 
						SpeechSummaryPane.OK_OPTION,
						options
		);
	}

	/**
	 * Displays the Software license in a dialog box.
	 */
	public void showLicense() {
		BufferedReader reader = null; 
		try{
			reader  = new BufferedReader(
					new InputStreamReader(
							getClass().getResourceAsStream(
									"license.txt")));
			StringBuilder builder = new StringBuilder();
			String line;
			while ((line = reader.readLine()) != null){
				builder.append(line).append('\n');
			}
			String options[] = {resources.getString("dialog.ok_button")};
			SpeechSummaryPane.showDialog(editorTabbedPane, 
					resources.getString("dialog.license.title"), 
					builder.toString(), 
					SpeechSummaryPane.OK_OPTION,options);
		}catch (IOException exception){
			SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.license_not_found"));
		}finally{
			if(reader != null)
				try{reader.close();}catch(IOException ioe){ioe.printStackTrace();}
		}
	}

	/**
	 * Saves the diagram template in the <i>templates</i> folder.
	 * 
	 * The template can then be reused to create new diagrams with the same type of nodes and 
	 * edges.  
	 * 
	 * @param diagram the diagram to get the template from
	 * @throws IOException if something goes wrong with I/O when saving the file
	 */
	public void saveDiagramTemplate(Diagram diagram) throws IOException {
		File file = new File(
				new StringBuilder(PreferencesService.getInstance().get("home", "."))
				.append(System.getProperty("file.separator"))
				.append(resources.getString("dir.templates"))
				.append(System.getProperty("file.separator"))
				.append(diagram.getName())
				.append(resources.getString("template.extension"))
				.toString()
		);
		PersistenceManager.encodeDiagramTemplate(diagram,file);

	}

	/**
     * Adds a diagram type to the File->New menu.
     *
   	 *	@param diagram the diagram whose nodes and edges definition will be used as a template
   	 * for new diagrams creation.
     *
	 */
	public void addDiagramType(final Diagram diagram){
		/* this is to prevent the user from creating other diagram prototypes with the same name */
		existingTemplateNames.add(diagram.getName());
		existingTemplates.add(diagram);
		JMenuItem newTypeItem = SpeechMenuFactory.getMenuItem(diagram.getName());
		newTypeItem.addActionListener(new ActionListener(){
			@Override
			public void actionPerformed(ActionEvent event){
				Diagram clone = (Diagram)diagram.clone();
				/* find a good unique name for the new tab */
				Pattern pattern = Pattern.compile("new "+clone.getName()+"( \\(([0-9]+)\\))?");
				int maxOpenDiagram = -1;
				for(int i=0;i<editorTabbedPane.getTabCount();i++){
					Matcher matcher = pattern.matcher(editorTabbedPane.getComponentAt(i).getDiagram().getName());
					if(matcher.matches()){
						if(matcher.group(1) == null)
							maxOpenDiagram = 0;
						else
							maxOpenDiagram = Math.max(maxOpenDiagram, Integer.parseInt(matcher.group(2)));
					}
				}
				if(maxOpenDiagram >= 0)
					clone.setName(String.format("new %s (%d)", clone.getName(),++maxOpenDiagram));
				else clone.setName("new "+clone.getName());
				addTab(null, clone);
				iLog("new diagram created of type: "+diagram.getName());
			}
		});
		newMenu.add(newTypeItem);
	}

	/**
	 * Saves the user preferences before exiting.
	 */
	public void savePreferences(){
		String recent = "";     
		for (int i = 0; i < Math.min(recentFiles.size(), maxRecentFiles); i++){
			if (recent.length() > 0) recent += "|";
			recent += recentFiles.get(i);
		}      
		preferences.put("recent", recent);   
	}

	/**
	 * Returns the currently selected tab's diagram panel.
	 *  
	 * @return the currently selected tab's diagram panel or {@code null} 
	 * if no tab is open.
	 */
	public DiagramPanel getActiveTab(){
		return (DiagramPanel)editorTabbedPane.getSelectedComponent();
	}

	/**
	 * Set the variable holding the node or edge that would be highlighted if
	 * {@link #hHighlight()} is called. The menu item for highlight is also enabled
	 * if {@code de} is not {@code null}.
	 * 
	 * @param de the diagram element to be selected by {@code hHighlight()}
	 * or {@code null} for no selection. 
	 */
	public void selectHapticHighligh(DiagramElement de){
		hapticHighlightDiagramElement = de;
		highlightMenuItem.setEnabled(de == null ? false : true);
	}

	private DiagramPanel addTab(String path, Diagram diagram){
		DiagramPanel diagramPanel = new DiagramPanel(diagram,editorTabbedPane);
		diagramPanel.setFilePath(path);
		diagramPanel.getTree().addTreeSelectionListener(treeSelectionListener);
		diagramPanel.setAwarenessPanelListener(awarenessPanelListener);
		/* update the haptics */
		haptics.addNewDiagram(diagramPanel.getDiagram().getName());
		for(Node n : diagram.getCollectionModel().getNodes())
			haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null);
		for(Edge e : diagram.getCollectionModel().getEdges()){
			Edge.PointRepresentation pr = e.getPointRepresentation();
			haptics.addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null);
		}
		/* install the listener that handling the haptics device and the one handling the audio feedback */
		diagram.getCollectionModel().addCollectionListener(hapticTrigger);
		AudioFeedback audioFeedback = new AudioFeedback(diagramPanel.getTree());
		diagram.getCollectionModel().addCollectionListener(audioFeedback);
		diagram.getTreeModel().addDiagramTreeNodeListener(audioFeedback);

		editorTabbedPane.add(diagramPanel);
		editorTabbedPane.setToolTipTextAt(editorTabbedPane.getTabCount()-1,path);//the new panel is at tabCount -1
		editorTabbedPane.setSelectedIndex(editorTabbedPane.getTabCount()-1);
		/* give the focus to the Content Pane, else it's grabbed by the rootPane
 	      and it does not work when adding a new tab with the tree focused    */
		getContentPane().requestFocusInWindow();
		return diagramPanel;
	}

	private void diagramPanelEnabledMenuUpdate(DiagramPanel dPanel){
		fileSaveItem.setEnabled(false);
		fileSaveAsItem.setEnabled(false);
		fileSaveCopyItem.setEnabled(false);
		fileCloseItem.setEnabled(false);
		shareDiagramMenuItem.setEnabled(false);
		graphExportItem.setEnabled(false);
		showAwarenessPanelMenuItem.setEnabled(false);
		hideAwarenessPanelMenuItem.setEnabled(false);
		if(dPanel == null)
			return;

		fileSaveItem.setEnabled(true);
		fileSaveCopyItem.setEnabled(true);
		graphExportItem.setEnabled(true);
		if(dPanel.getDiagram() instanceof NetDiagram){
			if(dPanel.isAwarenessPanelVisible()) 
				hideAwarenessPanelMenuItem.setEnabled(true);
			else
				showAwarenessPanelMenuItem.setEnabled(true);
		}else{
			/* only enabled for local diagram, otherwise changing the name * 
			 * of the diagram would messes up the network protocol         */
			fileSaveAsItem.setEnabled(true);
		}

		boolean isSharedDiagram = dPanel.getDiagram() instanceof NetDiagram; 
		if(server != null && !isSharedDiagram){
			shareDiagramMenuItem.setEnabled(true);
		}
		
		if(!(isSharedDiagram && dPanel.getDiagram().getLabel().endsWith(NetDiagram.LOCALHOST_STRING)))
			fileCloseItem.setEnabled(true);
	}

	private void treeEnabledMenuUpdate(TreePath path){
		canJumpRef = false;
		insertMenuItem.setEnabled(false);
		deleteMenuItem.setEnabled(false);
		renameMenuItem.setEnabled(false);
		editNotesMenuItem.setEnabled(false);
		bookmarkMenuItem.setEnabled(false);
		jumpMenuItem.setEnabled(false);
		locateMenuItem.setEnabled(false);
		selectMenuItem.setEnabled(false);
		if(path == null)
			return;

		jumpMenuItem.setEnabled(true);
		editNotesMenuItem.setEnabled(true);
		bookmarkMenuItem.setEnabled(true);

		/* jump to reference : a reference node must be selected */
		DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent();

		/* root node */
		if((treeNode).getParent() == null)
			return;

		if(treeNode instanceof EdgeReferenceMutableTreeNode)
			canJumpRef = true;

		if(treeNode instanceof NodeReferenceMutableTreeNode){
			insertMenuItem.setEnabled(true);
			canJumpRef  = true ;
		}

		/* insert a node : the type node must be selected */
		if(treeNode instanceof TypeMutableTreeNode){
			insertMenuItem.setEnabled(true);
		}

		/* it's a property node */
		if(treeNode instanceof PropertyMutableTreeNode){
			deleteMenuItem.setEnabled(true);
			renameMenuItem.setEnabled(true);
			insertMenuItem.setEnabled(true);
		}

		if(treeNode instanceof PropertyTypeMutableTreeNode)
			insertMenuItem.setEnabled(true);
		if(treeNode instanceof DiagramElement){
			deleteMenuItem.setEnabled(true);
			renameMenuItem.setEnabled(true);
			if(HapticsFactory.getInstance().isAlive())
				locateMenuItem.setEnabled(true);
			if(treeNode instanceof Node)
				selectMenuItem.setEnabled(true);
		}
	}

	private boolean readTemplateFiles(File[] files){
		
		/* add the pd diagam type first */
		addDiagramType(new PdDiagram());
		
		boolean someFilesNotRead = false;
		for(File file : files){
			try {
				Diagram d = PersistenceManager.decodeDiagramTemplate(file);
				addDiagramType(d);
			} catch (IOException e) {
				someFilesNotRead = true;
				e.printStackTrace();
			}
		}
		return someFilesNotRead;
	}

	private void iLog(String action,String args){
		InteractionLog.log("TREE",action,args);
	}

	private void iLog(String message){
		InteractionLog.log(message);
	}

	private Server server;
	private SocketChannel localSocket;
	private ExceptionHandler netLocalDiagramExceptionHandler;
	private ClientConnectionManager clientConnectionManager;
	private Haptics haptics;
	private ResourceBundle resources;
	public EditorTabbedPane editorTabbedPane;
	private FileService.ChooserService fileService;
	private PreferencesService preferences;
	private HapticTrigger hapticTrigger;
	private DiagramElement hapticHighlightDiagramElement;
	private ArrayList<String> existingTemplateNames;
	private ArrayList<Diagram> existingTemplates;
	private AwarenessPanelEnablingListener awarenessPanelListener;

	private JMenu newMenu;
	private JMenuItem jumpMenuItem;
	private boolean canJumpRef;
	private JMenuItem fileSaveItem;
	private JMenuItem graphExportItem;
	private JMenuItem fileSaveAsItem;
	private JMenuItem fileSaveCopyItem;
	private JMenuItem fileCloseItem;
	private JMenuItem insertMenuItem;
	private JMenuItem deleteMenuItem;
	private JMenuItem renameMenuItem;
	private JMenuItem selectMenuItem;
	private JMenuItem bookmarkMenuItem;
	private JMenuItem editNotesMenuItem;
	private JMenuItem locateMenuItem;
	private JMenuItem highlightMenuItem;
	private JMenuItem shareDiagramMenuItem;
	private JMenuItem startServerMenuItem; 
	private JMenuItem stopServerMenuItem;
	private JMenuItem showAwarenessPanelMenuItem;
	private JMenuItem hideAwarenessPanelMenuItem;
	private TreeSelectionListener treeSelectionListener;
	private ChangeListener tabChangeListener; 
	private String defaultExtension;
	private String backupDirPath;
	private ArrayList<String> recentFiles;
	private JMenu recentFilesMenu;
	private int maxRecentFiles = DEFAULT_MAX_RECENT_FILES;

	private ExtensionFilter extensionFilter;
	private ExtensionFilter exportFilter;

	private static final int DEFAULT_MAX_RECENT_FILES = 5;
	private static final double GROW_SCALE_FACTOR = Math.sqrt(2);

}