1 /* 2 * Copyright (c) 1998, 2014, 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 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 static final 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 @SuppressWarnings("deprecation") 603 private String getImageData(Point point) { 604 605 String mouseCoords = point.x + ":" + point.y; 606 int sep = mouseCoords.indexOf(':'); 607 String x = mouseCoords.substring(0, sep); 608 String y = mouseCoords.substring(++sep); 609 String name = (String) getElement().getAttributes().getAttribute(HTML.Attribute.NAME); 610 611 String data; 612 if (name == null || name.equals("")) { 613 data = "x="+ x +"&y="+ y; 614 } else { 615 name = URLEncoder.encode(name); 616 data = name + ".x" +"="+ x +"&"+ name +".y"+"="+ y; 617 } 618 return data; 619 } 620 621 622 /** 623 * The following methods provide functionality required to 624 * iterate over a the elements of the form and in the case 625 * of a form submission, extract the data from each model 626 * that is associated with each form element, and in the 627 * case of reset, reinitialize the each model to its 628 * initial state. 629 */ 630 631 632 /** 633 * Returns the Element representing the <code>FORM</code>. 634 */ 635 private Element getFormElement() { 636 Element elem = getElement(); 637 while (elem != null) { 638 if (elem.getAttributes().getAttribute 639 (StyleConstants.NameAttribute) == HTML.Tag.FORM) { 640 return elem; 641 } 642 elem = elem.getParentElement(); 643 } 644 return null; 645 } 646 647 /** 648 * Iterates over the 649 * element hierarchy, extracting data from the 650 * models associated with the relevant form elements. 651 * "Relevant" means the form elements that are part 652 * of the same form whose element triggered the submit 653 * action. 654 * 655 * @param buffer the buffer that contains that data to submit 656 * @param targetElement the element that triggered the 657 * form submission 658 */ 659 private void getFormData(StringBuilder buffer) { 660 Element formE = getFormElement(); 661 if (formE != null) { 662 ElementIterator it = new ElementIterator(formE); 663 Element next; 664 665 while ((next = it.next()) != null) { 666 if (isControl(next)) { 667 String type = (String)next.getAttributes().getAttribute 668 (HTML.Attribute.TYPE); 669 670 if (type != null && type.equals("submit") && 671 next != getElement()) { 672 // do nothing - this submit is not the trigger 673 } else if (type == null || !type.equals("image")) { 674 // images only result in data if they triggered 675 // the submit and they require that the mouse click 676 // coords be appended to the data. Hence its 677 // processing is handled by the view. 678 loadElementDataIntoBuffer(next, buffer); 679 } 680 } 681 } 682 } 683 } 684 685 /** 686 * Loads the data 687 * associated with the element into the buffer. 688 * The format in which data is appended depends 689 * on the type of the form element. Essentially 690 * data is loaded in name/value pairs. 691 * 692 */ 693 private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) { 694 695 AttributeSet attr = elem.getAttributes(); 696 String name = (String)attr.getAttribute(HTML.Attribute.NAME); 697 if (name == null) { 698 return; 699 } 700 String value = null; 701 HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute 702 (StyleConstants.NameAttribute); 703 704 if (tag == HTML.Tag.INPUT) { 705 value = getInputElementData(attr); 706 } else if (tag == HTML.Tag.TEXTAREA) { 707 value = getTextAreaData(attr); 708 } else if (tag == HTML.Tag.SELECT) { 709 loadSelectData(attr, buffer); 710 } 711 712 if (name != null && value != null) { 713 appendBuffer(buffer, name, value); 714 } 715 } 716 717 718 /** 719 * Returns the data associated with an <INPUT> form 720 * element. The value of "type" attributes is 721 * used to determine the type of the model associated 722 * with the element and then the relevant data is 723 * extracted. 724 */ 725 private String getInputElementData(AttributeSet attr) { 726 727 Object model = attr.getAttribute(StyleConstants.ModelAttribute); 728 String type = (String) attr.getAttribute(HTML.Attribute.TYPE); 729 String value = null; 730 731 if (type.equals("text") || type.equals("password")) { 732 Document doc = (Document)model; 733 try { 734 value = doc.getText(0, doc.getLength()); 735 } catch (BadLocationException e) { 736 value = null; 737 } 738 } else if (type.equals("submit") || type.equals("hidden")) { 739 value = (String) attr.getAttribute(HTML.Attribute.VALUE); 740 if (value == null) { 741 value = ""; 742 } 743 } else if (type.equals("radio") || type.equals("checkbox")) { 744 ButtonModel m = (ButtonModel)model; 745 if (m.isSelected()) { 746 value = (String) attr.getAttribute(HTML.Attribute.VALUE); 747 if (value == null) { 748 value = "on"; 749 } 750 } 751 } else if (type.equals("file")) { 752 Document doc = (Document)model; 753 String path; 754 755 try { 756 path = doc.getText(0, doc.getLength()); 757 } catch (BadLocationException e) { 758 path = null; 759 } 760 if (path != null && path.length() > 0) { 761 value = path; 762 } 763 } 764 return value; 765 } 766 767 /** 768 * Returns the data associated with the <TEXTAREA> form 769 * element. This is done by getting the text stored in the 770 * Document model. 771 */ 772 private String getTextAreaData(AttributeSet attr) { 773 Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute); 774 try { 775 return doc.getText(0, doc.getLength()); 776 } catch (BadLocationException e) { 777 return null; 778 } 779 } 780 781 782 /** 783 * Loads the buffer with the data associated with the Select 784 * form element. Basically, only items that are selected 785 * and have their name attribute set are added to the buffer. 786 */ 787 private void loadSelectData(AttributeSet attr, StringBuilder buffer) { 788 789 String name = (String)attr.getAttribute(HTML.Attribute.NAME); 790 if (name == null) { 791 return; 792 } 793 Object m = attr.getAttribute(StyleConstants.ModelAttribute); 794 if (m instanceof OptionListModel) { 795 @SuppressWarnings("unchecked") 796 OptionListModel<Option> model = (OptionListModel<Option>) m; 797 798 for (int i = 0; i < model.getSize(); i++) { 799 if (model.isSelectedIndex(i)) { 800 Option option = model.getElementAt(i); 801 appendBuffer(buffer, name, option.getValue()); 802 } 803 } 804 } else if (m instanceof ComboBoxModel) { 805 @SuppressWarnings("unchecked") 806 ComboBoxModel<?> model = (ComboBoxModel)m; 807 Option option = (Option)model.getSelectedItem(); 808 if (option != null) { 809 appendBuffer(buffer, name, option.getValue()); 810 } 811 } 812 } 813 814 /** 815 * Appends name / value pairs into the 816 * buffer. Both names and values are encoded using the 817 * URLEncoder.encode() method before being added to the 818 * buffer. 819 */ 820 @SuppressWarnings("deprecation") 821 private void appendBuffer(StringBuilder buffer, String name, String value) { 822 if (buffer.length() > 0) { 823 buffer.append('&'); 824 } 825 String encodedName = URLEncoder.encode(name); 826 buffer.append(encodedName); 827 buffer.append('='); 828 String encodedValue = URLEncoder.encode(value); 829 buffer.append(encodedValue); 830 } 831 832 /** 833 * Returns true if the Element <code>elem</code> represents a control. 834 */ 835 private boolean isControl(Element elem) { 836 return elem.isLeaf(); 837 } 838 839 /** 840 * Iterates over the element hierarchy to determine if 841 * the element parameter, which is assumed to be an 842 * <INPUT> element of type password or text, is the last 843 * one of either kind, in the form to which it belongs. 844 */ 845 boolean isLastTextOrPasswordField() { 846 Element parent = getFormElement(); 847 Element elem = getElement(); 848 849 if (parent != null) { 850 ElementIterator it = new ElementIterator(parent); 851 Element next; 852 boolean found = false; 853 854 while ((next = it.next()) != null) { 855 if (next == elem) { 856 found = true; 857 } 858 else if (found && isControl(next)) { 859 AttributeSet elemAttr = next.getAttributes(); 860 861 if (HTMLDocument.matchNameAttribute 862 (elemAttr, HTML.Tag.INPUT)) { 863 String type = (String)elemAttr.getAttribute 864 (HTML.Attribute.TYPE); 865 866 if ("text".equals(type) || "password".equals(type)) { 867 return false; 868 } 869 } 870 } 871 } 872 } 873 return true; 874 } 875 876 /** 877 * Resets the form 878 * to its initial state by reinitializing the models 879 * associated with each form element to their initial 880 * values. 881 * 882 * param elem the element that triggered the reset 883 */ 884 void resetForm() { 885 Element parent = getFormElement(); 886 887 if (parent != null) { 888 ElementIterator it = new ElementIterator(parent); 889 Element next; 890 891 while((next = it.next()) != null) { 892 if (isControl(next)) { 893 AttributeSet elemAttr = next.getAttributes(); 894 Object m = elemAttr.getAttribute(StyleConstants. 895 ModelAttribute); 896 if (m instanceof TextAreaDocument) { 897 TextAreaDocument doc = (TextAreaDocument)m; 898 doc.reset(); 899 } else if (m instanceof PlainDocument) { 900 try { 901 PlainDocument doc = (PlainDocument)m; 902 doc.remove(0, doc.getLength()); 903 if (HTMLDocument.matchNameAttribute 904 (elemAttr, HTML.Tag.INPUT)) { 905 String value = (String)elemAttr. 906 getAttribute(HTML.Attribute.VALUE); 907 if (value != null) { 908 doc.insertString(0, value, null); 909 } 910 } 911 } catch (BadLocationException e) { 912 } 913 } else if (m instanceof OptionListModel) { 914 @SuppressWarnings("unchecked") 915 OptionListModel<?> model = (OptionListModel) m; 916 int size = model.getSize(); 917 for (int i = 0; i < size; i++) { 918 model.removeIndexInterval(i, i); 919 } 920 BitSet selectionRange = model.getInitialSelection(); 921 for (int i = 0; i < selectionRange.size(); i++) { 922 if (selectionRange.get(i)) { 923 model.addSelectionInterval(i, i); 924 } 925 } 926 } else if (m instanceof OptionComboBoxModel) { 927 @SuppressWarnings("unchecked") 928 OptionComboBoxModel<?> model = (OptionComboBoxModel) m; 929 Option option = model.getInitialSelection(); 930 if (option != null) { 931 model.setSelectedItem(option); 932 } 933 } else if (m instanceof JToggleButton.ToggleButtonModel) { 934 boolean checked = ((String)elemAttr.getAttribute 935 (HTML.Attribute.CHECKED) != null); 936 JToggleButton.ToggleButtonModel model = 937 (JToggleButton.ToggleButtonModel)m; 938 model.setSelected(checked); 939 } 940 } 941 } 942 } 943 } 944 945 946 /** 947 * BrowseFileAction is used for input type == file. When the user 948 * clicks the button a JFileChooser is brought up allowing the user 949 * to select a file in the file system. The resulting path to the selected 950 * file is set in the text field (actually an instance of Document). 951 */ 952 private class BrowseFileAction implements ActionListener { 953 private AttributeSet attrs; 954 private Document model; 955 956 BrowseFileAction(AttributeSet attrs, Document model) { 957 this.attrs = attrs; 958 this.model = model; 959 } 960 961 public void actionPerformed(ActionEvent ae) { 962 // PENDING: When mime support is added to JFileChooser use the 963 // accept value of attrs. 964 JFileChooser fc = new JFileChooser(); 965 fc.setMultiSelectionEnabled(false); 966 if (fc.showOpenDialog(getContainer()) == 967 JFileChooser.APPROVE_OPTION) { 968 File selected = fc.getSelectedFile(); 969 970 if (selected != null) { 971 try { 972 if (model.getLength() > 0) { 973 model.remove(0, model.getLength()); 974 } 975 model.insertString(0, selected.getPath(), null); 976 } catch (BadLocationException ble) {} 977 } 978 } 979 } 980 } 981 }