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