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: }