001 //This file is part of the Attempto Java Packages. 002 //Copyright 2008-2009, Attempto Group, University of Zurich (see http://attempto.ifi.uzh.ch). 003 004 //The Attempto Java Packages is free software: you can redistribute it and/or modify it under the 005 //terms of the GNU Lesser General Public License as published by the Free Software Foundation, 006 //either version 3 of the License, or (at your option) any later version. 007 008 //The Attempto Java Packages is distributed in the hope that it will be useful, but WITHOUT ANY 009 //WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 010 //PURPOSE. See the GNU Lesser General Public License for more details. 011 012 //You should have received a copy of the GNU Lesser General Public License along with the Attempto 013 //Java Packages. If not, see http://www.gnu.org/licenses/. 014 015 package ch.uzh.ifi.attempto.aceeditor; 016 017 import java.io.IOException; 018 import java.io.InputStream; 019 import java.io.OutputStream; 020 import java.util.ArrayList; 021 import java.util.Properties; 022 023 import nextapp.echo2.app.ApplicationInstance; 024 import nextapp.echo2.app.Column; 025 import nextapp.echo2.app.Component; 026 import nextapp.echo2.app.Extent; 027 import nextapp.echo2.app.Insets; 028 import nextapp.echo2.app.SplitPane; 029 import nextapp.echo2.app.Window; 030 import nextapp.echo2.app.event.ActionEvent; 031 import nextapp.echo2.app.event.ActionListener; 032 import nextapp.echo2.app.filetransfer.Download; 033 import nextapp.echo2.app.filetransfer.DownloadProvider; 034 import nextapp.echo2.webcontainer.command.BrowserRedirectCommand; 035 import ch.uzh.ifi.attempto.aceeditor.grammar.ACEGrammar; 036 import ch.uzh.ifi.attempto.echocomp.MessageWindow; 037 import ch.uzh.ifi.attempto.echocomp.TextAreaWindow; 038 import ch.uzh.ifi.attempto.echocomp.UploadWindow; 039 import ch.uzh.ifi.attempto.preditor.PreditorWindow; 040 import ch.uzh.ifi.attempto.preditor.text.ContextChecker; 041 import ch.uzh.ifi.attempto.preditor.text.EnglishContextChecker; 042 import ch.uzh.ifi.attempto.preditor.text.TextContainer; 043 import ch.uzh.ifi.attempto.preditor.text.TextElement; 044 import echopointng.KeyStrokeListener; 045 046 /** 047 * This is the ACE Editor web application. 048 * 049 * @author Tobias Kuhn 050 */ 051 public class ACEEditor extends Window implements ActionListener { 052 053 private static final long serialVersionUID = -684743065195237612L; 054 055 private static final ContextChecker contextChecker = new EnglishContextChecker(true, true); 056 057 private Column textColumn = new Column(); 058 private Column mainColumn = new Column(); 059 private MenuBar menuBar; 060 private TextEntry selectedEntry; 061 private TextEntry finalEntry = new TextEntry(null, this); 062 private boolean edit; 063 private TextEntry clipboard; 064 private KeyStrokeListener keyStrokeListener = new KeyStrokeListener(); 065 private int downloadCount = 0; 066 private LexiconHandler lexiconHandler; 067 private boolean parseWithClexEnabled; 068 069 private static Properties properties; 070 071 /** 072 * Creates a new ACE Editor application. 073 * 074 * @param lexiconFile The file name of the lexicon to be used. 075 * @param parseWithClexEnabled true if the compiled lexicon (clex) of the APE executable should be used for 076 * parsing the ACE sentences. This should be done only if the compiled lexicon is the same as the 077 * lexicon defined by the first argument. Parsing with the compiled lexicon is faster. 078 */ 079 public ACEEditor(String lexiconFile, boolean parseWithClexEnabled) { 080 setTitle("ACE Editor"); 081 lexiconHandler = new LexiconHandler(lexiconFile); 082 this.parseWithClexEnabled = parseWithClexEnabled; 083 084 SplitPane splitPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL); 085 splitPane.setSeparatorPosition(new Extent(25)); 086 087 menuBar = new MenuBar(this); 088 menuBar.setSelected("Default Expanded", true); 089 menuBar.setSelected("Default Paraphrase", true); 090 menuBar.setSelected("Default Syntax Boxes", true); 091 menuBar.setSelected("Default Pretty-Printed DRS", true); 092 menuBar.setEnabled("Paste", false); 093 splitPane.add(menuBar); 094 095 textColumn.setInsets(new Insets(0, 5)); 096 textColumn.add(finalEntry); 097 098 mainColumn.add(textColumn); 099 100 // Up and down keys for moving the selection: 101 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_UP, "Up Pressed"); 102 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_DOWN, "Down Pressed"); 103 104 // Space key for expand/collapse or add: 105 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_SPACE, "Space Pressed"); 106 107 // Backspace key for delete: 108 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_BACK_SPACE, "Backspace Pressed"); 109 110 // Function key + A for add: 111 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_A | KeyStrokeListener.CONTROL_MASK, "Func-A Pressed"); 112 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_A | KeyStrokeListener.META_MASK, "Func-A Pressed"); 113 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_A | KeyStrokeListener.ALT_MASK, "Func-A Pressed"); 114 115 // Function key + M for modify: 116 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_M | KeyStrokeListener.CONTROL_MASK, "Func-M Pressed"); 117 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_M | KeyStrokeListener.META_MASK, "Func-M Pressed"); 118 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_M | KeyStrokeListener.ALT_MASK, "Func-M Pressed"); 119 120 // Function key + X for cut: 121 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_X | KeyStrokeListener.CONTROL_MASK, "Func-X Pressed"); 122 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_X | KeyStrokeListener.META_MASK, "Func-X Pressed"); 123 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_X | KeyStrokeListener.ALT_MASK, "Func-X Pressed"); 124 125 // Function key + C for copy: 126 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_C | KeyStrokeListener.CONTROL_MASK, "Func-C Pressed"); 127 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_C | KeyStrokeListener.META_MASK, "Func-C Pressed"); 128 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_C | KeyStrokeListener.ALT_MASK, "Func-C Pressed"); 129 130 // Function key + V for paste: 131 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_V | KeyStrokeListener.CONTROL_MASK, "Func-V Pressed"); 132 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_V | KeyStrokeListener.META_MASK, "Func-V Pressed"); 133 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_V | KeyStrokeListener.ALT_MASK, "Func-V Pressed"); 134 135 // Function key + O for open: 136 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_O | KeyStrokeListener.CONTROL_MASK, "Func-O Pressed"); 137 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_O | KeyStrokeListener.META_MASK, "Func-O Pressed"); 138 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_O | KeyStrokeListener.ALT_MASK, "Func-O Pressed"); 139 140 // Function key + S for save: 141 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_S | KeyStrokeListener.CONTROL_MASK, "Func-S Pressed"); 142 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_S | KeyStrokeListener.META_MASK, "Func-S Pressed"); 143 keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_S | KeyStrokeListener.ALT_MASK, "Func-S Pressed"); 144 145 keyStrokeListener.addActionListener(this); 146 mainColumn.add(keyStrokeListener); 147 148 splitPane.add(mainColumn); 149 getContent().add(splitPane); 150 151 select(finalEntry); 152 } 153 154 /** 155 * Returns whether parsing with the compiled lexicon of the APE executable is enabled. 156 * 157 * @return true if parsing with the compiled lexicon is enabled. 158 */ 159 public boolean isParseWithClexEnabled() { 160 return parseWithClexEnabled; 161 } 162 163 LexiconHandler getLexiconHandler() { 164 return lexiconHandler; 165 } 166 167 void select(TextEntry entry) { 168 if (selectedEntry != null) { 169 selectedEntry.setSelected(false); 170 } 171 entry.setSelected(true); 172 selectedEntry = entry; 173 174 if (selectedEntry == finalEntry) { 175 menuBar.setEnabled("Delete", false); 176 menuBar.setEnabled("Cut", false); 177 } else { 178 menuBar.setEnabled("Delete", true); 179 menuBar.setEnabled("Cut", true); 180 } 181 if (selectedEntry.isEmpty()) { 182 menuBar.setEnabled("Modify...", false); 183 } else { 184 menuBar.setEnabled("Modify...", true); 185 } 186 if (selectedEntry.isEmpty() || selectedEntry.hasError() || selectedEntry.isComment()) { 187 menuBar.setEnabled("Expanded", false); 188 menuBar.setSelected("Expanded", false); 189 for (String s : ResultItem.TYPES) { 190 menuBar.setEnabled("Show " + s, false); 191 menuBar.setSelected("Show " + s, false); 192 } 193 } else { 194 menuBar.setEnabled("Expanded", true); 195 menuBar.setSelected("Expanded", selectedEntry.isExpanded()); 196 for (String s : ResultItem.TYPES) { 197 menuBar.setEnabled("Show " + s, true); 198 menuBar.setSelected("Show " + s, selectedEntry.isResultItemVisible(s)); 199 } 200 } 201 menuBar.update(); 202 } 203 204 public void actionPerformed(ActionEvent e) { 205 String command = e.getActionCommand(); 206 Object source = e.getSource(); 207 208 if (command.equals("About")) { 209 String v = getInfo("aceeditor-version") + " (" + getInfo("aceeditor-release-stage") + ")"; 210 String d = getInfo("aceeditor-build-date"); 211 String a = getInfo("aceeditor-developer"); 212 getContent().add(new MessageWindow("ACE Editor", "ACE Editor " + v + ", " + d + ", " + a, "OK")); 213 } else if (command.equals("Attempto Website")) { 214 ApplicationInstance.getActive().enqueueCommand(new BrowserRedirectCommand("http://attempto.ifi.uzh.ch")); 215 } else if (command.equals("Open...")) { 216 openFile(); 217 } else if (command.equals("Save As...")) { 218 saveFile(); 219 } else if (command.equals("Add...")) { 220 showEditor(false); 221 } else if (command.equals("Add Comment...")) { 222 showCommentEditor(false); 223 } else if (command.equals("Add Separator")) { 224 TextEntry newEntry = new TextEntry(null, this); 225 textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); 226 select(newEntry); 227 } else if (command.equals("Modify...")) { 228 if (selectedEntry.isComment()) { 229 showCommentEditor(true); 230 } else { 231 showEditor(true); 232 } 233 } else if (command.equals("Delete")) { 234 deleteSelectedEntry(); 235 } else if (command.equals("Cut")) { 236 cutSelectedEntry(); 237 } else if (command.equals("Copy")) { 238 copySelectedEntry(); 239 } else if (command.equals("Paste")) { 240 pasteFromClipboard(); 241 } else if (command.equals("Expanded")) { 242 selectedEntry.setExpanded(menuBar.isSelected("Expanded")); 243 } else if (command.equals("Expand All")) { 244 for (Component c : textColumn.getComponents()) { 245 ((TextEntry) c).setExpanded(true); 246 } 247 } else if (command.equals("Collapse All")) { 248 for (Component c : textColumn.getComponents()) { 249 ((TextEntry) c).setExpanded(false); 250 } 251 } else if (command.startsWith("Show ")) { 252 selectedEntry.setResultItemVisible(command.substring(5), menuBar.isSelected(command)); 253 selectedEntry.setExpanded(true); 254 } else if (source instanceof PreditorWindow && command.equals("Cancel")) { 255 PreditorWindow preditor = (PreditorWindow) source; 256 preditor.setVisible(false); 257 preditor.dispose(); 258 refreshKeyStrokeListener(); 259 } else if (source instanceof PreditorWindow && command.equals("OK")) { 260 PreditorWindow preditor = (PreditorWindow) source; 261 preditor.setContextChecker(contextChecker); 262 TextContainer textContainer = preditor.getTextContainer(); 263 if (textContainer.getTextElementsCount() == 0) { 264 preditor.dispose(); 265 preditor.setVisible(false); 266 refreshKeyStrokeListener(); 267 } else { 268 ArrayList<TextElement> finalElements = preditor.getPossibleNextTokens(".", "?"); 269 if (!finalElements.isEmpty()) textContainer.addElement(finalElements.get(0)); 270 ArrayList<TextElement> l = textContainer.getTextElements(); 271 if (l.isEmpty() || l.get(l.size() - 1).getText().matches("[.?]")) { 272 if (edit) { 273 selectedEntry.setAceText(textContainer.getText()); 274 select(selectedEntry); 275 } else { 276 TextEntry newEntry = new TextEntry(textContainer.getText(), this, menuBar.isSelected("Default Expanded")); 277 for (String s : ResultItem.TYPES) { 278 newEntry.setResultItemVisible(s, menuBar.isSelected("Default " + s)); 279 } 280 textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); 281 select(newEntry); 282 } 283 preditor.dispose(); 284 preditor.setVisible(false); 285 refreshKeyStrokeListener(); 286 } else { 287 getContent().add(new MessageWindow("Error", "There are unfinished sentences.", "OK")); 288 } 289 } 290 } else if (source instanceof TextAreaWindow && command.equals("OK")) { 291 TextAreaWindow cew = (TextAreaWindow) source; 292 if (edit) { 293 selectedEntry.setAceText("# " + cew.getText()); 294 select(selectedEntry); 295 } else { 296 TextEntry newEntry = new TextEntry("# " + cew.getText(), this, false); 297 textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); 298 select(newEntry); 299 } 300 } else if (command.equals("Upload")) { 301 String fileContent = ((UploadWindow) source).getFileContent(); 302 if (fileContent != null) { 303 textColumn.removeAll(); 304 String[] l = fileContent.replaceAll("\\s*(#[^\\n]*\\n)", "\n\n$1\n").split("\\n[ \\t\\x0B\\f\\r]*\\n"); 305 for (String line : l) { 306 TextEntry newEntry = new TextEntry(line, this, false); 307 textColumn.add(newEntry); 308 for (String s : ResultItem.TYPES) { 309 newEntry.setResultItemVisible(s, menuBar.isSelected("Default " + s)); 310 } 311 } 312 textColumn.add(finalEntry); 313 select((TextEntry) textColumn.getComponent(0)); 314 } 315 } else if (command.equals("Up Pressed")) { 316 int i = textColumn.indexOf(selectedEntry); 317 if (i > 0) { 318 select((TextEntry) textColumn.getComponent(i-1)); 319 } 320 } else if (command.equals("Down Pressed")) { 321 int i = textColumn.indexOf(selectedEntry); 322 if (i < textColumn.getComponentCount()-1) { 323 select((TextEntry) textColumn.getComponent(i+1)); 324 } 325 } else if (command.equals("Space Pressed")) { 326 if (selectedEntry.isEmpty()) { 327 showEditor(false); 328 } else { 329 if (selectedEntry.isExpanded()) { 330 selectedEntry.setExpanded(false); 331 } else { 332 selectedEntry.setExpanded(true); 333 } 334 } 335 } else if (command.equals("Backspace Pressed")) { 336 deleteSelectedEntry(); 337 } else if (command.equals("Func-A Pressed")) { 338 showEditor(false); 339 } else if (command.equals("Func-M Pressed")) { 340 if (selectedEntry.isComment()) { 341 showCommentEditor(true); 342 } else { 343 showEditor(true); 344 } 345 } else if (command.equals("Func-X Pressed")) { 346 cutSelectedEntry(); 347 } else if (command.equals("Func-C Pressed")) { 348 copySelectedEntry(); 349 } else if (command.equals("Func-V Pressed")) { 350 pasteFromClipboard(); 351 } else if (command.equals("Func-O Pressed")) { 352 openFile(); 353 } else if (command.equals("Func-S Pressed")) { 354 saveFile(); 355 } 356 } 357 358 private void refreshKeyStrokeListener() { 359 // The different keystroke listeners somehow interfere with each other so that this 360 // work-around is needed: 361 mainColumn.remove(keyStrokeListener); 362 mainColumn.add(keyStrokeListener); 363 } 364 365 private void showEditor(boolean edit) { 366 if (edit && selectedEntry.isEmpty()) return; 367 368 PreditorWindow preditor = new PreditorWindow("ACE Text Editor", new ACEGrammar(), new ACEEditorMenuCreator(lexiconHandler)); 369 preditor.setContextChecker(contextChecker); 370 preditor.addActionListener(this); 371 this.edit = edit; 372 if (edit) { 373 preditor.addText(selectedEntry.getAceText() + " "); 374 } 375 getContent().add(preditor); 376 } 377 378 private void showCommentEditor(boolean edit) { 379 this.edit = edit; 380 if (edit) { 381 getContent().add(new TextAreaWindow("Comment Editor", selectedEntry.getAceText().substring(2), this)); 382 } else { 383 getContent().add(new TextAreaWindow("Comment Editor", "", this)); 384 } 385 } 386 387 private void deleteSelectedEntry() { 388 if (selectedEntry != finalEntry) { 389 int i = textColumn.indexOf(selectedEntry); 390 TextEntry nextEntry = (TextEntry) textColumn.getComponent(i+1); 391 textColumn.remove(selectedEntry); 392 select(nextEntry); 393 } 394 } 395 396 private void copySelectedEntry() { 397 clipboard = selectedEntry.copy(); 398 menuBar.setEnabled("Paste", true); 399 menuBar.update(); 400 } 401 402 private void cutSelectedEntry() { 403 if (selectedEntry != finalEntry) { 404 copySelectedEntry(); 405 deleteSelectedEntry(); 406 } 407 } 408 409 private void pasteFromClipboard() { 410 if (clipboard != null) { 411 TextEntry newEntry = clipboard.copy(); 412 textColumn.add(newEntry, textColumn.indexOf(selectedEntry)); 413 select(newEntry); 414 } 415 } 416 417 private void openFile() { 418 UploadWindow uw = new UploadWindow( 419 "Open File", 420 "Warning: This will delete the current content.\nChoose a file to open:", 421 null, 422 this 423 ); 424 uw.setMaxFileSize(10000); 425 getContent().add(uw); 426 } 427 428 private void saveFile() { 429 final String f = getFullText(); 430 downloadCount++; 431 DownloadProvider provider = new DownloadProvider() { 432 433 public String getContentType() { 434 return "text/plain"; 435 } 436 437 public String getFileName() { 438 return "text" + downloadCount + ".ace.txt"; 439 } 440 441 public int getSize() { 442 return f.length(); 443 } 444 445 public void writeFile(OutputStream out) throws IOException { 446 out.write(f.getBytes()); 447 out.close(); 448 } 449 450 }; 451 getApplicationInstance().enqueueCommand(new Download(provider, true)); 452 } 453 454 void entryChanged(TextEntry entry) { 455 if (entry == selectedEntry) { 456 menuBar.setSelected("Expanded", selectedEntry.isExpanded()); 457 menuBar.update(); 458 } 459 } 460 461 /** 462 * Returns the full text of the current content of this ACE Editor instance. 463 * 464 * @return The full text. 465 */ 466 public String getFullText() { 467 String text = ""; 468 for (Component c : textColumn.getComponents()) { 469 String s = ((TextEntry) c).getAceText(); 470 if (c == finalEntry) break; 471 if (s == null) s = ""; 472 text += s + "\n\n"; 473 } 474 return text; 475 } 476 477 /** 478 * Returns information about ACE Editor, like the version number and the release date. This 479 * information is read from the file "aceeditor.properties". 480 * 481 * @param key The key string. 482 * @return The value for the given key. 483 */ 484 public static String getInfo(String key) { 485 if (properties == null) { 486 String f = "ch/uzh/ifi/attempto/aceeditor/aceeditor.properties"; 487 InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(f); 488 properties = new Properties(); 489 try { 490 properties.load(in); 491 } catch (Exception ex) { 492 ex.printStackTrace(); 493 } 494 } 495 496 return properties.getProperty(key); 497 } 498 499 }