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