Project: AceWiki
// This file is part of AceWiki.
// Copyright 2008-2012, 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.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(nullthis); 
 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(05)); 
  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")) { 
   ApplicationInstance.getActive().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(nullthis); 
   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(), thisfalse); 
    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, thisfalse); 
     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) { 
   showWindow(new TextAreaWindow( 
     "Comment Editor"
     selectedEntry.getText().substring(2), 
     this 
    )); 
  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, nullthis); 
  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); 
 
 
}