// This file is part of AceWiki. // Copyright 2008-2013, AceWiki developers. // // AceWiki is free software: you can redistribute it and/or modify it under the terms of the GNU // Lesser General Public License as published by the Free Software Foundation, either version 3 of // the License, or (at your option) any later version. // // AceWiki is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License along with AceWiki. If // not, see http://www.gnu.org/licenses/. package ch.uzh.ifi.attempto.aceeditor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.List; import java.util.Map; import java.util.Properties; import nextapp.echo.app.ApplicationInstance; import nextapp.echo.app.Column; import nextapp.echo.app.Component; import nextapp.echo.app.Extent; import nextapp.echo.app.Insets; import nextapp.echo.app.SplitPane; import nextapp.echo.app.Window; import nextapp.echo.app.WindowPane; import nextapp.echo.app.event.ActionEvent; import nextapp.echo.app.event.ActionListener; import nextapp.echo.filetransfer.app.AbstractDownloadProvider; import nextapp.echo.filetransfer.app.DownloadCommand; import nextapp.echo.webcontainer.command.BrowserRedirectCommand; import ch.uzh.ifi.attempto.base.TextContainer; import ch.uzh.ifi.attempto.base.TextElement; import ch.uzh.ifi.attempto.chartparser.ChartParser; import ch.uzh.ifi.attempto.echocomp.EchoThread; import ch.uzh.ifi.attempto.echocomp.MessageWindow; import ch.uzh.ifi.attempto.echocomp.TextAreaWindow; import ch.uzh.ifi.attempto.echocomp.UploadWindow; import ch.uzh.ifi.attempto.preditor.PreditorWindow; /** * This is the main class of the ACE Editor web application. The ACE Editor allows users to write * sentences in ACE by the use of a predictive editor. Users can extend the lexicon and they can * upload their own lexica. * * @author Tobias Kuhn */ public class ACEEditor extends Window implements ActionListener { private static final long serialVersionUID = -684743065195237612L; private static Properties properties; private boolean editMode; private LexiconHandler lexiconHandler; private Map<String, String> parameters; private TextEntry selectedEntry; private TextEntry finalEntry = new TextEntry(null, this); private TextEntry clipboard; private Column textColumn = new Column(); private Column mainColumn = new Column(); private MenuBar menuBar; // TODO: reactive key combinations // private KeyStrokeListener keyStrokeListener = new KeyStrokeListener(); /** * Creates a new ACE Editor application. * * @param parameters A set of parameters in the form of name/value pairs. */ public ACEEditor(Map<String, String> parameters) { setTitle("ACE Editor"); this.parameters = parameters; lexiconHandler = new LexiconHandler(parameters.get("lexicon")); SplitPane splitPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL); splitPane.setSeparatorPosition(new Extent(23)); menuBar = new MenuBar(this); menuBar.setSelected("Default Expanded", true); menuBar.setSelected("Default Paraphrase", true); menuBar.setSelected("Default Syntax Boxes", true); menuBar.setSelected("Default Pretty-Printed DRS", true); menuBar.setEnabled("Paste", false); splitPane.add(menuBar.getContent()); textColumn.setInsets(new Insets(0, 5)); textColumn.add(finalEntry); mainColumn.add(textColumn); // // Up and down keys for moving the selection: // keyStrokeListener.addKeyCombination(VK_UP, "Up Pressed"); // keyStrokeListener.addKeyCombination(VK_DOWN, "Down Pressed"); // // // Space key for expand/collapse or add: // keyStrokeListener.addKeyCombination(VK_SPACE, "Space Pressed"); // // // Backspace key for delete: // keyStrokeListener.addKeyCombination(VK_BACK_SPACE, "Backspace Pressed"); // // // Function key + A for add: // keyStrokeListener.addKeyCombination(VK_A | CONTROL_MASK, "Func-A Pressed"); // keyStrokeListener.addKeyCombination(VK_A | META_MASK, "Func-A Pressed"); // keyStrokeListener.addKeyCombination(VK_A | ALT_MASK, "Func-A Pressed"); // // // Function key + M for modify: // keyStrokeListener.addKeyCombination(VK_M | CONTROL_MASK, "Func-M Pressed"); // keyStrokeListener.addKeyCombination(VK_M | META_MASK, "Func-M Pressed"); // keyStrokeListener.addKeyCombination(VK_M | ALT_MASK, "Func-M Pressed"); // // // Function key + X for cut: // keyStrokeListener.addKeyCombination(VK_X | CONTROL_MASK, "Func-X Pressed"); // keyStrokeListener.addKeyCombination(VK_X | META_MASK, "Func-X Pressed"); // keyStrokeListener.addKeyCombination(VK_X | ALT_MASK, "Func-X Pressed"); // // // Function key + C for copy: // keyStrokeListener.addKeyCombination(VK_C | CONTROL_MASK, "Func-C Pressed"); // keyStrokeListener.addKeyCombination(VK_C | META_MASK, "Func-C Pressed"); // keyStrokeListener.addKeyCombination(VK_C | ALT_MASK, "Func-C Pressed"); // // // Function key + V for paste: // keyStrokeListener.addKeyCombination(VK_V | CONTROL_MASK, "Func-V Pressed"); // keyStrokeListener.addKeyCombination(VK_V | META_MASK, "Func-V Pressed"); // keyStrokeListener.addKeyCombination(VK_V | ALT_MASK, "Func-V Pressed"); // // // Function key + O for open: // keyStrokeListener.addKeyCombination(VK_O | CONTROL_MASK, "Func-O Pressed"); // keyStrokeListener.addKeyCombination(VK_O | META_MASK, "Func-O Pressed"); // keyStrokeListener.addKeyCombination(VK_O | ALT_MASK, "Func-O Pressed"); // // // Function key + S for save: // keyStrokeListener.addKeyCombination(VK_S | CONTROL_MASK, "Func-S Pressed"); // keyStrokeListener.addKeyCombination(VK_S | META_MASK, "Func-S Pressed"); // keyStrokeListener.addKeyCombination(VK_S | ALT_MASK, "Func-S Pressed"); // // keyStrokeListener.addActionListener(this); // mainColumn.add(keyStrokeListener); splitPane.add(mainColumn); getContent().add(splitPane); select(finalEntry); } /** * Returns whether parsing with the compiled lexicon of the APE executable is enabled. * * @return true if parsing with the compiled lexicon is enabled. */ public boolean isParseWithClexEnabled() { return !"off".equals(getParameter("parse_with_clex")); } /** * Returns whether the lexicon is immutable or can be changed by users. * * @return true if the lexicon is immutable. */ public boolean isLexiconImmutable() { return !"off".equals(getParameter("immutable_lexicon")); } /** * Returns the maximum file size (in bytes) for file upload. 0 means unlimited file size. * * @return The maximum file size. */ public int getMaxUploadFileSize() { try { return Integer.parseInt(getParameter("max_upload_file_size")); } catch (NumberFormatException ex) {} return 0; } /** * Returns the value of the given parameter. These parameters are defined in the web.xml file * of the web application. * * @param paramName The parameter name. * @return The value of the parameter. */ public String getParameter(String paramName) { return parameters.get(paramName); } /** * Returns the full text of the current content of this ACE Editor instance. * * @return The full text. */ public String getFullText() { String text = ""; for (Component c : textColumn.getComponents()) { String s = ((TextEntry) c).getText(); if (c == finalEntry) break; if (s == null) s = ""; text += s + "\n\n"; } return text; } LexiconHandler getLexiconHandler() { return lexiconHandler; } void select(TextEntry entry) { if (selectedEntry != null) { selectedEntry.setSelected(false); } entry.setSelected(true); selectedEntry = entry; if (selectedEntry == finalEntry) { menuBar.setEnabled("Delete", false); menuBar.setEnabled("Cut", false); } else { menuBar.setEnabled("Delete", true); menuBar.setEnabled("Cut", true); } if (selectedEntry.isEmpty()) { menuBar.setEnabled("Modify...", false); } else { menuBar.setEnabled("Modify...", true); } if (selectedEntry.isEmpty() || selectedEntry.isComment()) { menuBar.setEnabled("Expanded", false); menuBar.setSelected("Expanded", false); for (String s : ResultItem.TYPES) { menuBar.setEnabled("Show " + s, false); menuBar.setSelected("Show " + s, false); } } else { menuBar.setEnabled("Expanded", true); menuBar.setSelected("Expanded", selectedEntry.isExpanded()); for (String s : ResultItem.TYPES) { menuBar.setEnabled("Show " + s, true); menuBar.setSelected("Show " + s, selectedEntry.isResultItemVisible(s)); } } menuBar.update(); } void entryChanged(TextEntry entry) { if (entry == selectedEntry) { menuBar.setSelected("Expanded", selectedEntry.isExpanded()); menuBar.update(); } } void showWindow(WindowPane window) { cleanWindows(); getContent().add(window); } void removeWindow(WindowPane window) { window.setVisible(false); window.dispose(); cleanWindows(); } private void cleanWindows() { for (Component c : getContent().getComponents()) { if (!c.isVisible()) { getContent().remove(c); } } } public void actionPerformed(ActionEvent e) { String c = e.getActionCommand(); Object source = e.getSource(); if (c.equals("About")) { String v = getInfo("aceeditor-version"); String r = getInfo("aceeditor-release-stage"); String d = getInfo("aceeditor-build-date"); showWindow(new MessageWindow( "ACE Editor", "ACE Editor " + v + " (" + r + "), " + d, "OK" )); } else if (c.equals("Attempto Website")) { getApplication().enqueueCommand( new BrowserRedirectCommand("http://attempto.ifi.uzh.ch") ); } else if (c.equals("Open Text...")) { openFile(); } else if (c.equals("Save Text...")) { saveFile(); } else if (c.equals("Load Lexicon...")) { loadLexicon(false); } else if (c.equals("Replace Lexicon...")) { loadLexicon(true); } else if (c.equals("Save Lexicon...")) { saveLexicon(); } else if (c.equals("Add...")) { showEditor(false); } else if (c.equals("Add Comment...")) { showCommentEditor(false); } else if (c.equals("Add Separator")) { TextEntry newEntry = new TextEntry(null, this); textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); select(newEntry); } else if (c.equals("Modify...")) { if (selectedEntry.isComment()) { showCommentEditor(true); } else { showEditor(true); } } else if (c.equals("Delete")) { deleteSelectedEntry(); } else if (c.equals("Cut")) { cutSelectedEntry(); } else if (c.equals("Copy")) { copySelectedEntry(); } else if (c.equals("Paste")) { pasteFromClipboard(); } else if (c.equals("Expanded")) { selectedEntry.setExpanded(menuBar.isSelected("Expanded")); } else if (c.equals("Expand All")) { for (Component comp : textColumn.getComponents()) { ((TextEntry) comp).setExpanded(true); } } else if (c.equals("Collapse All")) { for (Component comp : textColumn.getComponents()) { ((TextEntry) comp).setExpanded(false); } } else if (c.startsWith("Show ")) { selectedEntry.setResultItemVisible(c.substring(5), menuBar.isSelected(c)); selectedEntry.setExpanded(true); } else if (source instanceof PreditorWindow && c.matches("Cancel|Close|Escape")) { PreditorWindow preditor = (PreditorWindow) source; removeWindow(preditor); refreshKeyStrokeListener(); } else if (source instanceof PreditorWindow && c.matches("OK|Enter")) { PreditorWindow preditor = (PreditorWindow) source; TextContainer textContainer = preditor.getTextContainer(); if (textContainer.getTextElementsCount() == 0) { removeWindow(preditor); refreshKeyStrokeListener(); } else { if (preditor.isPossibleNextToken(".")) { textContainer.addElement(new TextElement(".")); } else if (preditor.isPossibleNextToken("?")) { textContainer.addElement(new TextElement("?")); } List<TextElement> l = textContainer.getTextElements(); if (l.isEmpty() || l.get(l.size() - 1).getText().matches("[.?]")) { if (editMode) { selectedEntry.setText(textContainer.getText()); select(selectedEntry); } else { TextEntry newEntry = new TextEntry( textContainer.getText(), this, menuBar.isSelected("Default Expanded") ); for (String s : ResultItem.TYPES) { newEntry.setResultItemVisible(s, menuBar.isSelected("Default " + s)); } textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); select(newEntry); } removeWindow(preditor); refreshKeyStrokeListener(); } else if (c.equals("OK")) { showWindow(new MessageWindow( "Error", "There are unfinished sentences.", "OK" )); } } } else if (source instanceof TextAreaWindow && c.equals("OK")) { TextAreaWindow cew = (TextAreaWindow) source; if (editMode) { selectedEntry.setText("# " + cew.getText()); select(selectedEntry); } else { TextEntry newEntry = new TextEntry("# " + cew.getText(), this, false); textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); select(newEntry); } } else if (c.equals("Upload File")) { String fileContent = ((UploadWindow) source).getFileContent(); if (fileContent != null) { textColumn.removeAll(); String[] l = fileContent.replaceAll("\\s*(#[^\\n]*\\n)", "\n\n$1\n") .split("\\n[ \\t\\x0B\\f\\r]*\\n"); for (String line : l) { TextEntry newEntry = new TextEntry(line, this, false); textColumn.add(newEntry); for (String s : ResultItem.TYPES) { newEntry.setResultItemVisible(s, menuBar.isSelected("Default " + s)); } } textColumn.add(finalEntry); select((TextEntry) textColumn.getComponent(0)); } } else if (c.equals("Load Lexicon") || c.equals("Replace Lexicon")) { String fileContent = ((UploadWindow) source).getFileContent(); if (fileContent != null) { if (c.equals("Replace Lexicon")) { textColumn.removeAll(); textColumn.add(finalEntry); select(finalEntry); lexiconHandler.clear(); } String[] l = (fileContent + " ").replaceAll("#[^\\n]*\\n", " ") .replaceAll("\\s+", " ").replaceFirst("^ ", "") .replaceAll("\\. ", ".\n").split("\\n"); for (String line : l) { if (line.equals("")) continue; lexiconHandler.addWord(line); } if (c.equals("Replace Lexicon")) { showWindow(new MessageWindow( "Lexicon Replaced", "The lexicon has been replaced.", "OK" )); } else { showWindow(new MessageWindow( "Lexicon Loaded", "The lexicon has been loaded.", "OK" )); } } } else if (c.equals("Up Pressed")) { int i = textColumn.indexOf(selectedEntry); if (i > 0) { select((TextEntry) textColumn.getComponent(i-1)); } } else if (c.equals("Down Pressed")) { int i = textColumn.indexOf(selectedEntry); if (i < textColumn.getComponentCount()-1) { select((TextEntry) textColumn.getComponent(i+1)); } } else if (c.equals("Space Pressed")) { if (selectedEntry.isEmpty()) { showEditor(false); } else { if (selectedEntry.isExpanded()) { selectedEntry.setExpanded(false); } else { selectedEntry.setExpanded(true); } } } else if (c.equals("Backspace Pressed")) { deleteSelectedEntry(); } else if (c.equals("Func-A Pressed")) { showEditor(false); } else if (c.equals("Func-M Pressed")) { if (selectedEntry.isComment()) { showCommentEditor(true); } else { showEditor(true); } } else if (c.equals("Func-X Pressed")) { cutSelectedEntry(); } else if (c.equals("Func-C Pressed")) { copySelectedEntry(); } else if (c.equals("Func-V Pressed")) { pasteFromClipboard(); } else if (c.equals("Func-O Pressed")) { openFile(); } else if (c.equals("Func-S Pressed")) { saveFile(); } } private void refreshKeyStrokeListener() { // The different keystroke listeners somehow interfere with each other so that this // work-around is needed: // mainColumn.remove(keyStrokeListener); // mainColumn.add(keyStrokeListener); } private void showEditor(boolean edit) { if (edit && selectedEntry.isEmpty()) return; ACEEditorMenuCreator menuCreator = new ACEEditorMenuCreator(this, lexiconHandler); ChartParser cp = new ChartParser(ACEEditorGrammar.grammar, "text"); cp.setDynamicLexicon(lexiconHandler); PreditorWindow preditor = new PreditorWindow("ACE Text Editor", cp); preditor.setMenuCreator(menuCreator); menuCreator.setPreditorWindow(preditor); preditor.addActionListener(this); this.editMode = edit; if (edit) { preditor.addText(selectedEntry.getText() + " "); } showWindow(preditor); } private void showCommentEditor(boolean edit) { this.editMode = edit; if (edit) { TextAreaWindow w = new TextAreaWindow("Comment Editor", this); w.setText(selectedEntry.getText().substring(2)); showWindow(w); } else { showWindow(new TextAreaWindow("Comment Editor", this)); } } private void deleteSelectedEntry() { if (selectedEntry != finalEntry) { int i = textColumn.indexOf(selectedEntry); TextEntry nextEntry = (TextEntry) textColumn.getComponent(i+1); textColumn.remove(selectedEntry); select(nextEntry); } } private void copySelectedEntry() { clipboard = selectedEntry.copy(); menuBar.setEnabled("Paste", true); menuBar.update(); } private void cutSelectedEntry() { if (selectedEntry != finalEntry) { copySelectedEntry(); deleteSelectedEntry(); } } private void pasteFromClipboard() { if (clipboard != null) { TextEntry newEntry = clipboard.copy(); textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); select(newEntry); } } private void openFile() { UploadWindow uw = new UploadWindow( "Open File", "Warning: This will delete the current content.\nChoose a file to open:", null, this ); uw.setActionCommand("Upload File"); uw.setMaxFileSize(getMaxUploadFileSize()); showWindow(uw); } private void saveFile() { final String f = getFullText(); AbstractDownloadProvider provider = new AbstractDownloadProvider() { private static final long serialVersionUID = 898782345234987345L; public String getContentType() { return "text/plain"; } public String getFileName() { return "text.ace.txt"; } public long getSize() { return f.length(); } public void writeFile(OutputStream out) throws IOException { out.write(f.getBytes()); out.close(); } }; getApplicationInstance().enqueueCommand(new DownloadCommand(provider)); } private void loadLexicon(boolean replace) { String title = "Load Lexicon"; String message = "Choose a lexicon file to load:"; String actionCommand = "Load Lexicon"; if (replace) { title = "Replace Lexicon"; message = "Warning: This will delete the current content.\n" + message; actionCommand = "Replace Lexicon"; } UploadWindow uw = new UploadWindow(title, message, null, this); uw.setActionCommand(actionCommand); uw.setMaxFileSize(getMaxUploadFileSize()); showWindow(uw); } private void saveLexicon() { final String f = lexiconHandler.getLexiconFileContent(); AbstractDownloadProvider provider = new AbstractDownloadProvider() { private static final long serialVersionUID = 1932606314346272768L; public String getContentType() { return "text/plain"; } public String getFileName() { return "text.lex.pl"; } public long getSize() { return f.length(); } public void writeFile(OutputStream out) throws IOException { out.write(f.getBytes()); out.close(); } }; getApplicationInstance().enqueueCommand(new DownloadCommand(provider)); } /** * Returns information about ACE Editor, like the version number and the release date. This * information is read from the file "aceeditor.properties". * * @param key The key string. * @return The value for the given key. */ public static String getInfo(String key) { if (properties == null) { String f = "ch/uzh/ifi/attempto/aceeditor/aceeditor.properties"; InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(f); properties = new Properties(); try { properties.load(in); } catch (Exception ex) { ex.printStackTrace(); } } return properties.getProperty(key); } private ApplicationInstance getApplication() { return EchoThread.getActiveApplication(); } }