       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, &lt;input&gt;, &lt;textarea&gt;,
      39  * and &lt;select&gt;.  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 &gt; 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 &lt;INPUT&gt; element based on the
     203      * value of the "type" attribute.
     204      *
     205      * @param attr set of attributes associated with the &lt;INPUT&gt; 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 &gt;= 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 &lt;INPUT&gt; 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 &lt;INPUT&gt; 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 &lt;TEXTAREA&gt; 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      * &lt;INPUT&gt; 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 }