f@0: /* f@0: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool f@0: f@0: Copyright (C) 2002 Cay S. Horstmann (http://horstmann.com) f@0: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) f@0: f@0: This program is free software: you can redistribute it and/or modify f@0: it under the terms of the GNU General Public License as published by f@0: the Free Software Foundation, either version 3 of the License, or f@0: (at your option) any later version. f@0: f@0: This program is distributed in the hope that it will be useful, f@0: but WITHOUT ANY WARRANTY; without even the implied warranty of f@0: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the f@0: GNU General Public License for more details. f@0: f@0: You should have received a copy of the GNU General Public License f@0: along with this program. If not, see . f@0: */ f@0: f@0: package uk.ac.qmul.eecs.ccmi.gui; f@0: f@0: import java.awt.Color; f@0: import java.awt.Dimension; f@0: import java.awt.Graphics2D; f@0: import java.awt.KeyboardFocusManager; f@0: import java.awt.Toolkit; f@0: import java.awt.event.ActionEvent; f@0: import java.awt.event.ActionListener; f@0: import java.awt.event.WindowAdapter; f@0: import java.awt.event.WindowEvent; f@0: import java.awt.event.WindowFocusListener; f@0: import java.awt.geom.Rectangle2D; f@0: import java.awt.image.BufferedImage; f@0: import java.io.BufferedInputStream; f@0: import java.io.BufferedOutputStream; f@0: import java.io.BufferedReader; f@0: import java.io.ByteArrayInputStream; f@0: import java.io.File; f@0: import java.io.FileOutputStream; f@0: import java.io.IOException; f@0: import java.io.InputStream; f@0: import java.io.InputStreamReader; f@0: import java.io.OutputStream; f@0: import java.net.InetSocketAddress; f@0: import java.net.URL; f@0: import java.nio.channels.SocketChannel; f@0: import java.text.MessageFormat; f@0: import java.text.SimpleDateFormat; f@0: import java.util.ArrayList; f@0: import java.util.Arrays; f@0: import java.util.Date; f@0: import java.util.ResourceBundle; f@0: import java.util.Set; f@0: import java.util.concurrent.ExecutionException; f@0: import java.util.regex.Matcher; f@0: import java.util.regex.Pattern; f@0: f@0: import javax.imageio.ImageIO; f@0: import javax.swing.ImageIcon; f@0: import javax.swing.JCheckBoxMenuItem; f@0: import javax.swing.JFrame; f@0: import javax.swing.JMenu; f@0: import javax.swing.JMenuBar; f@0: import javax.swing.JMenuItem; f@0: import javax.swing.JTree; f@0: import javax.swing.SwingUtilities; f@0: import javax.swing.UIManager; f@0: import javax.swing.UnsupportedLookAndFeelException; f@0: import javax.swing.event.ChangeEvent; f@0: import javax.swing.event.ChangeListener; f@0: import javax.swing.event.MenuEvent; f@0: import javax.swing.event.MenuListener; f@0: import javax.swing.event.TreeSelectionEvent; f@0: import javax.swing.event.TreeSelectionListener; f@0: import javax.swing.tree.TreePath; f@0: f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.ConnectNodesException; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.EdgeReferenceMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties.Modifiers; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeReferenceMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.PropertyTypeMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.diagrammodel.TypeMutableTreeNode; f@0: import uk.ac.qmul.eecs.ccmi.gui.awareness.BroadcastFilter; f@0: import uk.ac.qmul.eecs.ccmi.gui.awareness.DisplayFilter; f@0: import uk.ac.qmul.eecs.ccmi.gui.persistence.PersistenceManager; f@0: import uk.ac.qmul.eecs.ccmi.haptics.Haptics; f@0: import uk.ac.qmul.eecs.ccmi.haptics.HapticsFactory; f@0: import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage; f@0: import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager; f@0: import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.RmDiagramRequest; f@0: import uk.ac.qmul.eecs.ccmi.network.ClientConnectionManager.SendAwarenessRequest; f@0: import uk.ac.qmul.eecs.ccmi.network.Command; f@0: import uk.ac.qmul.eecs.ccmi.network.DiagramDownloader; f@0: import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; f@0: import uk.ac.qmul.eecs.ccmi.network.DiagramShareException; f@0: import uk.ac.qmul.eecs.ccmi.network.NetDiagram; f@0: import uk.ac.qmul.eecs.ccmi.network.ProtocolFactory; f@0: import uk.ac.qmul.eecs.ccmi.network.Server; f@0: import uk.ac.qmul.eecs.ccmi.pdsupport.PdDiagram; f@0: import uk.ac.qmul.eecs.ccmi.pdsupport.PdPersistenceManager; f@0: import uk.ac.qmul.eecs.ccmi.sound.PlayerListener; f@0: import uk.ac.qmul.eecs.ccmi.sound.SoundEvent; f@0: import uk.ac.qmul.eecs.ccmi.sound.SoundFactory; f@0: import uk.ac.qmul.eecs.ccmi.speech.Narrator; f@0: import uk.ac.qmul.eecs.ccmi.speech.NarratorFactory; f@0: import uk.ac.qmul.eecs.ccmi.utils.ExceptionHandler; f@0: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; f@0: import uk.ac.qmul.eecs.ccmi.utils.PreferencesService; f@0: f@0: /** f@0: * The main frame of the editor which contains diagram panes that show graphs and f@0: * tree representations of diagrams. f@0: */ f@0: @SuppressWarnings("serial") f@0: public class EditorFrame extends JFrame { f@0: /** f@0: * Creates a new {@code EditorFrame} f@0: * f@0: * @param haptics an instance of {@code Haptics} handling the haptic device during this f@0: * @param templateFiles an array of template files. New diagrams can be created from template files by clonation f@0: * @param backupDirPath the path of a folder where all the currently open diagrams will be saved if f@0: * the haptic device crashes f@0: * @param templateEditors the template editors for this instance of the program f@0: * @param additionalTemplate additional diagram templates. An entry will be created in {@code File->New Diagram} f@0: * for each template f@0: */ f@0: public EditorFrame(Haptics haptics, File[] templateFiles, String backupDirPath, TemplateEditor[] templateEditors, Diagram[] additionalTemplate){ f@0: this.backupDirPath = backupDirPath; f@0: /* load resources */ f@0: resources = ResourceBundle.getBundle(this.getClass().getName()); f@0: f@0: /* haptics */ f@0: this.haptics = haptics; f@0: hapticTrigger = new HapticTrigger(); f@0: f@0: /* read editor related preferences */ f@0: preferences = PreferencesService.getInstance(); f@0: f@0: URL url = getClass().getResource("ccmi_favicon.gif"); f@0: setIconImage(new ImageIcon(url).getImage()); f@0: changeLookAndFeel(preferences.get("laf", null)); f@0: f@0: recentFiles = new ArrayList(); f@0: File lastDir = new File("."); f@0: String recent = preferences.get("recent", "").trim(); f@0: if (recent.length() > 0){ f@0: recentFiles.addAll(Arrays.asList(recent.split("[|]"))); f@0: lastDir = new File(recentFiles.get(0)).getParentFile(); f@0: } f@0: fileService = new FileService.ChooserService(lastDir); f@0: f@0: /* set up extensions */ f@0: defaultExtension = resources.getString("files.extension"); f@0: extensionFilter = new ExtensionFilter( f@0: resources.getString("files.name"), f@0: new String[] { defaultExtension }); f@0: exportFilter = new ExtensionFilter( f@0: resources.getString("files.image.name"), f@0: resources.getString("files.image.extension")); f@0: f@0: /* start building the GUI */ f@0: editorTabbedPane = new EditorTabbedPane(this); f@0: setContentPane(editorTabbedPane); f@0: f@0: f@0: setTitle(resources.getString("app.name")); f@0: Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); f@0: f@0: int screenWidth = (int)screenSize.getWidth(); f@0: int screenHeight = (int)screenSize.getHeight(); f@0: f@0: setLocation(screenWidth / 16, screenHeight / 16); f@0: editorTabbedPane.setPreferredSize(new Dimension( f@0: screenWidth * 5 / 8, screenHeight * 5 / 8)); f@0: f@0: /* install the player listener (a narrator speech) for CANCEL, EMPTY and MESSAGE ok event. They are * f@0: * not depending on what the user is doing and the speech is therefore always the same. We only need * f@0: * to set the listener once and it will do the job each time the sound is triggered */ f@0: SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){ f@0: @Override f@0: public void playEnded() { f@0: DiagramPanel dPanel = getActiveTab(); f@0: if(dPanel != null){// we can cancel a dialog even when no diagram is open (e.g. for tcp connections) f@0: speakFocusedComponent(""); f@0: }else{ f@0: NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.cancelled"),"")); f@0: } f@0: } f@0: }, SoundEvent.CANCEL); f@0: SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){ f@0: @Override f@0: public void playEnded() { f@0: DiagramPanel dPanel = getActiveTab(); f@0: DiagramTree tree = dPanel.getTree(); f@0: NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.empty_property"),tree.currentPathSpeech())); f@0: } f@0: }, SoundEvent.EMPTY); f@0: SoundFactory.getInstance().setDefaultPlayerListener(new PlayerListener(){ f@0: @Override f@0: public void playEnded() { f@0: DiagramPanel dPanel = getActiveTab(); f@0: if(dPanel != null){ f@0: DiagramTree tree = dPanel.getTree(); f@0: NarratorFactory.getInstance().speak(tree.currentPathSpeech()); f@0: } f@0: } f@0: }, SoundEvent.MESSAGE_OK); f@0: f@0: /* setup listeners */ f@0: initListeners(); f@0: /* set up menus */ f@0: existingTemplateNames = new ArrayList(10); f@0: existingTemplates = new ArrayList(10); f@0: int extensionLength = resources.getString("template.extension").length(); f@0: for(File file : templateFiles){ f@0: existingTemplateNames.add(file.getName().substring(0, file.getName().length()-extensionLength)); f@0: } f@0: initMenu(templateEditors); f@0: /* read template files. this call must be placed after menu creation as it adds menu items to file->new-> */ f@0: boolean someTemplateFilesNotRead = readTemplateFiles(templateFiles); f@0: f@0: for(Diagram additionalDiagram : additionalTemplate){ f@0: addDiagramType(additionalDiagram); f@0: } f@0: f@0: /* become visible */ f@0: pack(); f@0: setVisible(true); f@0: /* if some templates were not read successfully, warn the user with a message */ f@0: if(someTemplateFilesNotRead){ f@0: SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.filesnotread"), SpeechOptionPane.WARNING_MESSAGE); f@0: } f@0: } f@0: f@0: private void initListeners(){ f@0: /* window closing */ f@0: setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); f@0: addWindowListener(new WindowAdapter(){ f@0: @Override f@0: public void windowClosing(WindowEvent event){ f@0: exit(); f@0: } f@0: @Override f@0: public void windowOpened(WindowEvent e) { f@0: // bring the window to front, else the openGL window would have higher priority f@0: e.getWindow().toFront(); f@0: } f@0: }); f@0: f@0: addWindowFocusListener(new WindowFocusListener(){ f@0: @Override f@0: public void windowGainedFocus(WindowEvent evt) { f@0: if(evt.getOppositeWindow() == null) f@0: NarratorFactory.getInstance().speak(resources.getString("window.focus")); f@0: } f@0: f@0: @Override f@0: public void windowLostFocus(WindowEvent evt) { f@0: if(evt.getOppositeWindow() == null) f@0: NarratorFactory.getInstance().speak(resources.getString("window.unfocus")); f@0: } f@0: }); f@0: f@0: /* set up listeners reacting to change of selection in the tree and tab */ f@0: tabChangeListener = new ChangeListener(){ f@0: @Override f@0: public void stateChanged(ChangeEvent evt) { f@0: DiagramPanel diagramPanel = getActiveTab(); f@0: if (diagramPanel != null){ f@0: /* give the focus to the Content Pane, otherwise it's grabbed by the rootPane */ f@0: getContentPane().requestFocusInWindow(); f@0: TreePath path = diagramPanel.getTree().getSelectionPath(); f@0: treeEnabledMenuUpdate(path); f@0: NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex()))); f@0: f@0: // updated the haptics f@0: HapticsFactory.getInstance().switchDiagram(diagramPanel.getDiagram().getName()); f@0: iLog("diagram tab changed to "+editorTabbedPane.getTitleAt(editorTabbedPane.getSelectedIndex())); f@0: }else{ f@0: treeEnabledMenuUpdate(null); f@0: } f@0: /* if we change tab, the haptic highlight must be set again */ f@0: selectHapticHighligh(null); f@0: /* so do the menu depending on the panel */ f@0: diagramPanelEnabledMenuUpdate(diagramPanel); f@0: } f@0: }; f@0: editorTabbedPane.addChangeListener(tabChangeListener); f@0: f@0: treeSelectionListener = new TreeSelectionListener(){ f@0: @Override f@0: public void valueChanged(TreeSelectionEvent evt) { f@0: treeEnabledMenuUpdate(evt.getPath()); f@0: } f@0: }; f@0: f@0: netLocalDiagramExceptionHandler = new ExceptionHandler(){ f@0: @Override f@0: public void handleException(Exception e) { f@0: SwingUtilities.invokeLater(new Runnable(){ f@0: @Override f@0: public void run() { f@0: SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.local_server")); f@0: } f@0: }); f@0: } f@0: }; f@0: } f@0: f@0: private void initMenu(TemplateEditor[] templateEditors){ f@0: ResourceFactory factory = new ResourceFactory(resources); f@0: f@0: JMenuBar menuBar = SpeechMenuFactory.getMenuBar(); f@0: setJMenuBar(menuBar); f@0: f@0: /* --- FILE MENU --- */ f@0: JMenu fileMenu = factory.createMenu("file"); f@0: menuBar.add(fileMenu); f@0: f@0: /* menu items and listener added by addDiagramType function */ f@0: newMenu = factory.createMenu("file.new"); f@0: fileMenu.add(newMenu); f@0: f@0: JMenuItem fileOpenItem = factory.createMenuItem( f@0: "file.open", this, "openFile"); f@0: fileMenu.add(fileOpenItem); f@0: f@0: recentFilesMenu = factory.createMenu("file.recent"); f@0: buildRecentFilesMenu(); f@0: fileMenu.add(recentFilesMenu); f@0: f@0: fileSaveItem = factory.createMenuItem("file.save", this, "saveFile"); f@0: fileMenu.add(fileSaveItem); f@0: f@0: fileSaveAsItem = factory.createMenuItem("file.save_as", this, "saveFileAs"); f@0: fileMenu.add(fileSaveAsItem); f@0: f@0: fileSaveCopyItem = factory.createMenuItem("file.save_copy", this, "saveCopy"); f@0: fileMenu.add(fileSaveCopyItem); f@0: f@0: fileCloseItem = factory.createMenuItem("file.close",this,"closeFile"); f@0: fileMenu.add(fileCloseItem); f@0: f@0: graphExportItem = factory.createMenuItem("file.export_image", this, "exportImage"); f@0: fileMenu.add(graphExportItem); f@0: f@0: JMenuItem fileExitItem = factory.createMenuItem( f@0: "file.exit", this, "exit"); f@0: fileMenu.add(fileExitItem); f@0: f@0: /* --- EDIT MENU --- */ f@0: JMenu editMenu = factory.createMenu("edit"); f@0: menuBar.add(editMenu); f@0: f@0: locateMenuItem = factory.createMenuItem("edit.locate", this,"locate"); f@0: editMenu.add(locateMenuItem); f@0: f@0: highlightMenuItem = factory.createMenuItem("edit.highlight", this, "hHighlight"); f@0: highlightMenuItem.setEnabled(false); f@0: editMenu.add(highlightMenuItem); f@0: f@0: jumpMenuItem = factory.createMenuItem("edit.jump",this,"jump"); f@0: editMenu.add(jumpMenuItem); f@0: f@0: insertMenuItem = factory.createMenuItem("edit.insert", this, "insert"); f@0: editMenu.add(insertMenuItem); f@0: f@0: deleteMenuItem = factory.createMenuItem("edit.delete",this,"delete"); f@0: editMenu.add(deleteMenuItem); f@0: f@0: renameMenuItem = factory.createMenuItem("edit.rename",this,"rename"); f@0: editMenu.add(renameMenuItem); f@0: f@0: selectMenuItem = factory.createMenuItem("edit.select", this, "selectNode"); f@0: editMenu.add(selectMenuItem); f@0: f@0: bookmarkMenuItem = factory.createMenuItem("edit.bookmark",this,"editBookmarks"); f@0: editMenu.add(bookmarkMenuItem); f@0: f@0: editNotesMenuItem = factory.createMenuItem("edit.edit_note",this,"editNotes"); f@0: editMenu.add(editNotesMenuItem); f@0: f@0: /* --- VIEW MENU --- */ f@0: JMenu viewMenu = factory.createMenu("view"); f@0: menuBar.add(viewMenu); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.zoom_out", new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: dPanel.getGraphPanel().changeZoom(-1); f@0: } f@0: })); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.zoom_in", new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: dPanel.getGraphPanel().changeZoom(1); f@0: } f@0: })); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.grow_drawing_area", new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: GraphPanel gPanel = dPanel.getGraphPanel(); f@0: Rectangle2D bounds = gPanel.getGraphBounds(); f@0: bounds.add(gPanel.getBounds()); f@0: gPanel.setMinBounds(new Rectangle2D.Double(0, 0, f@0: GROW_SCALE_FACTOR * bounds.getWidth(), f@0: GROW_SCALE_FACTOR * bounds.getHeight())); f@0: gPanel.revalidate(); f@0: gPanel.repaint(); f@0: } f@0: })); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.clip_drawing_area", new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: GraphPanel gPanel = dPanel.getGraphPanel(); f@0: gPanel.setMinBounds(null); f@0: gPanel.revalidate(); f@0: gPanel.repaint(); f@0: } f@0: })); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.smaller_grid", new f@0: ActionListener() f@0: { f@0: public void actionPerformed(ActionEvent event) f@0: { f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: dPanel.getGraphPanel().changeGridSize(-1); f@0: } f@0: })); f@0: f@0: viewMenu.add(factory.createMenuItem( f@0: "view.larger_grid", new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: dPanel.getGraphPanel().changeGridSize(1); f@0: } f@0: })); f@0: f@0: final JCheckBoxMenuItem hideGridItem; f@0: viewMenu.add(hideGridItem = (JCheckBoxMenuItem) factory.createCheckBoxMenuItem( f@0: "view.hide_grid", new f@0: ActionListener() f@0: { f@0: public void actionPerformed(ActionEvent event) f@0: { f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) event.getSource(); f@0: dPanel.getGraphPanel().setHideGrid(menuItem.isSelected()); f@0: } f@0: })); f@0: f@0: viewMenu.addMenuListener(new f@0: MenuListener(){ f@0: /* changes the checkbox according to the diagram selected */ f@0: public void menuSelected(MenuEvent event){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if (dPanel == null) f@0: return; f@0: hideGridItem.setSelected(dPanel.getGraphPanel().getHideGrid()); f@0: } f@0: public void menuDeselected(MenuEvent event){} f@0: public void menuCanceled(MenuEvent event){} f@0: }); f@0: f@0: JMenu lafMenu = factory.createMenu("view.change_laf"); f@0: viewMenu.add(lafMenu); f@0: f@0: UIManager.LookAndFeelInfo[] infos = f@0: UIManager.getInstalledLookAndFeels(); f@0: for (int i = 0; i < infos.length; i++){ f@0: final UIManager.LookAndFeelInfo info = infos[i]; f@0: JMenuItem item = SpeechMenuFactory.getMenuItem(info.getName()); f@0: lafMenu.add(item); f@0: item.addActionListener(new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: String laf = info.getClassName(); f@0: changeLookAndFeel(laf); f@0: preferences.put("laf", laf); f@0: } f@0: }); f@0: } f@0: f@0: /* --- TEMPLATE --- */ f@0: if(templateEditors.length > 0){ f@0: JMenu templateMenu = factory.createMenu("template"); f@0: menuBar.add(templateMenu); f@0: f@0: for(final TemplateEditor templateEditor : templateEditors){ f@0: JMenuItem newDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForNew()); f@0: newDiagramItem.addActionListener(new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: Diagram diagram = templateEditor.createNew(EditorFrame.this, existingTemplateNames); f@0: if(diagram == null) f@0: return; f@0: try{ f@0: saveDiagramTemplate(diagram); f@0: }catch(IOException ioe){ f@0: SpeechOptionPane.showMessageDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.error.save_template")); f@0: return; f@0: } f@0: addDiagramType(diagram); f@0: SpeechOptionPane.showMessageDialog( f@0: EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()), f@0: SpeechOptionPane.INFORMATION_MESSAGE); f@0: SoundFactory.getInstance().play(SoundEvent.OK,null); f@0: } f@0: }); f@0: templateMenu.add(newDiagramItem); f@0: f@0: JMenuItem editDiagramItem = SpeechMenuFactory.getMenuItem(templateEditor.getLabelForEdit()); f@0: editDiagramItem.addActionListener(new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt){ f@0: if(existingTemplates.isEmpty()){ f@0: NarratorFactory.getInstance().speak(resources.getString("dialog.error.no_template_to_edit")); f@0: return; f@0: } f@0: Diagram[] diagrams = new Diagram[existingTemplates.size()]; f@0: diagrams = existingTemplates.toArray(diagrams); f@0: Diagram selectedDiagram = (Diagram)SpeechOptionPane.showSelectionDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.input.edit_diagram_template"), f@0: diagrams, f@0: diagrams[0]); f@0: f@0: if(selectedDiagram == null){ f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: return; f@0: } f@0: f@0: Diagram diagram = templateEditor.edit(EditorFrame.this, existingTemplateNames,selectedDiagram); f@0: if(diagram == null) f@0: return; f@0: try{ f@0: saveDiagramTemplate(diagram); f@0: }catch(IOException ioe){ f@0: SpeechOptionPane.showMessageDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.error.save_template")); f@0: return; f@0: } f@0: addDiagramType(diagram); f@0: SpeechOptionPane.showMessageDialog( f@0: EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.template_created"), diagram.getName()), f@0: SpeechOptionPane.INFORMATION_MESSAGE); f@0: SoundFactory.getInstance().play(SoundEvent.OK,null); f@0: } f@0: } f@0: ); f@0: templateMenu.add(editDiagramItem); f@0: } f@0: } f@0: f@0: /* --- COLLABORATION ---- */ f@0: JMenu collabMenu = factory.createMenu("collab"); f@0: menuBar.add(collabMenu); f@0: f@0: startServerMenuItem = factory.createMenuItem("collab.start_server", this, "startServer"); f@0: collabMenu.add(startServerMenuItem); f@0: f@0: stopServerMenuItem = factory.createMenuItem("collab.stop_server", this, "stopServer"); f@0: collabMenu.add(stopServerMenuItem); f@0: stopServerMenuItem.setEnabled(false); f@0: f@0: shareDiagramMenuItem = factory.createMenuItem("collab.share_diagram", this, "shareDiagram"); f@0: collabMenu.add(shareDiagramMenuItem); f@0: f@0: collabMenu.add(factory.createMenuItem("collab.open_shared_diagram", this, "openSharedDiagram")); f@0: f@0: showAwarenessPanelMenuItem = factory.createMenuItem("collab.show_awareness_panel", this, "showAwarenessPanel"); f@0: collabMenu.add(showAwarenessPanelMenuItem); f@0: f@0: hideAwarenessPanelMenuItem = factory.createMenuItem("collab.hide_awareness_panel", this, "hideAwarenessPanel"); f@0: collabMenu.add(hideAwarenessPanelMenuItem); f@0: f@0: awarenessPanelListener = new AwarenessPanelEnablingListener(){ f@0: @Override f@0: public void awarenessPanelEnabled(boolean enabled) { f@0: if(enabled){ f@0: showAwarenessPanelMenuItem.setEnabled(true); f@0: hideAwarenessPanelMenuItem.setEnabled(false); f@0: }else{ f@0: showAwarenessPanelMenuItem.setEnabled(false); f@0: hideAwarenessPanelMenuItem.setEnabled(false); f@0: } f@0: } f@0: f@0: @Override f@0: public void awarenessPanelVisible(boolean visible) { f@0: if(visible){ f@0: showAwarenessPanelMenuItem.setEnabled(false); f@0: hideAwarenessPanelMenuItem.setEnabled(true); f@0: }else{ f@0: showAwarenessPanelMenuItem.setEnabled(true); f@0: hideAwarenessPanelMenuItem.setEnabled(false); f@0: } f@0: } f@0: }; f@0: f@0: /* --- PREFERENCES --- */ f@0: JMenu preferencesMenu = factory.createMenu("preferences"); f@0: menuBar.add(preferencesMenu); f@0: f@0: /* show haptic window menu item only unless it's a actual haptic device thread * f@0: * that has its own window run by a native dll */ f@0: if(!HapticsFactory.getInstance().isAlive()){ f@0: preferencesMenu.add(factory.createCheckBoxMenuItem("preferences.show_haptics", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt){ f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); f@0: haptics.setVisible(menuItem.isSelected()); f@0: NarratorFactory.getInstance().speakWholeText(resources.getString( f@0: menuItem.isSelected() ? "speech.haptic_window_open" : "speech.haptic_window_close")); f@0: } f@0: })); f@0: } f@0: f@0: preferencesMenu.add(factory.createMenuItem("preferences.change_haptics", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt){ f@0: String [] hapticDevices = { f@0: HapticsFactory.PHANTOM_ID, f@0: HapticsFactory.FALCON_ID, f@0: HapticsFactory.TABLET_ID}; f@0: String selection = (String)SpeechOptionPane.showSelectionDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.input.haptics.select"), f@0: hapticDevices, f@0: hapticDevices[0]); f@0: if(selection == null) f@0: return; f@0: preferences.put("haptic_device", selection); f@0: SpeechOptionPane.showMessageDialog(EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.feedback.haptic_init"),selection), f@0: SpeechOptionPane.WARNING_MESSAGE); f@0: } f@0: })); f@0: f@0: // awareness menu f@0: JMenu awarenessMenu = factory.createMenu("preferences.awareness"); f@0: preferencesMenu.add(awarenessMenu); f@0: f@0: awarenessMenu.add(factory.createMenuItem("preferences.awareness.username", this, "showAwarnessUsernameDialog")); f@0: awarenessMenu.add(factory.createMenuItem("preferences.awareness.broadcast", this, "showAwarenessBroadcastDialog")); f@0: awarenessMenu.add(factory.createMenuItem("preferences.awareness.display", this, "showAwarenessDisplayDialog")); f@0: f@0: JMenuItem enableAwarenessVoiceMenuItem = factory.createCheckBoxMenuItem("preferences.awareness.enable_voice", f@0: new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); f@0: NarratorFactory.getInstance().setMuted(!menuItem.isSelected(),Narrator.SECOND_VOICE); f@0: PreferencesService.getInstance().put("second_voice_enabled", Boolean.toString(menuItem.isSelected())); f@0: } f@0: }); f@0: NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE); f@0: enableAwarenessVoiceMenuItem.setSelected(Boolean.parseBoolean( f@0: PreferencesService.getInstance().get("second_voice_enabled", Boolean.toString(true)))); f@0: awarenessMenu.add(enableAwarenessVoiceMenuItem); f@0: NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE); f@0: f@0: // sound f@0: JMenu soundMenu = factory.createMenu("preferences.sound"); f@0: preferencesMenu.add(soundMenu); f@0: f@0: JMenuItem muteMenuItem = factory.createCheckBoxMenuItem("preferences.sound.mute", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); f@0: NarratorFactory.getInstance().setMuted(menuItem.isSelected(),Narrator.FIRST_VOICE); f@0: SoundFactory.getInstance().setMuted(menuItem.isSelected()); f@0: } f@0: }); f@0: soundMenu.add(muteMenuItem); f@0: f@0: JMenuItem rateMenuItem = factory.createMenuItem("preferences.sound.rate", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: Integer newRate = SpeechOptionPane.showNarratorRateDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.input.sound_rate"), f@0: NarratorFactory.getInstance().getRate(), f@0: Narrator.MIN_RATE, f@0: Narrator.MAX_RATE); f@0: if(newRate != null){ f@0: NarratorFactory.getInstance().setRate(newRate); f@0: NarratorFactory.getInstance().speak( f@0: MessageFormat.format( f@0: resources.getString("dialog.feedback.speech_rate"), f@0: newRate)); f@0: }else{ f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: } f@0: } f@0: }); f@0: soundMenu.add(rateMenuItem); f@0: f@0: //server ports f@0: JMenu networkingMenu = factory.createMenu("preferences.server"); f@0: preferencesMenu.add(networkingMenu); f@0: f@0: JMenuItem localPortMenuItem = factory.createMenuItem("preferences.local_server.port", this, "showLocalServerPortDialog"); f@0: networkingMenu.add(localPortMenuItem); f@0: f@0: JMenuItem remotePortMenuItem = factory.createMenuItem("preferences.remote_server.port", this, "showRemoteServerPortDialog"); f@0: networkingMenu.add(remotePortMenuItem); f@0: f@0: // accessible file chooser f@0: JMenuItem fileChooserMenuItem = factory.createCheckBoxMenuItem("preferences.file_chooser", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); f@0: PreferencesService.getInstance().put("use_accessible_filechooser", Boolean.toString(menuItem.isSelected())); f@0: } f@0: }); f@0: f@0: JMenuItem enableLogMenuItem = factory.createCheckBoxMenuItem("preferences.enable_log", new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent evt) { f@0: JCheckBoxMenuItem menuItem = (JCheckBoxMenuItem) evt.getSource(); f@0: PreferencesService preferences = PreferencesService.getInstance(); f@0: preferences.put("enable_log", Boolean.toString(menuItem.isSelected())); f@0: if(menuItem.isSelected()){ f@0: try{ f@0: InteractionLog.enable(preferences.get("dir.log", "")); f@0: }catch(IOException ioe){ f@0: SpeechOptionPane.showMessageDialog(EditorFrame.this, resources.getString("dialog.error.log_enable")); f@0: } f@0: }else{ f@0: InteractionLog.disable(); f@0: } f@0: } f@0: }); f@0: f@0: /* temporarily mute the narrator to select the menus without triggering a speech */ f@0: NarratorFactory.getInstance().setMuted(true,Narrator.FIRST_VOICE); f@0: fileChooserMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("use_accessible_filechooser", "true"))); f@0: enableLogMenuItem.setSelected(Boolean.parseBoolean(PreferencesService.getInstance().get("enable_log", "false"))); f@0: NarratorFactory.getInstance().setMuted(false,Narrator.FIRST_VOICE); f@0: preferencesMenu.add(fileChooserMenuItem); f@0: preferencesMenu.add(enableLogMenuItem); f@0: f@0: f@0: /* --- HELP --- */ f@0: JMenu helpMenu = factory.createMenu("help"); f@0: menuBar.add(helpMenu); f@0: f@0: helpMenu.add(factory.createMenuItem( f@0: "help.about", this, "showAboutDialog")); f@0: f@0: helpMenu.add(factory.createMenuItem( f@0: "help.license", this, "showLicense")); f@0: f@0: treeEnabledMenuUpdate(null); f@0: diagramPanelEnabledMenuUpdate(null); f@0: } f@0: f@0: private void changeLookAndFeel(String lafName){ f@0: if(lafName == null) f@0: lafName = UIManager.getSystemLookAndFeelClassName(); f@0: try{ f@0: UIManager.setLookAndFeel(lafName); f@0: SwingUtilities.updateComponentTreeUI(EditorFrame.this); f@0: } f@0: catch (ClassNotFoundException ex) {} f@0: catch (InstantiationException ex) {} f@0: catch (IllegalAccessException ex) {} f@0: catch (UnsupportedLookAndFeelException ex) {} f@0: } f@0: f@0: /** f@0: * Adds a file name to the "recent files" list and rebuilds the "recent files" menu. f@0: * @param newFile the file name to add f@0: */ f@0: private void addRecentFile(final String newFile){ f@0: recentFiles.remove(newFile); f@0: if (newFile == null || newFile.equals("")) return; f@0: recentFiles.add(0, newFile); f@0: buildRecentFilesMenu(); f@0: } f@0: f@0: /* speaks out the selected tree node if the tree is focused or the tab label if the tab is focused */ f@0: private void speakFocusedComponent(String message){ f@0: message = (message == null) ? "" : message+"; ";//add a dot to pause the TTS f@0: DiagramPanel dPanel = getActiveTab(); f@0: if(dPanel != null){ f@0: String focusedComponent = null; f@0: if(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTree) f@0: focusedComponent = dPanel.getTree().currentPathSpeech(); f@0: else f@0: focusedComponent = MessageFormat.format(resources.getString("window.tab"),editorTabbedPane.getComponentTabTitle(dPanel)); f@0: NarratorFactory.getInstance().speak(message+focusedComponent); f@0: } f@0: } f@0: /** f@0: * Rebuilds the "recent files" menu. f@0: */ f@0: private void buildRecentFilesMenu(){ f@0: recentFilesMenu.removeAll(); f@0: for (int i = 0; i < recentFiles.size(); i++){ f@0: final String file = recentFiles.get(i); f@0: String name = new File(file).getName(); f@0: JMenuItem item = SpeechMenuFactory.getMenuItem(name); f@0: item.setToolTipText(file); f@0: recentFilesMenu.add(item); f@0: item.addActionListener(new f@0: ActionListener(){ f@0: public void actionPerformed(ActionEvent event){ f@0: try { f@0: FileService.Open open = new FileService.DirectService().open(new File(((JMenuItem)event.getSource()).getToolTipText())); f@0: InputStream in = open.getInputStream(); f@0: String path = open.getPath(); f@0: for(int i=0; i user clicked on cancel f@0: String path = open.getPath(); f@0: int index = editorTabbedPane.getPathTabIndex(path); f@0: if(index != -1){ //diagram is already open f@0: editorTabbedPane.setSelectedIndex(index); f@0: speakFocusedComponent(""); f@0: return; f@0: } f@0: /* every opened diagram must have a unique name */ f@0: if(editorTabbedPane.getDiagramNameTabIndex(open.getName()) != -1) f@0: throw new IOException(resources.getString("dialog.error.same_file_name")); f@0: iLog("START READ LOCAL DIAGRAM "+open.getName()); f@0: Diagram diagram = path.endsWith(PdPersistenceManager.PD_EXTENSION) ? f@0: PdPersistenceManager.getInstance().decodeDiagramInstance(in) : f@0: PersistenceManager.decodeDiagramInstance(in); f@0: iLog("END READ LOCAL DIAGRAM "+open.getName()); f@0: /* force the name of the diagram to be the same as the file name * f@0: * it should never be useful, unless the .ccmi file is edited manually */ f@0: diagram.setName(open.getName()); f@0: addTab(open.getPath(), diagram); f@0: addRecentFile(open.getPath()); f@0: } f@0: } f@0: catch (IOException exception) { f@0: SpeechOptionPane.showMessageDialog( f@0: editorTabbedPane, f@0: exception.getLocalizedMessage()); f@0: }finally{ f@0: if(in != null) f@0: try{in.close();}catch(IOException ioe){ioe.printStackTrace();} f@0: } f@0: } f@0: f@0: /** f@0: * Close a diagram tab. If the diagram has not been saved the user will be f@0: * asked if they want to save the diagram. f@0: */ f@0: public void closeFile(){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: if(dPanel.isModified()||dPanel.getFilePath() == null){ f@0: int answer = SpeechOptionPane.showConfirmDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.confirm.close"), f@0: SpeechOptionPane.YES_NO_OPTION); f@0: f@0: if(answer == SpeechOptionPane.YES_OPTION) // save file only if the user decides to f@0: if(!saveFile()) f@0: return; /* if the user closes the save dialog do nothing */ f@0: } f@0: iLog("diagram closed :"+dPanel.getDiagram().getName()); f@0: NarratorFactory.getInstance().speak(MessageFormat.format(resources.getString("speech.diagram_closed"),dPanel.getDiagram().getName())); f@0: if(dPanel.getDiagram() instanceof NetDiagram){ f@0: if(clientConnectionManager != null && clientConnectionManager.isAlive()) f@0: clientConnectionManager.addRequest(new RmDiagramRequest(dPanel.getDiagram().getName())); f@0: } f@0: editorTabbedPane.remove(dPanel); f@0: //getActiveTab, after removing, returns the new selected panel after the remotion, if any f@0: String newFocusedTabName = null; f@0: if(getActiveTab() != null){ f@0: newFocusedTabName = getActiveTab().getDiagram().getName(); f@0: } f@0: haptics.removeDiagram(dPanel.getDiagram().getName(), newFocusedTabName); f@0: } f@0: f@0: /** f@0: * Saves the currently open tab diagram into a file. If the diagram has no file path associated f@0: * (it has never been saved before), {@link #saveFileAs()} is called. f@0: * f@0: * @return {@code true} if the file is successfully saved, or {@code false} otherwise. f@0: */ f@0: public boolean saveFile(){ f@0: DiagramPanel diagramPanel = getActiveTab(); f@0: if (diagramPanel == null) // no tabs open f@0: return false; f@0: String filePath = diagramPanel.getFilePath(); f@0: if (filePath == null) { f@0: return saveFileAs(); f@0: } f@0: f@0: OutputStream out = null; f@0: try{ f@0: File file = new File(filePath); f@0: out = new BufferedOutputStream(new FileOutputStream(file)); f@0: Diagram d = diagramPanel.getDiagram(); f@0: if(d instanceof PdDiagram){ f@0: PdPersistenceManager.getInstance().encodeDiagramInstance(d, out); f@0: }else { f@0: PersistenceManager.encodeDiagramInstance(d, out); f@0: } f@0: /* we saved the diagram, therefore there are no more pending changes */ f@0: diagramPanel.setModified(false); f@0: speakFocusedComponent(resources.getString("dialog.file_saved")); f@0: return true; f@0: }catch(IOException ioe){ f@0: SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage()); f@0: return false; f@0: }finally{ f@0: try { f@0: out.close(); f@0: }catch(IOException ioe){ /*can't do anything */ } f@0: } f@0: } f@0: f@0: /** f@0: * Prompts the user with a dialog for saving a diagram on disk. The current diagram is not affected f@0: * by this call and it keeps to be displayed in the editor. f@0: * f@0: * @return {@code true} if the file is successfully saved, or {@code false} otherwise. f@0: */ f@0: public boolean saveCopy(){ f@0: DiagramPanel diagramPanel = getActiveTab(); f@0: if (diagramPanel == null) // no tabs open f@0: return false; f@0: OutputStream out = null; f@0: FileService.Save save; f@0: try { f@0: save = fileService.save( f@0: PreferencesService.getInstance().get("dir.diagrams", "."), f@0: diagramPanel.getFilePath(), //default file to save to f@0: extensionFilter, f@0: null, f@0: defaultExtension, f@0: null); f@0: out = save.getOutputStream(); f@0: if (out == null) /* user didn't select any file for saving */ f@0: return false; f@0: if(diagramPanel.getDiagram() instanceof PdDiagram){ f@0: PdPersistenceManager.getInstance().encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out); f@0: }else{ f@0: PersistenceManager.encodeDiagramInstance(diagramPanel.getDiagram(),save.getName(), out); f@0: } f@0: speakFocusedComponent(resources.getString("dialog.file_saved")); f@0: return true; f@0: } catch (IOException ioe) { f@0: SpeechOptionPane.showMessageDialog(editorTabbedPane,ioe.getLocalizedMessage()); f@0: return false; f@0: } finally { f@0: if(out != null) f@0: try{out.close();}catch(IOException ioe){ioe.printStackTrace();} f@0: } f@0: } f@0: f@0: /** f@0: * Saves the current diagram as a new file. The user is prompter with a {@code FileChooser} f@0: * to chose a file to save the diagram to. f@0: * f@0: * @return {@code true} if the file is successfully saved, or {@code false} otherwise. f@0: */ f@0: public boolean saveFileAs() { f@0: DiagramPanel diagramPanel = getActiveTab(); f@0: if (diagramPanel == null) // no tabs open f@0: return false; f@0: String oldName = diagramPanel.getDiagram().getName(); f@0: OutputStream out = null; f@0: try { f@0: String[] currentTabs = new String[editorTabbedPane.getTabCount()]; f@0: for(int i=0; i 0){ f@0: int answer = SpeechOptionPane.showConfirmDialog( f@0: EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.confirm.exit"), diagramsToSave), f@0: SpeechOptionPane.YES_NO_OPTION); f@0: // if the doesn't want to save changes, veto the close f@0: if(answer != SpeechOptionPane.NO_OPTION){ f@0: if(answer == SpeechOptionPane.YES_OPTION){ // user clicked on yes button we just get them back to the editor f@0: speakFocusedComponent(""); f@0: }else{// user pressed the ESC button f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: } f@0: return; f@0: } f@0: } f@0: f@0: NarratorFactory.getInstance().dispose(); f@0: SoundFactory.getInstance().dispose(); f@0: haptics.dispose(); f@0: if(server != null) f@0: server.shutdown(); f@0: if(clientConnectionManager != null) f@0: clientConnectionManager.shutdown(); f@0: BroadcastFilter broadcastFilter = BroadcastFilter.getInstance(); f@0: if(broadcastFilter != null) f@0: broadcastFilter.saveProperties(this); f@0: DisplayFilter displayFilter = DisplayFilter.getInstance(); f@0: if(displayFilter != null) f@0: displayFilter.saveProperties(this); f@0: f@0: while(haptics.isAlive()){/* wait */} f@0: /* closes the logger's handlers */ f@0: iLog("PROGRAM EXIT"); f@0: InteractionLog.dispose(); f@0: f@0: savePreferences(); f@0: System.exit(0); f@0: } f@0: f@0: /** f@0: * Changes the selection path of the diagram tree to a specific destination. f@0: * The user with a selection dialog to choose the destination from the following f@0: * choices : the root of the diagram, one of the element types, a bookmarked f@0: * node or a node/edge reference. f@0: */ f@0: public void jump(){ f@0: String[] options = new String[canJumpRef ? 4 : 3]; f@0: options[0] = resources.getString("options.jump.type"); f@0: options[1] = resources.getString("options.jump.diagram"); f@0: options[2] = resources.getString("options.jump.bookmark"); f@0: if(canJumpRef){ f@0: options[3] = resources.getString("options.jump.reference"); f@0: } f@0: iLog("open jump to dialog",""); f@0: String result = (String)SpeechOptionPane.showSelectionDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.input.jump.select"), f@0: options, f@0: options[0]); f@0: DiagramPanel dPanel = getActiveTab(); f@0: DiagramTree tree = dPanel.getTree(); f@0: if(result != null){ f@0: if(result.equals(options[0])){ // jump type f@0: tree.jump(DiagramTree.JumpTo.SELECTED_TYPE); f@0: }else if(result.equals(options[1])){// diagram f@0: tree.jump(DiagramTree.JumpTo.ROOT); f@0: }else if(result.equals(options[2])){// bookmark f@0: tree.jump(DiagramTree.JumpTo.BOOKMARK); f@0: }else if(result.equals(options[3])){ f@0: tree.jump(DiagramTree.JumpTo.REFERENCE); f@0: } f@0: }else{ f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: iLog("cancel jump to dialog",""); f@0: } f@0: } f@0: f@0: /** f@0: * Locates on the haptic device the node or edge currently selected on the diagram tree. A command f@0: * is sent to the haptic device which in turns drag the user to the node/edge location. f@0: */ f@0: public void locate(){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: DiagramTree tree = dPanel.getTree(); f@0: DiagramElement de = (DiagramElement)tree.getSelectionPath().getLastPathComponent(); f@0: HapticsFactory.getInstance().attractTo(System.identityHashCode(de)); f@0: iLog("locate " +((de instanceof Node)? "node" : "edge"),DiagramElement.toLogString(de)); f@0: } f@0: f@0: /** f@0: * Selects on the diagram tree the node or edge that's currently being touched by the haptic f@0: * device. f@0: */ f@0: public void hHighlight() { f@0: getActiveTab().getTree().jumpTo(hapticHighlightDiagramElement); f@0: iLog("highlight " +((hapticHighlightDiagramElement instanceof Node)? "node" : "edge"),DiagramElement.toLogString(hapticHighlightDiagramElement)); f@0: } f@0: f@0: /** f@0: * Sends a command to the haptic device to pick up an element (node or edge) for f@0: * moving it. f@0: * f@0: * @param de the diagram element to be picked up f@0: */ f@0: public void hPickUp(DiagramElement de) { f@0: HapticsFactory.getInstance().pickUp(System.identityHashCode(de)); f@0: } f@0: f@0: /** f@0: * Prompts the user for an object insertion. The object can be a node, an edge, a property, f@0: * a modifier, an edge label, an edge arrow head. Which object is to be inserted depends on f@0: * the currently selected tree node on the tree. f@0: */ f@0: public void insert(){ f@0: DiagramPanel dPanel = getActiveTab(); f@0: final DiagramTree tree = dPanel.getTree(); f@0: DiagramTreeNode treeNode = (DiagramTreeNode)tree.getSelectionPath().getLastPathComponent(); f@0: DiagramModelUpdater modelUpdater = dPanel.getDiagram().getModelUpdater(); f@0: if(treeNode instanceof TypeMutableTreeNode){ //adding a diagram Element f@0: TypeMutableTreeNode typeNode = (TypeMutableTreeNode)treeNode; f@0: final DiagramElement diagramElement = (DiagramElement)typeNode.getPrototype().clone(); f@0: try { f@0: if(diagramElement instanceof Edge){ f@0: Edge edge = (Edge)diagramElement; f@0: edge.connect(Arrays.asList(tree.getSelectedNodes())); f@0: iLog("insert edge",DiagramElement.toLogString(edge)); f@0: modelUpdater.insertInTree(diagramElement); f@0: f@0: /* remove the selections on the edge's nodes and release their lock */ f@0: tree.clearNodeSelections(); f@0: for(int i=0; i result = SpeechOptionPane.showModifiersDialog(EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.input.check_modifiers"), f@0: n.getProperties().getValues(typeNode.getType()).get(index)) , f@0: modifiers.getTypes(), f@0: modifiers.getIndexes(index) f@0: ); f@0: if(result == null){ f@0: iLog("cancel modifiers dialog",""); f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: }else{ f@0: iLog("edit modifiers",Arrays.toString(result.toArray())); f@0: modelUpdater.setModifiers(n, typeNode.getType(), index, result,DiagramEventSource.TREE); f@0: } f@0: modelUpdater.yieldLock(n, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_MODIFIERS,n.getId(),n.getName())); f@0: } f@0: }else{ //NodeReferenceMutableTreeNode = edit label and arrow head f@0: NodeReferenceMutableTreeNode nodeRef = (NodeReferenceMutableTreeNode)treeNode; f@0: Node n = (Node)nodeRef.getNode(); f@0: Edge e = (Edge)nodeRef.getEdge(); f@0: if(!modelUpdater.getLock(e, Lock.EDGE_END,new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName()))){ f@0: iLog("Could not get lock on edge for end label",DiagramElement.toLogString(e)); f@0: SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.lock_failure.end_label"), SpeechOptionPane.INFORMATION_MESSAGE); f@0: SoundFactory.getInstance().play(SoundEvent.MESSAGE_OK); f@0: return; f@0: } f@0: iLog("open edge operation selection dialog",""); f@0: f@0: boolean hasAvailArrowHeads = (e.getAvailableEndDescriptions().length > 0); f@0: String[] operations = new String[hasAvailArrowHeads ? 2 : 1]; f@0: operations[0] = resources.getString("dialog.input.edge_operation.label"); f@0: if(hasAvailArrowHeads) f@0: operations[1] = resources.getString("dialog.input.edge_operation.arrow_head"); f@0: String choice = (String)SpeechOptionPane.showSelectionDialog( f@0: EditorFrame.this, f@0: resources.getString("dialog.input.edge_operation.select"), f@0: operations, f@0: operations[0]); f@0: f@0: if(choice == null){ f@0: iLog("cancel edge operation selection dialog",""); f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: modelUpdater.yieldLock(e, Lock.EDGE_END, f@0: new DiagramEventActionSource(DiagramEventSource.TREE,Command.Name.SET_ENDLABEL,e.getId(),e.getName())); f@0: return; f@0: } f@0: if(choice.equals(operations[0])){ //operations[0] = edit edge end-label f@0: iLog("open edge label dialog",""); f@0: String label = SpeechOptionPane.showInputDialog( f@0: EditorFrame.this, f@0: MessageFormat.format(resources.getString("dialog.input.edge_label"),n.getType(), n.getName()), f@0: e.getEndLabel(n) ); f@0: if(label != null){ f@0: modelUpdater.setEndLabel(e, n, label,DiagramEventSource.TREE); f@0: SoundFactory.getInstance().play(SoundEvent.OK, new PlayerListener(){ f@0: @Override f@0: public void playEnded() { f@0: NarratorFactory.getInstance().speak(tree.currentPathSpeech()); f@0: } f@0: }); f@0: }else{ f@0: iLog("cancel edge label dialog",""); f@0: SoundFactory.getInstance().play(SoundEvent.CANCEL); f@0: } f@0: }else{//operations[1] = edit edge arrow head f@0: String[] endDescriptions = new String[e.getAvailableEndDescriptions().length+1]; f@0: for(int i=0;ibackup folder in the ccmi_editor_data directory. f@0: */ f@0: public void backupOpenDiagrams(){ f@0: SimpleDateFormat dateFormat = new SimpleDateFormat("EEE_d_MMM_yyyy_HH_mm_ss"); f@0: String date = dateFormat.format(new Date()); f@0: File backupDir = new File(new StringBuilder(backupDirPath) f@0: .append(System.getProperty("file.separator")) f@0: .append(date) f@0: .toString()); f@0: backupDir.mkdir(); f@0: for(int i=0; i 65535) f@0: badFormat = true; f@0: }catch(NumberFormatException nfe){ f@0: badFormat = true; f@0: } f@0: f@0: if(badFormat){ f@0: SoundFactory.getInstance().play(SoundEvent.ERROR, new PlayerListener(){ f@0: @Override f@0: public void playEnded() { f@0: NarratorFactory.getInstance().speak(resources.getString("speech.bad_format_port")); f@0: } f@0: }); f@0: return; f@0: } f@0: PreferencesService.getInstance().put(preferenceKey,newPort); f@0: NarratorFactory.getInstance().speak(MessageFormat.format( f@0: resources.getString(feedbackMessage),newPort)); f@0: } f@0: f@0: /** f@0: Displays the About dialog box. f@0: */ f@0: public void showAboutDialog(){ f@0: String options[] = {resources.getString("dialog.ok_button")}; f@0: SpeechSummaryPane.showDialog(this, f@0: resources.getString("dialog.about.title"), f@0: MessageFormat.format(resources.getString("dialog.about"), f@0: resources.getString("app.name"), f@0: resources.getString("app.version"), f@0: resources.getString("dialog.about.description"), f@0: resources.getString("dialog.about.license")), f@0: SpeechSummaryPane.OK_OPTION, f@0: options f@0: ); f@0: } f@0: f@0: /** f@0: * Displays the Software license in a dialog box. f@0: */ f@0: public void showLicense() { f@0: BufferedReader reader = null; f@0: try{ f@0: reader = new BufferedReader( f@0: new InputStreamReader( f@0: getClass().getResourceAsStream( f@0: "license.txt"))); f@0: StringBuilder builder = new StringBuilder(); f@0: String line; f@0: while ((line = reader.readLine()) != null){ f@0: builder.append(line).append('\n'); f@0: } f@0: String options[] = {resources.getString("dialog.ok_button")}; f@0: SpeechSummaryPane.showDialog(editorTabbedPane, f@0: resources.getString("dialog.license.title"), f@0: builder.toString(), f@0: SpeechSummaryPane.OK_OPTION,options); f@0: }catch (IOException exception){ f@0: SpeechOptionPane.showMessageDialog(this, resources.getString("dialog.error.license_not_found")); f@0: }finally{ f@0: if(reader != null) f@0: try{reader.close();}catch(IOException ioe){ioe.printStackTrace();} f@0: } f@0: } f@0: f@0: /** f@0: * Saves the diagram template in the templates folder. f@0: * f@0: * The template can then be reused to create new diagrams with the same type of nodes and f@0: * edges. f@0: * f@0: * @param diagram the diagram to get the template from f@0: * @throws IOException if something goes wrong with I/O when saving the file f@0: */ f@0: public void saveDiagramTemplate(Diagram diagram) throws IOException { f@0: File file = new File( f@0: new StringBuilder(PreferencesService.getInstance().get("home", ".")) f@0: .append(System.getProperty("file.separator")) f@0: .append(resources.getString("dir.templates")) f@0: .append(System.getProperty("file.separator")) f@0: .append(diagram.getName()) f@0: .append(resources.getString("template.extension")) f@0: .toString() f@0: ); f@0: PersistenceManager.encodeDiagramTemplate(diagram,file); f@0: f@0: } f@0: f@0: /** f@0: * Adds a diagram type to the File->New menu. f@0: * f@0: * @param diagram the diagram whose nodes and edges definition will be used as a template f@0: * for new diagrams creation. f@0: * f@0: */ f@0: public void addDiagramType(final Diagram diagram){ f@0: /* this is to prevent the user from creating other diagram prototypes with the same name */ f@0: existingTemplateNames.add(diagram.getName()); f@0: existingTemplates.add(diagram); f@0: JMenuItem newTypeItem = SpeechMenuFactory.getMenuItem(diagram.getName()); f@0: newTypeItem.addActionListener(new ActionListener(){ f@0: @Override f@0: public void actionPerformed(ActionEvent event){ f@0: Diagram clone = (Diagram)diagram.clone(); f@0: /* find a good unique name for the new tab */ f@0: Pattern pattern = Pattern.compile("new "+clone.getName()+"( \\(([0-9]+)\\))?"); f@0: int maxOpenDiagram = -1; f@0: for(int i=0;i= 0) f@0: clone.setName(String.format("new %s (%d)", clone.getName(),++maxOpenDiagram)); f@0: else clone.setName("new "+clone.getName()); f@0: addTab(null, clone); f@0: iLog("new diagram created of type: "+diagram.getName()); f@0: } f@0: }); f@0: newMenu.add(newTypeItem); f@0: } f@0: f@0: /** f@0: * Saves the user preferences before exiting. f@0: */ f@0: public void savePreferences(){ f@0: String recent = ""; f@0: for (int i = 0; i < Math.min(recentFiles.size(), maxRecentFiles); i++){ f@0: if (recent.length() > 0) recent += "|"; f@0: recent += recentFiles.get(i); f@0: } f@0: preferences.put("recent", recent); f@0: } f@0: f@0: /** f@0: * Returns the currently selected tab's diagram panel. f@0: * f@0: * @return the currently selected tab's diagram panel or {@code null} f@0: * if no tab is open. f@0: */ f@0: public DiagramPanel getActiveTab(){ f@0: return (DiagramPanel)editorTabbedPane.getSelectedComponent(); f@0: } f@0: f@0: /** f@0: * Set the variable holding the node or edge that would be highlighted if f@0: * {@link #hHighlight()} is called. The menu item for highlight is also enabled f@0: * if {@code de} is not {@code null}. f@0: * f@0: * @param de the diagram element to be selected by {@code hHighlight()} f@0: * or {@code null} for no selection. f@0: */ f@0: public void selectHapticHighligh(DiagramElement de){ f@0: hapticHighlightDiagramElement = de; f@0: highlightMenuItem.setEnabled(de == null ? false : true); f@0: } f@0: f@0: private DiagramPanel addTab(String path, Diagram diagram){ f@0: DiagramPanel diagramPanel = new DiagramPanel(diagram,editorTabbedPane); f@0: diagramPanel.setFilePath(path); f@0: diagramPanel.getTree().addTreeSelectionListener(treeSelectionListener); f@0: diagramPanel.setAwarenessPanelListener(awarenessPanelListener); f@0: /* update the haptics */ f@0: haptics.addNewDiagram(diagramPanel.getDiagram().getName()); f@0: for(Node n : diagram.getCollectionModel().getNodes()) f@0: haptics.addNode(n.getBounds().getCenterX(), n.getBounds().getCenterY(), System.identityHashCode(n),null); f@0: for(Edge e : diagram.getCollectionModel().getEdges()){ f@0: Edge.PointRepresentation pr = e.getPointRepresentation(); f@0: haptics.addEdge(System.identityHashCode(e),pr.xs,pr.ys,pr.adjMatrix,pr.nodeStart,e.getStipplePattern(),e.getNameLine(),null); f@0: } f@0: /* install the listener that handling the haptics device and the one handling the audio feedback */ f@0: diagram.getCollectionModel().addCollectionListener(hapticTrigger); f@0: AudioFeedback audioFeedback = new AudioFeedback(diagramPanel.getTree()); f@0: diagram.getCollectionModel().addCollectionListener(audioFeedback); f@0: diagram.getTreeModel().addDiagramTreeNodeListener(audioFeedback); f@0: f@0: editorTabbedPane.add(diagramPanel); f@0: editorTabbedPane.setToolTipTextAt(editorTabbedPane.getTabCount()-1,path);//the new panel is at tabCount -1 f@0: editorTabbedPane.setSelectedIndex(editorTabbedPane.getTabCount()-1); f@0: /* give the focus to the Content Pane, else it's grabbed by the rootPane f@0: and it does not work when adding a new tab with the tree focused */ f@0: getContentPane().requestFocusInWindow(); f@0: return diagramPanel; f@0: } f@0: f@0: private void diagramPanelEnabledMenuUpdate(DiagramPanel dPanel){ f@0: fileSaveItem.setEnabled(false); f@0: fileSaveAsItem.setEnabled(false); f@0: fileSaveCopyItem.setEnabled(false); f@0: fileCloseItem.setEnabled(false); f@0: shareDiagramMenuItem.setEnabled(false); f@0: graphExportItem.setEnabled(false); f@0: showAwarenessPanelMenuItem.setEnabled(false); f@0: hideAwarenessPanelMenuItem.setEnabled(false); f@0: if(dPanel == null) f@0: return; f@0: f@0: fileSaveItem.setEnabled(true); f@0: fileSaveCopyItem.setEnabled(true); f@0: graphExportItem.setEnabled(true); f@0: if(dPanel.getDiagram() instanceof NetDiagram){ f@0: if(dPanel.isAwarenessPanelVisible()) f@0: hideAwarenessPanelMenuItem.setEnabled(true); f@0: else f@0: showAwarenessPanelMenuItem.setEnabled(true); f@0: }else{ f@0: /* only enabled for local diagram, otherwise changing the name * f@0: * of the diagram would messes up the network protocol */ f@0: fileSaveAsItem.setEnabled(true); f@0: } f@0: f@0: boolean isSharedDiagram = dPanel.getDiagram() instanceof NetDiagram; f@0: if(server != null && !isSharedDiagram){ f@0: shareDiagramMenuItem.setEnabled(true); f@0: } f@0: f@0: if(!(isSharedDiagram && dPanel.getDiagram().getLabel().endsWith(NetDiagram.LOCALHOST_STRING))) f@0: fileCloseItem.setEnabled(true); f@0: } f@0: f@0: private void treeEnabledMenuUpdate(TreePath path){ f@0: canJumpRef = false; f@0: insertMenuItem.setEnabled(false); f@0: deleteMenuItem.setEnabled(false); f@0: renameMenuItem.setEnabled(false); f@0: editNotesMenuItem.setEnabled(false); f@0: bookmarkMenuItem.setEnabled(false); f@0: jumpMenuItem.setEnabled(false); f@0: locateMenuItem.setEnabled(false); f@0: selectMenuItem.setEnabled(false); f@0: if(path == null) f@0: return; f@0: f@0: jumpMenuItem.setEnabled(true); f@0: editNotesMenuItem.setEnabled(true); f@0: bookmarkMenuItem.setEnabled(true); f@0: f@0: /* jump to reference : a reference node must be selected */ f@0: DiagramTreeNode treeNode = (DiagramTreeNode)path.getLastPathComponent(); f@0: f@0: /* root node */ f@0: if((treeNode).getParent() == null) f@0: return; f@0: f@0: if(treeNode instanceof EdgeReferenceMutableTreeNode) f@0: canJumpRef = true; f@0: f@0: if(treeNode instanceof NodeReferenceMutableTreeNode){ f@0: insertMenuItem.setEnabled(true); f@0: canJumpRef = true ; f@0: } f@0: f@0: /* insert a node : the type node must be selected */ f@0: if(treeNode instanceof TypeMutableTreeNode){ f@0: insertMenuItem.setEnabled(true); f@0: } f@0: f@0: /* it's a property node */ f@0: if(treeNode instanceof PropertyMutableTreeNode){ f@0: deleteMenuItem.setEnabled(true); f@0: renameMenuItem.setEnabled(true); f@0: insertMenuItem.setEnabled(true); f@0: } f@0: f@0: if(treeNode instanceof PropertyTypeMutableTreeNode) f@0: insertMenuItem.setEnabled(true); f@0: if(treeNode instanceof DiagramElement){ f@0: deleteMenuItem.setEnabled(true); f@0: renameMenuItem.setEnabled(true); f@0: if(HapticsFactory.getInstance().isAlive()) f@0: locateMenuItem.setEnabled(true); f@0: if(treeNode instanceof Node) f@0: selectMenuItem.setEnabled(true); f@0: } f@0: } f@0: f@0: private boolean readTemplateFiles(File[] files){ f@0: f@0: /* add the pd diagam type first */ f@0: addDiagramType(new PdDiagram()); f@0: f@0: boolean someFilesNotRead = false; f@0: for(File file : files){ f@0: try { f@0: Diagram d = PersistenceManager.decodeDiagramTemplate(file); f@0: addDiagramType(d); f@0: } catch (IOException e) { f@0: someFilesNotRead = true; f@0: e.printStackTrace(); f@0: } f@0: } f@0: return someFilesNotRead; f@0: } f@0: f@0: private void iLog(String action,String args){ f@0: InteractionLog.log("TREE",action,args); f@0: } f@0: f@0: private void iLog(String message){ f@0: InteractionLog.log(message); f@0: } f@0: f@0: private Server server; f@0: private SocketChannel localSocket; f@0: private ExceptionHandler netLocalDiagramExceptionHandler; f@0: private ClientConnectionManager clientConnectionManager; f@0: private Haptics haptics; f@0: private ResourceBundle resources; f@0: public EditorTabbedPane editorTabbedPane; f@0: private FileService.ChooserService fileService; f@0: private PreferencesService preferences; f@0: private HapticTrigger hapticTrigger; f@0: private DiagramElement hapticHighlightDiagramElement; f@0: private ArrayList existingTemplateNames; f@0: private ArrayList existingTemplates; f@0: private AwarenessPanelEnablingListener awarenessPanelListener; f@0: f@0: private JMenu newMenu; f@0: private JMenuItem jumpMenuItem; f@0: private boolean canJumpRef; f@0: private JMenuItem fileSaveItem; f@0: private JMenuItem graphExportItem; f@0: private JMenuItem fileSaveAsItem; f@0: private JMenuItem fileSaveCopyItem; f@0: private JMenuItem fileCloseItem; f@0: private JMenuItem insertMenuItem; f@0: private JMenuItem deleteMenuItem; f@0: private JMenuItem renameMenuItem; f@0: private JMenuItem selectMenuItem; f@0: private JMenuItem bookmarkMenuItem; f@0: private JMenuItem editNotesMenuItem; f@0: private JMenuItem locateMenuItem; f@0: private JMenuItem highlightMenuItem; f@0: private JMenuItem shareDiagramMenuItem; f@0: private JMenuItem startServerMenuItem; f@0: private JMenuItem stopServerMenuItem; f@0: private JMenuItem showAwarenessPanelMenuItem; f@0: private JMenuItem hideAwarenessPanelMenuItem; f@0: private TreeSelectionListener treeSelectionListener; f@0: private ChangeListener tabChangeListener; f@0: private String defaultExtension; f@0: private String backupDirPath; f@0: private ArrayList recentFiles; f@0: private JMenu recentFilesMenu; f@0: private int maxRecentFiles = DEFAULT_MAX_RECENT_FILES; f@0: f@0: private ExtensionFilter extensionFilter; f@0: private ExtensionFilter exportFilter; f@0: f@0: private static final int DEFAULT_MAX_RECENT_FILES = 5; f@0: private static final double GROW_SCALE_FACTOR = Math.sqrt(2); f@0: f@0: }