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 }