1 /* 2 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing.text.html; 26 27 import java.net.*; 28 import java.io.*; 29 import java.awt.*; 30 import java.awt.event.*; 31 import java.util.*; 32 import javax.swing.*; 33 import javax.swing.event.*; 34 import javax.swing.text.*; 35 36 /** 37 * Component decorator that implements the view interface 38 * for form elements, <input>, <textarea>, 39 * and <select>. The model for the component is stored 40 * as an attribute of the the element (using StyleConstants.ModelAttribute), 41 * and is used to build the component of the view. The type 42 * of the model is assumed to of the type that would be set by 43 * <code>HTMLDocument.HTMLReader.FormAction</code>. If there are 44 * multiple views mapped over the document, they will share the 45 * embedded component models. 46 * <p> 47 * The following table shows what components get built 48 * by this view. 49 * <table summary="shows what components get built by this view"> 50 * <tr> 51 * <th>Element Type</th> 52 * <th>Component built</th> 53 * </tr> 54 * <tr> 55 * <td>input, type button</td> 56 * <td>JButton</td> 57 * </tr> 58 * <tr> 59 * <td>input, type checkbox</td> 60 * <td>JCheckBox</td> 61 * </tr> 62 * <tr> 63 * <td>input, type image</td> 64 * <td>JButton</td> 65 * </tr> 66 * <tr> 67 * <td>input, type password</td> 68 * <td>JPasswordField</td> 69 * </tr> 70 * <tr> 71 * <td>input, type radio</td> 72 * <td>JRadioButton</td> 73 * </tr> 74 * <tr> 75 * <td>input, type reset</td> 76 * <td>JButton</td> 77 * </tr> 78 * <tr> 79 * <td>input, type submit</td> 80 * <td>JButton</td> 81 * </tr> 82 * <tr> 83 * <td>input, type text</td> 84 * <td>JTextField</td> 85 * </tr> 86 * <tr> 87 * <td>select, size > 1 or multiple attribute defined</td> 88 * <td>JList in a JScrollPane</td> 89 * </tr> 90 * <tr> 91 * <td>select, size unspecified or 1</td> 92 * <td>JComboBox</td> 93 * </tr> 94 * <tr> 95 * <td>textarea</td> 96 * <td>JTextArea in a JScrollPane</td> 97 * </tr> 98 * <tr> 99 * <td>input, type file</td> 100 * <td>JTextField</td> 101 * </tr> 102 * </table> 103 * 104 * @author Timothy Prinzing 105 * @author Sunita Mani 106 */ 107 public class FormView extends ComponentView implements ActionListener { 108 109 /** 110 * If a value attribute is not specified for a FORM input element 111 * of type "submit", then this default string is used. 112 * 113 * @deprecated As of 1.3, value now comes from UIManager property 114 * FormView.submitButtonText 115 */ 116 @Deprecated 117 public static final String SUBMIT = new String("Submit Query"); 118 /** 119 * If a value attribute is not specified for a FORM input element 120 * of type "reset", then this default string is used. 121 * 122 * @deprecated As of 1.3, value comes from UIManager UIManager property 123 * FormView.resetButtonText 124 */ 125 @Deprecated 126 public static final String RESET = new String("Reset"); 127 128 /** 129 * Document attribute name for storing POST data. JEditorPane.getPostData() 130 * uses the same name, should be kept in sync. 131 */ 132 final static String PostDataProperty = "javax.swing.JEditorPane.postdata"; 133 134 /** 135 * Used to indicate if the maximum span should be the same as the 136 * preferred span. This is used so that the Component's size doesn't 137 * change if there is extra room on a line. The first bit is used for 138 * the X direction, and the second for the y direction. 139 */ 140 private short maxIsPreferred; 141 142 /** 143 * Creates a new FormView object. 144 * 145 * @param elem the element to decorate 146 */ 147 public FormView(Element elem) { 148 super(elem); 149 } 150 151 /** 152 * Create the component. This is basically a 153 * big switch statement based upon the tag type 154 * and html attributes of the associated element. 155 */ 156 protected Component createComponent() { 157 AttributeSet attr = getElement().getAttributes(); 158 HTML.Tag t = (HTML.Tag) 159 attr.getAttribute(StyleConstants.NameAttribute); 160 JComponent c = null; 161 Object model = attr.getAttribute(StyleConstants.ModelAttribute); 162 163 // Remove listeners previously registered in shared model 164 // when a new UI component is replaced. See bug 7189299. 165 removeStaleListenerForModel(model); 166 if (t == HTML.Tag.INPUT) { 167 c = createInputComponent(attr, model); 168 } else if (t == HTML.Tag.SELECT) { 169 170 if (model instanceof OptionListModel) { 171 @SuppressWarnings("unchecked") 172 JList<?> list = new JList<>((ListModel) model); 173 int size = HTML.getIntegerAttributeValue(attr, 174 HTML.Attribute.SIZE, 175 1); 176 list.setVisibleRowCount(size); 177 list.setSelectionModel((ListSelectionModel)model); 178 c = new JScrollPane(list); 179 } else { 180 @SuppressWarnings("unchecked") 181 JComboBox<?> tmp = new JComboBox<>((ComboBoxModel) model); 182 c = tmp; 183 maxIsPreferred = 3; 184 } 185 } else if (t == HTML.Tag.TEXTAREA) { 186 JTextArea area = new JTextArea((Document) model); 187 int rows = HTML.getIntegerAttributeValue(attr, 188 HTML.Attribute.ROWS, 189 1); 190 area.setRows(rows); 191 int cols = HTML.getIntegerAttributeValue(attr, 192 HTML.Attribute.COLS, 193 20); 194 maxIsPreferred = 3; 195 area.setColumns(cols); 196 c = new JScrollPane(area, 197 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 198 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 199 } 200 201 if (c != null) { 202 c.setAlignmentY(1.0f); 203 } 204 return c; 205 } 206 207 208 /** 209 * Creates a component for an <INPUT> element based on the 210 * value of the "type" attribute. 211 * 212 * @param set of attributes associated with the <INPUT> element. 213 * @param model the value of the StyleConstants.ModelAttribute 214 * @return the component. 215 */ 216 private JComponent createInputComponent(AttributeSet attr, Object model) { 217 JComponent c = null; 218 String type = (String) attr.getAttribute(HTML.Attribute.TYPE); 219 220 if (type.equals("submit") || type.equals("reset")) { 221 String value = (String) 222 attr.getAttribute(HTML.Attribute.VALUE); 223 if (value == null) { 224 if (type.equals("submit")) { 225 value = UIManager.getString("FormView.submitButtonText"); 226 } else { 227 value = UIManager.getString("FormView.resetButtonText"); 228 } 229 } 230 JButton button = new JButton(value); 231 if (model != null) { 232 button.setModel((ButtonModel)model); 233 button.addActionListener(this); 234 } 235 c = button; 236 maxIsPreferred = 3; 237 } else if (type.equals("image")) { 238 String srcAtt = (String) attr.getAttribute(HTML.Attribute.SRC); 239 JButton button; 240 try { 241 URL base = ((HTMLDocument)getElement().getDocument()).getBase(); 242 URL srcURL = new URL(base, srcAtt); 243 Icon icon = new ImageIcon(srcURL); 244 button = new JButton(icon); 245 } catch (MalformedURLException e) { 246 button = new JButton(srcAtt); 247 } 248 if (model != null) { 249 button.setModel((ButtonModel)model); 250 button.addMouseListener(new MouseEventListener()); 251 } 252 c = button; 253 maxIsPreferred = 3; 254 } else if (type.equals("checkbox")) { 255 c = new JCheckBox(); 256 if (model != null) { 257 ((JCheckBox)c).setModel((JToggleButton.ToggleButtonModel) model); 258 } 259 maxIsPreferred = 3; 260 } else if (type.equals("radio")) { 261 c = new JRadioButton(); 262 if (model != null) { 263 ((JRadioButton)c).setModel((JToggleButton.ToggleButtonModel)model); 264 } 265 maxIsPreferred = 3; 266 } else if (type.equals("text")) { 267 int size = HTML.getIntegerAttributeValue(attr, 268 HTML.Attribute.SIZE, 269 -1); 270 JTextField field; 271 if (size > 0) { 272 field = new JTextField(); 273 field.setColumns(size); 274 } 275 else { 276 field = new JTextField(); 277 field.setColumns(20); 278 } 279 c = field; 280 if (model != null) { 281 field.setDocument((Document) model); 282 } 283 field.addActionListener(this); 284 maxIsPreferred = 3; 285 } else if (type.equals("password")) { 286 JPasswordField field = new JPasswordField(); 287 c = field; 288 if (model != null) { 289 field.setDocument((Document) model); 290 } 291 int size = HTML.getIntegerAttributeValue(attr, 292 HTML.Attribute.SIZE, 293 -1); 294 field.setColumns((size > 0) ? size : 20); 295 field.addActionListener(this); 296 maxIsPreferred = 3; 297 } else if (type.equals("file")) { 298 JTextField field = new JTextField(); 299 if (model != null) { 300 field.setDocument((Document)model); 301 } 302 int size = HTML.getIntegerAttributeValue(attr, HTML.Attribute.SIZE, 303 -1); 304 field.setColumns((size > 0) ? size : 20); 305 JButton browseButton = new JButton(UIManager.getString 306 ("FormView.browseFileButtonText")); 307 Box box = Box.createHorizontalBox(); 308 box.add(field); 309 box.add(Box.createHorizontalStrut(5)); 310 box.add(browseButton); 311 browseButton.addActionListener(new BrowseFileAction( 312 attr, (Document)model)); 313 c = box; 314 maxIsPreferred = 3; 315 } 316 return c; 317 } 318 319 private void removeStaleListenerForModel(Object model) { 320 if (model instanceof DefaultButtonModel) { 321 // case of JButton whose model is DefaultButtonModel 322 // Need to remove stale ActionListener, ChangeListener and 323 // ItemListener that are instance of AbstractButton$Handler. 324 DefaultButtonModel buttonModel = (DefaultButtonModel) model; 325 String listenerClass = "javax.swing.AbstractButton$Handler"; 326 for (ActionListener listener : buttonModel.getActionListeners()) { 327 if (listenerClass.equals(listener.getClass().getName())) { 328 buttonModel.removeActionListener(listener); 329 } 330 } 331 for (ChangeListener listener : buttonModel.getChangeListeners()) { 332 if (listenerClass.equals(listener.getClass().getName())) { 333 buttonModel.removeChangeListener(listener); 334 } 335 } 336 for (ItemListener listener : buttonModel.getItemListeners()) { 337 if (listenerClass.equals(listener.getClass().getName())) { 338 buttonModel.removeItemListener(listener); 339 } 340 } 341 } else if (model instanceof AbstractListModel) { 342 // case of JComboBox and JList 343 // For JList, the stale ListDataListener is instance 344 // BasicListUI$Handler. 345 // For JComboBox, there are 2 stale ListDataListeners, which are 346 // BasicListUI$Handler and BasicComboBoxUI$Handler. 347 @SuppressWarnings("unchecked") 348 AbstractListModel<?> listModel = (AbstractListModel) model; 349 String listenerClass1 = 350 "javax.swing.plaf.basic.BasicListUI$Handler"; 351 String listenerClass2 = 352 "javax.swing.plaf.basic.BasicComboBoxUI$Handler"; 353 for (ListDataListener listener : listModel.getListDataListeners()) { 354 if (listenerClass1.equals(listener.getClass().getName()) 355 || listenerClass2.equals(listener.getClass().getName())) 356 { 357 listModel.removeListDataListener(listener); 358 } 359 } 360 } else if (model instanceof AbstractDocument) { 361 // case of JPasswordField, JTextField and JTextArea 362 // All have 2 stale DocumentListeners. 363 String listenerClass1 = 364 "javax.swing.plaf.basic.BasicTextUI$UpdateHandler"; 365 String listenerClass2 = 366 "javax.swing.text.DefaultCaret$Handler"; 367 AbstractDocument docModel = (AbstractDocument) model; 368 for (DocumentListener listener : docModel.getDocumentListeners()) { 369 if (listenerClass1.equals(listener.getClass().getName()) 370 || listenerClass2.equals(listener.getClass().getName())) 371 { 372 docModel.removeDocumentListener(listener); 373 } 374 } 375 } 376 } 377 378 /** 379 * Determines the maximum span for this view along an 380 * axis. For certain components, the maximum and preferred span are the 381 * same. For others this will return the value 382 * returned by Component.getMaximumSize along the 383 * axis of interest. 384 * 385 * @param axis may be either View.X_AXIS or View.Y_AXIS 386 * @return the span the view would like to be rendered into >= 0. 387 * Typically the view is told to render into the span 388 * that is returned, although there is no guarantee. 389 * The parent may choose to resize or break the view. 390 * @exception IllegalArgumentException for an invalid axis 391 */ 392 public float getMaximumSpan(int axis) { 393 switch (axis) { 394 case View.X_AXIS: 395 if ((maxIsPreferred & 1) == 1) { 396 super.getMaximumSpan(axis); 397 return getPreferredSpan(axis); 398 } 399 return super.getMaximumSpan(axis); 400 case View.Y_AXIS: 401 if ((maxIsPreferred & 2) == 2) { 402 super.getMaximumSpan(axis); 403 return getPreferredSpan(axis); 404 } 405 return super.getMaximumSpan(axis); 406 default: 407 break; 408 } 409 return super.getMaximumSpan(axis); 410 } 411 412 413 /** 414 * Responsible for processing the ActionEvent. 415 * If the element associated with the FormView, 416 * has a type of "submit", "reset", "text" or "password" 417 * then the action is processed. In the case of a "submit" 418 * the form is submitted. In the case of a "reset" 419 * the form is reset to its original state. 420 * In the case of "text" or "password", if the 421 * element is the last one of type "text" or "password", 422 * the form is submitted. Otherwise, focus is transferred 423 * to the next component in the form. 424 * 425 * @param evt the ActionEvent. 426 */ 427 public void actionPerformed(ActionEvent evt) { 428 Element element = getElement(); 429 StringBuilder dataBuffer = new StringBuilder(); 430 HTMLDocument doc = (HTMLDocument)getDocument(); 431 AttributeSet attr = element.getAttributes(); 432 433 String type = (String) attr.getAttribute(HTML.Attribute.TYPE); 434 435 if (type.equals("submit")) { 436 getFormData(dataBuffer); 437 submitData(dataBuffer.toString()); 438 } else if (type.equals("reset")) { 439 resetForm(); 440 } else if (type.equals("text") || type.equals("password")) { 441 if (isLastTextOrPasswordField()) { 442 getFormData(dataBuffer); 443 submitData(dataBuffer.toString()); 444 } else { 445 getComponent().transferFocus(); 446 } 447 } 448 } 449 450 451 /** 452 * This method is responsible for submitting the form data. 453 * A thread is forked to undertake the submission. 454 * 455 * @param data data to submit 456 */ 457 protected void submitData(String data) { 458 Element form = getFormElement(); 459 AttributeSet attrs = form.getAttributes(); 460 HTMLDocument doc = (HTMLDocument) form.getDocument(); 461 URL base = doc.getBase(); 462 463 String target = (String) attrs.getAttribute(HTML.Attribute.TARGET); 464 if (target == null) { 465 target = "_self"; 466 } 467 468 String method = (String) attrs.getAttribute(HTML.Attribute.METHOD); 469 if (method == null) { 470 method = "GET"; 471 } 472 method = method.toLowerCase(); 473 boolean isPostMethod = method.equals("post"); 474 if (isPostMethod) { 475 storePostData(doc, target, data); 476 } 477 478 String action = (String) attrs.getAttribute(HTML.Attribute.ACTION); 479 URL actionURL; 480 try { 481 actionURL = (action == null) 482 ? new URL(base.getProtocol(), base.getHost(), 483 base.getPort(), base.getFile()) 484 : new URL(base, action); 485 if (!isPostMethod) { 486 String query = data.toString(); 487 actionURL = new URL(actionURL + "?" + query); 488 } 489 } catch (MalformedURLException e) { 490 actionURL = null; 491 } 492 final JEditorPane c = (JEditorPane) getContainer(); 493 HTMLEditorKit kit = (HTMLEditorKit) c.getEditorKit(); 494 495 FormSubmitEvent formEvent = null; 496 if (!kit.isAutoFormSubmission() || doc.isFrameDocument()) { 497 FormSubmitEvent.MethodType methodType = isPostMethod 498 ? FormSubmitEvent.MethodType.POST 499 : FormSubmitEvent.MethodType.GET; 500 formEvent = new FormSubmitEvent( 501 FormView.this, HyperlinkEvent.EventType.ACTIVATED, 502 actionURL, form, target, methodType, data); 503 504 } 505 // setPage() may take significant time so schedule it to run later. 506 final FormSubmitEvent fse = formEvent; 507 final URL url = actionURL; 508 SwingUtilities.invokeLater(new Runnable() { 509 public void run() { 510 if (fse != null) { 511 c.fireHyperlinkUpdate(fse); 512 } else { 513 try { 514 c.setPage(url); 515 } catch (IOException e) { 516 UIManager.getLookAndFeel().provideErrorFeedback(c); 517 } 518 } 519 } 520 }); 521 } 522 523 private void storePostData(HTMLDocument doc, String target, String data) { 524 525 /* POST data is stored into the document property named by constant 526 * PostDataProperty from where it is later retrieved by method 527 * JEditorPane.getPostData(). If the current document is in a frame, 528 * the data is initially put into the toplevel (frameset) document 529 * property (named <PostDataProperty>.<Target frame name>). It is the 530 * responsibility of FrameView which updates the target frame 531 * to move data from the frameset document property into the frame 532 * document property. 533 */ 534 535 Document propDoc = doc; 536 String propName = PostDataProperty; 537 538 if (doc.isFrameDocument()) { 539 // find the top-most JEditorPane holding the frameset view. 540 FrameView.FrameEditorPane p = 541 (FrameView.FrameEditorPane) getContainer(); 542 FrameView v = p.getFrameView(); 543 JEditorPane c = v.getOutermostJEditorPane(); 544 if (c != null) { 545 propDoc = c.getDocument(); 546 propName += ("." + target); 547 } 548 } 549 550 propDoc.putProperty(propName, data); 551 } 552 553 /** 554 * MouseEventListener class to handle form submissions when 555 * an input with type equal to image is clicked on. 556 * A MouseListener is necessary since along with the image 557 * data the coordinates associated with the mouse click 558 * need to be submitted. 559 */ 560 protected class MouseEventListener extends MouseAdapter { 561 562 public void mouseReleased(MouseEvent evt) { 563 String imageData = getImageData(evt.getPoint()); 564 imageSubmit(imageData); 565 } 566 } 567 568 /** 569 * This method is called to submit a form in response 570 * to a click on an image -- an <INPUT> form 571 * element of type "image". 572 * 573 * @param imageData the mouse click coordinates. 574 */ 575 protected void imageSubmit(String imageData) { 576 577 StringBuilder dataBuffer = new StringBuilder(); 578 Element elem = getElement(); 579 HTMLDocument hdoc = (HTMLDocument)elem.getDocument(); 580 getFormData(dataBuffer); 581 if (dataBuffer.length() > 0) { 582 dataBuffer.append('&'); 583 } 584 dataBuffer.append(imageData); 585 submitData(dataBuffer.toString()); 586 return; 587 } 588 589 /** 590 * Extracts the value of the name attribute 591 * associated with the input element of type 592 * image. If name is defined it is encoded using 593 * the URLEncoder.encode() method and the 594 * image data is returned in the following format: 595 * name + ".x" +"="+ x +"&"+ name +".y"+"="+ y 596 * otherwise, 597 * "x="+ x +"&y="+ y 598 * 599 * @param point associated with the mouse click. 600 * @return the image data. 601 */ 602 private String getImageData(Point point) { 603 604 String mouseCoords = point.x + ":" + point.y; 605 int sep = mouseCoords.indexOf(':'); 606 String x = mouseCoords.substring(0, sep); 607 String y = mouseCoords.substring(++sep); 608 String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME); 609 610 String data; 611 if (name == null || name.equals("")) { 612 data = "x="+ x +"&y="+ y; 613 } else { 614 name = URLEncoder.encode(name); 615 data = name + ".x" +"="+ x +"&"+ name +".y"+"="+ y; 616 } 617 return data; 618 } 619 620 621 /** 622 * The following methods provide functionality required to 623 * iterate over a the elements of the form and in the case 624 * of a form submission, extract the data from each model 625 * that is associated with each form element, and in the 626 * case of reset, reinitialize the each model to its 627 * initial state. 628 */ 629 630 631 /** 632 * Returns the Element representing the <code>FORM</code>. 633 */ 634 private Element getFormElement() { 635 Element elem = getElement(); 636 while (elem != null) { 637 if (elem.getAttributes().getAttribute 638 (StyleConstants.NameAttribute) == HTML.Tag.FORM) { 639 return elem; 640 } 641 elem = elem.getParentElement(); 642 } 643 return null; 644 } 645 646 /** 647 * Iterates over the 648 * element hierarchy, extracting data from the 649 * models associated with the relevant form elements. 650 * "Relevant" means the form elements that are part 651 * of the same form whose element triggered the submit 652 * action. 653 * 654 * @param buffer the buffer that contains that data to submit 655 * @param targetElement the element that triggered the 656 * form submission 657 */ 658 private void getFormData(StringBuilder buffer) { 659 Element formE = getFormElement(); 660 if (formE != null) { 661 ElementIterator it = new ElementIterator(formE); 662 Element next; 663 664 while ((next = it.next()) != null) { 665 if (isControl(next)) { 666 String type = (String)next.getAttributes().getAttribute 667 (HTML.Attribute.TYPE); 668 669 if (type != null && type.equals("submit") && 670 next != getElement()) { 671 // do nothing - this submit is not the trigger 672 } else if (type == null || !type.equals("image")) { 673 // images only result in data if they triggered 674 // the submit and they require that the mouse click 675 // coords be appended to the data. Hence its 676 // processing is handled by the view. 677 loadElementDataIntoBuffer(next, buffer); 678 } 679 } 680 } 681 } 682 } 683 684 /** 685 * Loads the data 686 * associated with the element into the buffer. 687 * The format in which data is appended depends 688 * on the type of the form element. Essentially 689 * data is loaded in name/value pairs. 690 * 691 */ 692 private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) { 693 694 AttributeSet attr = elem.getAttributes(); 695 String name = (String)attr.getAttribute(HTML.Attribute.NAME); 696 if (name == null) { 697 return; 698 } 699 String value = null; 700 HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute 701 (StyleConstants.NameAttribute); 702 703 if (tag == HTML.Tag.INPUT) { 704 value = getInputElementData(attr); 705 } else if (tag == HTML.Tag.TEXTAREA) { 706 value = getTextAreaData(attr); 707 } else if (tag == HTML.Tag.SELECT) { 708 loadSelectData(attr, buffer); 709 } 710 711 if (name != null && value != null) { 712 appendBuffer(buffer, name, value); 713 } 714 } 715 716 717 /** 718 * Returns the data associated with an <INPUT> form 719 * element. The value of "type" attributes is 720 * used to determine the type of the model associated 721 * with the element and then the relevant data is 722 * extracted. 723 */ 724 private String getInputElementData(AttributeSet attr) { 725 726 Object model = attr.getAttribute(StyleConstants.ModelAttribute); 727 String type = (String) attr.getAttribute(HTML.Attribute.TYPE); 728 String value = null; 729 730 if (type.equals("text") || type.equals("password")) { 731 Document doc = (Document)model; 732 try { 733 value = doc.getText(0, doc.getLength()); 734 } catch (BadLocationException e) { 735 value = null; 736 } 737 } else if (type.equals("submit") || type.equals("hidden")) { 738 value = (String) attr.getAttribute(HTML.Attribute.VALUE); 739 if (value == null) { 740 value = ""; 741 } 742 } else if (type.equals("radio") || type.equals("checkbox")) { 743 ButtonModel m = (ButtonModel)model; 744 if (m.isSelected()) { 745 value = (String) attr.getAttribute(HTML.Attribute.VALUE); 746 if (value == null) { 747 value = "on"; 748 } 749 } 750 } else if (type.equals("file")) { 751 Document doc = (Document)model; 752 String path; 753 754 try { 755 path = doc.getText(0, doc.getLength()); 756 } catch (BadLocationException e) { 757 path = null; 758 } 759 if (path != null && path.length() > 0) { 760 value = path; 761 } 762 } 763 return value; 764 } 765 766 /** 767 * Returns the data associated with the <TEXTAREA> form 768 * element. This is done by getting the text stored in the 769 * Document model. 770 */ 771 private String getTextAreaData(AttributeSet attr) { 772 Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute); 773 try { 774 return doc.getText(0, doc.getLength()); 775 } catch (BadLocationException e) { 776 return null; 777 } 778 } 779 780 781 /** 782 * Loads the buffer with the data associated with the Select 783 * form element. Basically, only items that are selected 784 * and have their name attribute set are added to the buffer. 785 */ 786 private void loadSelectData(AttributeSet attr, StringBuilder buffer) { 787 788 String name = (String)attr.getAttribute(HTML.Attribute.NAME); 789 if (name == null) { 790 return; 791 } 792 Object m = attr.getAttribute(StyleConstants.ModelAttribute); 793 if (m instanceof OptionListModel) { 794 @SuppressWarnings("unchecked") 795 OptionListModel<Option> model = (OptionListModel<Option>) m; 796 797 for (int i = 0; i < model.getSize(); i++) { 798 if (model.isSelectedIndex(i)) { 799 Option option = model.getElementAt(i); 800 appendBuffer(buffer, name, option.getValue()); 801 } 802 } 803 } else if (m instanceof ComboBoxModel) { 804 @SuppressWarnings("unchecked") 805 ComboBoxModel<?> model = (ComboBoxModel)m; 806 Option option = (Option)model.getSelectedItem(); 807 if (option != null) { 808 appendBuffer(buffer, name, option.getValue()); 809 } 810 } 811 } 812 813 /** 814 * Appends name / value pairs into the 815 * buffer. Both names and values are encoded using the 816 * URLEncoder.encode() method before being added to the 817 * buffer. 818 */ 819 private void appendBuffer(StringBuilder buffer, String name, String value) { 820 if (buffer.length() > 0) { 821 buffer.append('&'); 822 } 823 String encodedName = URLEncoder.encode(name); 824 buffer.append(encodedName); 825 buffer.append('='); 826 String encodedValue = URLEncoder.encode(value); 827 buffer.append(encodedValue); 828 } 829 830 /** 831 * Returns true if the Element <code>elem</code> represents a control. 832 */ 833 private boolean isControl(Element elem) { 834 return elem.isLeaf(); 835 } 836 837 /** 838 * Iterates over the element hierarchy to determine if 839 * the element parameter, which is assumed to be an 840 * <INPUT> element of type password or text, is the last 841 * one of either kind, in the form to which it belongs. 842 */ 843 boolean isLastTextOrPasswordField() { 844 Element parent = getFormElement(); 845 Element elem = getElement(); 846 847 if (parent != null) { 848 ElementIterator it = new ElementIterator(parent); 849 Element next; 850 boolean found = false; 851 852 while ((next = it.next()) != null) { 853 if (next == elem) { 854 found = true; 855 } 856 else if (found && isControl(next)) { 857 AttributeSet elemAttr = next.getAttributes(); 858 859 if (HTMLDocument.matchNameAttribute 860 (elemAttr, HTML.Tag.INPUT)) { 861 String type = (String)elemAttr.getAttribute 862 (HTML.Attribute.TYPE); 863 864 if ("text".equals(type) || "password".equals(type)) { 865 return false; 866 } 867 } 868 } 869 } 870 } 871 return true; 872 } 873 874 /** 875 * Resets the form 876 * to its initial state by reinitializing the models 877 * associated with each form element to their initial 878 * values. 879 * 880 * param elem the element that triggered the reset 881 */ 882 void resetForm() { 883 Element parent = getFormElement(); 884 885 if (parent != null) { 886 ElementIterator it = new ElementIterator(parent); 887 Element next; 888 889 while((next = it.next()) != null) { 890 if (isControl(next)) { 891 AttributeSet elemAttr = next.getAttributes(); 892 Object m = elemAttr.getAttribute(StyleConstants. 893 ModelAttribute); 894 if (m instanceof TextAreaDocument) { 895 TextAreaDocument doc = (TextAreaDocument)m; 896 doc.reset(); 897 } else if (m instanceof PlainDocument) { 898 try { 899 PlainDocument doc = (PlainDocument)m; 900 doc.remove(0, doc.getLength()); 901 if (HTMLDocument.matchNameAttribute 902 (elemAttr, HTML.Tag.INPUT)) { 903 String value = (String)elemAttr. 904 getAttribute(HTML.Attribute.VALUE); 905 if (value != null) { 906 doc.insertString(0, value, null); 907 } 908 } 909 } catch (BadLocationException e) { 910 } 911 } else if (m instanceof OptionListModel) { 912 @SuppressWarnings("unchecked") 913 OptionListModel<?> model = (OptionListModel) m; 914 int size = model.getSize(); 915 for (int i = 0; i < size; i++) { 916 model.removeIndexInterval(i, i); 917 } 918 BitSet selectionRange = model.getInitialSelection(); 919 for (int i = 0; i < selectionRange.size(); i++) { 920 if (selectionRange.get(i)) { 921 model.addSelectionInterval(i, i); 922 } 923 } 924 } else if (m instanceof OptionComboBoxModel) { 925 @SuppressWarnings("unchecked") 926 OptionComboBoxModel<?> model = (OptionComboBoxModel) m; 927 Option option = model.getInitialSelection(); 928 if (option != null) { 929 model.setSelectedItem(option); 930 } 931 } else if (m instanceof JToggleButton.ToggleButtonModel) { 932 boolean checked = ((String)elemAttr.getAttribute 933 (HTML.Attribute.CHECKED) != null); 934 JToggleButton.ToggleButtonModel model = 935 (JToggleButton.ToggleButtonModel)m; 936 model.setSelected(checked); 937 } 938 } 939 } 940 } 941 } 942 943 944 /** 945 * BrowseFileAction is used for input type == file. When the user 946 * clicks the button a JFileChooser is brought up allowing the user 947 * to select a file in the file system. The resulting path to the selected 948 * file is set in the text field (actually an instance of Document). 949 */ 950 private class BrowseFileAction implements ActionListener { 951 private AttributeSet attrs; 952 private Document model; 953 954 BrowseFileAction(AttributeSet attrs, Document model) { 955 this.attrs = attrs; 956 this.model = model; 957 } 958 959 public void actionPerformed(ActionEvent ae) { 960 // PENDING: When mime support is added to JFileChooser use the 961 // accept value of attrs. 962 JFileChooser fc = new JFileChooser(); 963 fc.setMultiSelectionEnabled(false); 964 if (fc.showOpenDialog(getContainer()) == 965 JFileChooser.APPROVE_OPTION) { 966 File selected = fc.getSelectedFile(); 967 968 if (selected != null) { 969 try { 970 if (model.getLength() > 0) { 971 model.remove(0, model.getLength()); 972 } 973 model.insertString(0, selected.getPath(), null); 974 } catch (BadLocationException ble) {} 975 } 976 } 977 } 978 } 979 }