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    }