blob: d2a6d081fd8978d792023b1f9a487bdd87da08c8 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1998-2006 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing.text.html;
26
27import java.net.*;
28import java.io.*;
29import java.awt.*;
30import java.awt.event.*;
31import java.util.*;
32import javax.swing.*;
33import javax.swing.event.*;
34import 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 */
107public 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 StringBuffer dataBuffer = new StringBuffer();
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 StringBuffer dataBuffer = new StringBuffer();
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 void getFormData(StringBuffer 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, StringBuffer 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 try {
698 Reader reader = new BufferedReader(new FileReader(path));
699 StringBuffer buffer = new StringBuffer();
700 char[] cBuff = new char[1024];
701 int read;
702
703 try {
704 while ((read = reader.read(cBuff)) != -1) {
705 buffer.append(cBuff, 0, read);
706 }
707 } catch (IOException ioe) {
708 buffer = null;
709 }
710 try {
711 reader.close();
712 } catch (IOException ioe) {}
713 if (buffer != null) {
714 value = buffer.toString();
715 }
716 } catch (IOException ioe) {}
717*/
718 }
719 }
720 return value;
721 }
722
723 /**
724 * Returns the data associated with the &lt;TEXTAREA&gt; form
725 * element. This is done by getting the text stored in the
726 * Document model.
727 */
728 private String getTextAreaData(AttributeSet attr) {
729 Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
730 try {
731 return doc.getText(0, doc.getLength());
732 } catch (BadLocationException e) {
733 return null;
734 }
735 }
736
737
738 /**
739 * Loads the buffer with the data associated with the Select
740 * form element. Basically, only items that are selected
741 * and have their name attribute set are added to the buffer.
742 */
743 private void loadSelectData(AttributeSet attr, StringBuffer buffer) {
744
745 String name = (String)attr.getAttribute(HTML.Attribute.NAME);
746 if (name == null) {
747 return;
748 }
749 Object m = attr.getAttribute(StyleConstants.ModelAttribute);
750 if (m instanceof OptionListModel) {
751 OptionListModel model = (OptionListModel)m;
752
753 for (int i = 0; i < model.getSize(); i++) {
754 if (model.isSelectedIndex(i)) {
755 Option option = (Option) model.getElementAt(i);
756 appendBuffer(buffer, name, option.getValue());
757 }
758 }
759 } else if (m instanceof ComboBoxModel) {
760 ComboBoxModel model = (ComboBoxModel)m;
761 Option option = (Option)model.getSelectedItem();
762 if (option != null) {
763 appendBuffer(buffer, name, option.getValue());
764 }
765 }
766 }
767
768 /**
769 * Appends name / value pairs into the
770 * buffer. Both names and values are encoded using the
771 * URLEncoder.encode() method before being added to the
772 * buffer.
773 */
774 private void appendBuffer(StringBuffer buffer, String name, String value) {
775 if (buffer.length() > 0) {
776 buffer.append('&');
777 }
778 String encodedName = URLEncoder.encode(name);
779 buffer.append(encodedName);
780 buffer.append('=');
781 String encodedValue = URLEncoder.encode(value);
782 buffer.append(encodedValue);
783 }
784
785 /**
786 * Returns true if the Element <code>elem</code> represents a control.
787 */
788 private boolean isControl(Element elem) {
789 return elem.isLeaf();
790 }
791
792 /**
793 * Iterates over the element hierarchy to determine if
794 * the element parameter, which is assumed to be an
795 * &lt;INPUT&gt; element of type password or text, is the last
796 * one of either kind, in the form to which it belongs.
797 */
798 boolean isLastTextOrPasswordField() {
799 Element parent = getFormElement();
800 Element elem = getElement();
801
802 if (parent != null) {
803 ElementIterator it = new ElementIterator(parent);
804 Element next;
805 boolean found = false;
806
807 while ((next = it.next()) != null) {
808 if (next == elem) {
809 found = true;
810 }
811 else if (found && isControl(next)) {
812 AttributeSet elemAttr = next.getAttributes();
813
814 if (HTMLDocument.matchNameAttribute
815 (elemAttr, HTML.Tag.INPUT)) {
816 String type = (String)elemAttr.getAttribute
817 (HTML.Attribute.TYPE);
818
819 if ("text".equals(type) || "password".equals(type)) {
820 return false;
821 }
822 }
823 }
824 }
825 }
826 return true;
827 }
828
829 /**
830 * Resets the form
831 * to its initial state by reinitializing the models
832 * associated with each form element to their initial
833 * values.
834 *
835 * param elem the element that triggered the reset
836 */
837 void resetForm() {
838 Element parent = getFormElement();
839
840 if (parent != null) {
841 ElementIterator it = new ElementIterator(parent);
842 Element next;
843
844 while((next = it.next()) != null) {
845 if (isControl(next)) {
846 AttributeSet elemAttr = next.getAttributes();
847 Object m = elemAttr.getAttribute(StyleConstants.
848 ModelAttribute);
849 if (m instanceof TextAreaDocument) {
850 TextAreaDocument doc = (TextAreaDocument)m;
851 doc.reset();
852 } else if (m instanceof PlainDocument) {
853 try {
854 PlainDocument doc = (PlainDocument)m;
855 doc.remove(0, doc.getLength());
856 if (HTMLDocument.matchNameAttribute
857 (elemAttr, HTML.Tag.INPUT)) {
858 String value = (String)elemAttr.
859 getAttribute(HTML.Attribute.VALUE);
860 if (value != null) {
861 doc.insertString(0, value, null);
862 }
863 }
864 } catch (BadLocationException e) {
865 }
866 } else if (m instanceof OptionListModel) {
867 OptionListModel model = (OptionListModel) m;
868 int size = model.getSize();
869 for (int i = 0; i < size; i++) {
870 model.removeIndexInterval(i, i);
871 }
872 BitSet selectionRange = model.getInitialSelection();
873 for (int i = 0; i < selectionRange.size(); i++) {
874 if (selectionRange.get(i)) {
875 model.addSelectionInterval(i, i);
876 }
877 }
878 } else if (m instanceof OptionComboBoxModel) {
879 OptionComboBoxModel model = (OptionComboBoxModel) m;
880 Option option = model.getInitialSelection();
881 if (option != null) {
882 model.setSelectedItem(option);
883 }
884 } else if (m instanceof JToggleButton.ToggleButtonModel) {
885 boolean checked = ((String)elemAttr.getAttribute
886 (HTML.Attribute.CHECKED) != null);
887 JToggleButton.ToggleButtonModel model =
888 (JToggleButton.ToggleButtonModel)m;
889 model.setSelected(checked);
890 }
891 }
892 }
893 }
894 }
895
896
897 /**
898 * BrowseFileAction is used for input type == file. When the user
899 * clicks the button a JFileChooser is brought up allowing the user
900 * to select a file in the file system. The resulting path to the selected
901 * file is set in the text field (actually an instance of Document).
902 */
903 private class BrowseFileAction implements ActionListener {
904 private AttributeSet attrs;
905 private Document model;
906
907 BrowseFileAction(AttributeSet attrs, Document model) {
908 this.attrs = attrs;
909 this.model = model;
910 }
911
912 public void actionPerformed(ActionEvent ae) {
913 // PENDING: When mime support is added to JFileChooser use the
914 // accept value of attrs.
915 JFileChooser fc = new JFileChooser();
916 fc.setMultiSelectionEnabled(false);
917 if (fc.showOpenDialog(getContainer()) ==
918 JFileChooser.APPROVE_OPTION) {
919 File selected = fc.getSelectedFile();
920
921 if (selected != null) {
922 try {
923 if (model.getLength() > 0) {
924 model.remove(0, model.getLength());
925 }
926 model.insertString(0, selected.getPath(), null);
927 } catch (BadLocationException ble) {}
928 }
929 }
930 }
931 }
932}