Mercurial > hg > accesspd
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); }