    001    // This file is part of the Attempto Java Packages.
    002    // Copyright 2008, 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.preditor;
    016
    017    import java.util.ArrayList;
    018    import java.util.Arrays;
    019    import java.util.List;
    020
    021    import nextapp.echo2.app.Alignment;
    022    import nextapp.echo2.app.ApplicationInstance;
    023    import nextapp.echo2.app.Border;
    024    import nextapp.echo2.app.Button;
    025    import nextapp.echo2.app.Color;
    026    import nextapp.echo2.app.Column;
    027    import nextapp.echo2.app.Extent;
    028    import nextapp.echo2.app.Font;
    029    import nextapp.echo2.app.Insets;
    030    import nextapp.echo2.app.Row;
    031    import nextapp.echo2.app.SplitPane;
    032    import nextapp.echo2.app.TextArea;
    033    import nextapp.echo2.app.event.ActionEvent;
    034    import nextapp.echo2.app.event.ActionListener;
    035    import ch.uzh.ifi.attempto.chartparser.ChartParser;
    036    import ch.uzh.ifi.attempto.chartparser.Grammar;
    037    import ch.uzh.ifi.attempto.echocomp.GeneralButton;
    038    import ch.uzh.ifi.attempto.echocomp.Label;
    039    import ch.uzh.ifi.attempto.echocomp.Style;
    040    import ch.uzh.ifi.attempto.echocomp.TextField;
    041    import ch.uzh.ifi.attempto.echocomp.WindowPane;
    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 class represents a predictive editor window. The predictive editor enables easy creation of texts that
    048     * comply with a certain grammar. The users can create such a text word-by-word by clicking on one of different
    049     * menu items. The menu items are structured into menu blocks each of which has a name that is displayed above
    050     * the menu block.
    051     *
    052     * @author Tobias Kuhn
    053     */
    054    public class PreditorWindow extends WindowPane implements ActionListener {
    055
    056            private static final long serialVersionUID = -7815494421993305554L;
    057
    058            private List<MenuBlockContent> menuBlockContents = new ArrayList<MenuBlockContent>();
    059            private List<MenuBlock> menuBlocksTop = new ArrayList<MenuBlock>();
    060            private List<MenuBlock> menuBlocksBottom = new ArrayList<MenuBlock>();
    061            private List<SplitPane> menuSplitPanesTop = new ArrayList<SplitPane>();
    062            private List<SplitPane> menuSplitPanesBottom = new ArrayList<SplitPane>();
    063
    064            private TextArea textArea = new TextArea();
    065            private TextField textField;
    066            private SplitPane menuBlockPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT, new Extent(0));
    067            private SplitPane doubleColumnMenuPane =  new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM, new Extent(258));
    068            private GeneralButton deleteButton = new GeneralButton("< Delete", 70, this);
    069
    070            private KeyStrokeListener keyStrokeListener = new KeyStrokeListener();
    071
    072            private TextContainer newTextContainer = new TextContainer();
    073            private MenuCreator menuCreator;
    074
    075            private ChartParser parser;
    076
    077            private Button okButton = new GeneralButton("OK", 70, this);
    078            private Button cancelButton = new GeneralButton("Cancel", 70, this);
    079
    080            private ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener>();
    081
    082            /**
    083             * Creates a new predictive editor window for the given grammar using the given menu creator.
    084             *
    085             * @param title The title of the window.
    086             * @param grammar The grammar to be used.
    087             * @param menuCreator The menu creator to be used.
    088             */
    089            public PreditorWindow(String title, Grammar grammar, MenuCreator menuCreator) {
    090                    this.parser = new ChartParser(grammar);
    091                    this.menuCreator = menuCreator;
    092
    093                    setModal(true);
    094                    setTitle(title);
    095                    setTitleFont(new Font(Style.fontTypeface, Font.ITALIC, new Extent(13)));
    096                    setWidth(new Extent(753));
    097                    setHeight(new Extent(503));
    098                    setResizable(false);
    099                    setTitleBackground(Style.windowTitleBackground);
    100                    setStyleName("Default");
    101
    102                    Row buttonBar = new Row();
    103                    buttonBar.setAlignment(new Alignment(Alignment.RIGHT, Alignment.CENTER));
    104                    buttonBar.setInsets(new Insets(10, 17, 10, 10));
    105                    buttonBar.setCellSpacing(new Extent(5));
    106                    buttonBar.add(okButton);
    107                    buttonBar.add(cancelButton);
    108
    109                    SplitPane splitPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL_BOTTOM_TOP, new Extent(47));
    110                    splitPane.add(buttonBar);
    111                    add(splitPane);
    112
    113                    SplitPane editorPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM, new Extent(148));
    114
    115                    Column textColumn = new Column();
    116                    textColumn.setInsets(new Insets(10, 10, 0, 0));
    117                    textColumn.setCellSpacing(new Extent(10));
    118
    119                    Column textAreaColumn = new Column();
    120
    121                    textArea.setWidth(new Extent(702));
    122                    textArea.setHeight(new Extent(42));
    123                    textArea.setEnabled(false);
    124                    textArea.setFont(new Font(Style.fontTypeface, Font.PLAIN, new Extent(12)));
    125                    textArea.setBorder(new Border(1, new Color(180, 180, 180), Border.STYLE_SOLID));
    126                    textArea.setInsets(new Insets(4, 4));
    127                    textArea.setBackground(new Color(255, 255, 255));
    128                    textArea.setFocusTraversalParticipant(false);
    129                    textAreaColumn.add(textArea);
    130
    131                    Row textAreaButtonBar = new Row();
    132                    textAreaButtonBar.setAlignment(new Alignment(Alignment.RIGHT, Alignment.CENTER));
    133                    textAreaButtonBar.setInsets(new Insets(0, 5, 10, 0));
    134                    textAreaButtonBar.setCellSpacing(new Extent(5));
    135                    deleteButton.setFocusTraversalParticipant(false);
    136                    textAreaButtonBar.add(deleteButton);
    137                    textAreaColumn.add(textAreaButtonBar);
    138
    139                    textColumn.add(textAreaColumn);
    140
    141                    Column textFieldColumn = new Column();
    142                    textFieldColumn.setCellSpacing(new Extent(1));
    143                    Label textFieldLabel = new Label("text", Font.ITALIC, 11);
    144                    textFieldColumn.add(textFieldLabel);
    145
    146                    textField = new TextField(this);
    147                    textField.setWidth(new Extent(708));
    148                    textField.setFocusTraversalParticipant(true);
    149                    textField.setFocusTraversalIndex(0);
    150                    textField.setDisabledBackground(Style.lightDisabled);
    151                    Row textFieldRow = new Row();
    152                    textFieldRow.add(textField);
    153                    TextField dummyTextField = new TextField();
    154                    dummyTextField.setWidth(new Extent(1));
    155                    dummyTextField.setBorder(new Border(0, null, 0));
    156                    dummyTextField.setBackground(Color.WHITE);
    157                    textFieldRow.add(dummyTextField);
    158                    textFieldColumn.add(textFieldRow);
    159
    160                    keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_TAB, "Tab");
    161                    keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_RIGHT, "Tab");
    162                    keyStrokeListener.addActionListener(this);
    163                    textFieldColumn.add(keyStrokeListener);
    164
    165                    textColumn.add(textFieldColumn);
    166                    editorPane.add(textColumn);
    167
    168                    menuBlockPane.setSeparatorWidth(new Extent(10));
    169                    menuBlockPane.setSeparatorColor(Color.WHITE);
    170                    menuBlockPane.add(new Label());
    171
    172                    SplitPane parentSplitPane = doubleColumnMenuPane;
    173                    for (int i=0; i<10; i++) {
    174                            MenuBlock menuBlock = new MenuBlock(this, this);
    175                            menuBlocksTop.add(menuBlock);
    176                            SplitPane menuSplitPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT);
    177                            menuSplitPane.setSeparatorWidth(new Extent(10));
    178                            menuSplitPane.setSeparatorColor(Color.WHITE);
    179                            menuSplitPane.setVisible(false);
    180                            menuSplitPane.add(menuBlock);
    181                            menuSplitPanesTop.add(menuSplitPane);
    182                            parentSplitPane.add(menuSplitPane);
    183                            parentSplitPane = menuSplitPane;
    184                    }
    185
    186                    parentSplitPane = doubleColumnMenuPane;
    187                    for (int i=0; i<10; i++) {
    188                            MenuBlock menuBlock = new MenuBlock(this, this);
    189                            menuBlocksBottom.add(menuBlock);
    190                            SplitPane menuSplitPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT);
    191                            menuSplitPane.setSeparatorWidth(new Extent(10));
    192                            menuSplitPane.setSeparatorColor(Color.WHITE);
    193                            menuSplitPane.setVisible(false);
    194                            menuSplitPane.add(menuBlock);
    195                            menuSplitPanesBottom.add(menuSplitPane);
    196                            parentSplitPane.add(menuSplitPane);
    197                            parentSplitPane = menuSplitPane;
    198                    }
    199
    200                    doubleColumnMenuPane.setSeparatorHeight(new Extent(12));
    201                    doubleColumnMenuPane.setSeparatorColor(Color.WHITE);
    202                    menuBlockPane.add(doubleColumnMenuPane);
    203                    editorPane.add(menuBlockPane);
    204                    splitPane.add(editorPane);
    205
    206                    update();
    207            }
    208
    209            /**
    210             * Returns the (partial) text that has been entered .
    211             *
    212             * @return The (partial) text in the form of a text container.
    213             */
    214            public TextContainer getTextContainer() {
    215                    return newTextContainer;
    216            }
    217
    218            /**
    219             * Returns a text element with the given content if it is a possible next token.
    220             *
    221             * @param content The content of the text element.
    222             * @return The text element.
    223             */
    224            public TextElement getPossibleNextToken(String content) {
    225                    for (MenuBlockContent m : menuBlockContents) {
    226                            TextElement e = m.getEntry(content);
    227                            if (e != null) return e;
    228                    }
    229                    return null;
    230            }
    231
    232            /**
    233             * Adds the text element to the end of the text.
    234             *
    235             * @param te The text element to be added.
    236             */
    237            public void addTextElement(TextElement te) {
    238                    textElementSelected(te);
    239                    textField.setText("");
    240                    update();
    241            }
    242
    243            /**
    244             * Reads the text and adds it to the end of the current text as far as possible.
    245             *
    246             * @param text The text to be added.
    247             */
    248            public void addText(String text) {
    249                    handleTextInput(tokenize(text));
    250                    update();
    251            }
    252
    253            private void textElementSelected(TextElement te) {
    254                    newTextContainer.addElement(te);
    255                    parser.addToken(te.getCategory());
    256                    log("edit", "words added: " + te.getText());
    257            }
    258
    259            private int getElementsCount() {
    260                    int c = 0;
    261                    for (MenuBlockContent m : menuBlockContents) {
    262                            c += m.getEntries().size();
    263                    }
    264                    return c;
    265            }
    266
    267            private String getStartString() {
    268                    String startString = "";
    269                    ArrayList<String> blockStartStrings = new ArrayList<String>();
    270
    271                    for (MenuBlockContent mc : menuBlockContents) {
    272                            String s = mc.getStartString();
    273                            if (s != null) {
    274                                    blockStartStrings.add(s);
    275                            }
    276                    }
    277
    278                    if (blockStartStrings.isEmpty()) return null;
    279
    280                    String first = blockStartStrings.get(0);
    281                    blockStartStrings.remove(0);
    282
    283                    if (blockStartStrings.isEmpty()) return first;
    284
    285                    for (int i = 0; i < first.length(); i++) {
    286                            char c = first.charAt(i);
    287                            boolean stop = false;
    288                            for (String s : blockStartStrings) {
    289                                    if (s.length() <= i || s.charAt(i) != c) stop = true;
    290                            }
    291                            if (stop) break;
    292                            startString += c;
    293                    }
    294
    295                    return startString;
    296            }
    297
    298            private void update() {
    299                    updateMenuBlockContents();
    300                    setFilter(textField.getText());
    301                    int mbCount = menuBlockContents.size();
    302                    if (mbCount < 5) {
    303                            int width = ( 720 / ( mbCount > 3 ? mbCount : 3 ) ) - 10;
    304                            for (int i=0; i < menuBlocksTop.size(); i++) {
    305                                    if (menuBlockContents.size() > i) {
    306                                            menuBlocksTop.get(i).setContent(menuBlockContents.get(i), width, 16);
    307                                            menuSplitPanesTop.get(i).setSeparatorPosition(new Extent(width));
    308                                            menuSplitPanesTop.get(i).setVisible(true);
    309                                    } else {
    310                                            menuSplitPanesTop.get(i).setVisible(false);
    311                                    }
    312                            }
    313                            doubleColumnMenuPane.setSeparatorPosition(new Extent(258));
    314                    } else {
    315                            int firstRowCount = (mbCount + 1) / 2;
    316                            int width = ( 720 / firstRowCount ) - 10;
    317                            for (int i=0; i < menuBlocksTop.size(); i++) {
    318                                    if (i < firstRowCount) {
    319                                            menuBlocksTop.get(i).setContent(menuBlockContents.get(i), width, 7);
    320                                            menuSplitPanesTop.get(i).setSeparatorPosition(new Extent(width));
    321                                            menuSplitPanesTop.get(i).setVisible(true);
    322                                    } else {
    323                                            menuSplitPanesTop.get(i).setVisible(false);
    324                                    }
    325                            }
    326                            for (int i=0; i < menuBlocksBottom.size(); i++) {
    327                                    if (firstRowCount + i < mbCount) {
    328                                            menuBlocksBottom.get(i).setContent(menuBlockContents.get(firstRowCount + i), width, 7);
    329                                            menuSplitPanesBottom.get(i).setSeparatorPosition(new Extent(width));
    330                                            menuSplitPanesBottom.get(i).setVisible(true);
    331                                    } else {
    332                                            menuSplitPanesBottom.get(i).setVisible(false);
    333                                    }
    334                            }
    335                            doubleColumnMenuPane.setSeparatorPosition(new Extent(123));
    336                    }
    337                    textField.setEnabled(menuBlockContents.size() > 0 || !textField.getText().equals(""));
    338                    ApplicationInstance.getActive().setFocusedComponent(textField);
    339                    deleteButton.setEnabled(textArea.getText().length() > 0);
    340            }
    341
    342            private void updateMenuBlockContents() {
    343                    textArea.setText(newTextContainer.getText());
    344                    menuBlockContents.clear();
    345                    List<MenuBlockContent> newMenuBlockContents = menuCreator.createMenu(parser, newTextContainer);
    346                    for (MenuBlockContent c : newMenuBlockContents) {
    347                            if (!c.isEmpty()) {
    348                                    menuBlockContents.add(c);
    349                            }
    350                    }
    351            }
    352
    353            private void setFilter(String filter) {
    354                    for (MenuBlockContent c : menuBlockContents) {
    355                            c.setFilter(filter);
    356                    }
    357            }
    358
    359            private void handleTextInput() {
    360                    addText(textField.getText());
    361            }
    362
    363            private void handleTextInput(ArrayList<String> tokens) {
    364
    365                    TextElement t = null;
    366                    String s = "";
    367                    int c = 0;
    368
    369                    for (String token : tokens) {
    370                            setFilter(s + token);
    371                            TextElement t2 = getPossibleNextToken(s + token);
    372                            if (t2 != null) {
    373                                    t = t2;
    374                                    c++;
    375                                    break;
    376                            }
    377                            if (getElementsCount() == 0) {
    378                                    break;
    379                            } else {
    380                                    s += token + " ";
    381                                    c++;
    382                            }
    383                    }
    384
    385                    setFilter(null);
    386
    387                    if (t != null) {
    388                            textElementSelected(t);
    389                            updateMenuBlockContents();
    390                            for (int i=0; i<c; i++) tokens.remove(0);
    391                            handleTextInput(tokens);
    392                    } else {
    393                            String text = "";
    394                            for (String token : tokens) {
    395                                    if (text.length() > 0) text += " ";
    396                                    text += token;
    397                            }
    398                            textField.setText(text);
    399                    }
    400            }
    401
    402            private ArrayList<String> tokenize(String text) {
    403                    text = text.replaceAll("\\.", " . ");
    404                    text = text.replaceAll("\\?", " ? ");
    405                    text = text.replaceAll("\\!", " ! ");
    406
    407                    ArrayList<String> tokens = new ArrayList<String>(Arrays.asList(text.split(" ")));
    408
    409                    while (tokens.contains("")) {
    410                            tokens.remove("");
    411                    }
    412                    return tokens;
    413            }
    414
    415            /**
    416             * Adds a new action-listener.
    417             *
    418             * @param actionListener The new action-listener.
    419             */
    420            public void addActionListener(ActionListener actionListener) {
    421                    actionListeners.add(actionListener);
    422            }
    423
    424            /**
    425             * Removes the action-listener.
    426             *
    427             * @param actionListener The action-listener to be removed.
    428             */
    429            public void removeActionListener(ActionListener actionListener) {
    430                    actionListeners.remove(actionListener);
    431            }
    432
    433            /**
    434             * Removes all action-listeners.
    435             */
    436            public void removeAllActionListeners() {
    437                    actionListeners.clear();
    438            }
    439
    440            private void notifyActionListeners(ActionEvent event) {
    441                    for (ActionListener al : actionListeners) {
    442                            al.actionPerformed(event);
    443                    }
    444            }
    445
    446            public void actionPerformed(ActionEvent e) {
    447                    if (e.getSource() == cancelButton) {
    448                            log("edit", "pressed: cancel");
    449                            notifyActionListeners(new ActionEvent(this, "Cancel"));
    450                            return;
    451                    } else if (e.getSource() == okButton) {
    452                            log("edit", "pressed: ok");
    453                            handleTextInput();
    454                            update();
    455                            notifyActionListeners(new ActionEvent(this, "OK"));
    456                            return;
    457                    } else if (e.getSource() == deleteButton) {
    458                            log("edit", "pressed: < delete");
    459                            newTextContainer.removeLastElement();
    460                            parser.removeToken();
    461                            textField.setText("");
    462                    } else if (e.getSource() instanceof MenuEntry) {
    463                            TextElement te = ((MenuEntry) e.getSource()).getTextElement();
    464                            log("edit", "pressed: menu-entry " + te.getText());
    465                            textElementSelected(te);
    466                            textField.setText("");
    467                    } else if (e.getSource() == textField) {
    468                            log("edit", "pressed: enter-key");
    469                            handleTextInput();
    470                    } else if ("Tab".equals(e.getActionCommand())) {
    471                            log("edit", "pressed: tab-key");
    472                            handleTextInput();
    473                    }
    474
    475                    update();
    476
    477                    if ("Tab".equals(e.getActionCommand())) {
    478                            String s = getStartString();
    479                            if (s != null) textField.setText(s);
    480                    }
    481            }
    482
    483            private void log(String type, String text) {
    484                    // No logging done at the moment
    485            }
    486
    487            public String toString() {
    488                    return "sentence: " + newTextContainer.getText();
    489            }
    490
    491    }