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