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