001/* FormView.java -- A view for a variety of HTML form elements
002   Copyright (C) 2006 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.text.html;
040
041import java.awt.Component;
042import java.awt.Point;
043import java.awt.event.ActionEvent;
044import java.awt.event.ActionListener;
045import java.awt.event.MouseAdapter;
046import java.awt.event.MouseEvent;
047import java.io.IOException;
048import java.io.OutputStreamWriter;
049import java.io.PrintWriter;
050import java.net.MalformedURLException;
051import java.net.URL;
052import java.net.URLConnection;
053import java.net.URLEncoder;
054
055import javax.swing.ButtonModel;
056import javax.swing.ImageIcon;
057import javax.swing.JButton;
058import javax.swing.JCheckBox;
059import javax.swing.JComboBox;
060import javax.swing.JEditorPane;
061import javax.swing.JList;
062import javax.swing.JPasswordField;
063import javax.swing.JRadioButton;
064import javax.swing.JScrollPane;
065import javax.swing.JTextArea;
066import javax.swing.JTextField;
067import javax.swing.ListSelectionModel;
068import javax.swing.SwingUtilities;
069import javax.swing.UIManager;
070import javax.swing.event.HyperlinkEvent;
071import javax.swing.text.AttributeSet;
072import javax.swing.text.BadLocationException;
073import javax.swing.text.ComponentView;
074import javax.swing.text.Document;
075import javax.swing.text.Element;
076import javax.swing.text.ElementIterator;
077import javax.swing.text.StyleConstants;
078
079/**
080 * A View that renders HTML form elements like buttons and input fields.
081 * This is implemented as a {@link ComponentView} that creates different Swing
082 * component depending on the type and setting of the different form elements.
083 *
084 * Namely, this view creates the following components:
085 * <table>
086 * <tr><th>Element type</th><th>Swing component</th></tr>
087 * <tr><td>input, button</td><td>JButton</td></tr>
088 * <tr><td>input, checkbox</td><td>JButton</td></tr>
089 * <tr><td>input, image</td><td>JButton</td></tr>
090 * <tr><td>input, password</td><td>JButton</td></tr>
091 * <tr><td>input, radio</td><td>JButton</td></tr>
092 * <tr><td>input, reset</td><td>JButton</td></tr>
093 * <tr><td>input, submit</td><td>JButton</td></tr>
094 * <tr><td>input, text</td><td>JButton</td></tr>
095 * <tr><td>select, size > 1 or with multiple attribute</td>
096 * <td>JList in JScrollPane</td></tr>
097 * <tr><td>select, size unspecified or == 1</td><td>JComboBox</td></tr>
098 * <tr><td>textarea, text</td><td>JTextArea in JScrollPane</td></tr>
099 * <tr><td>input, file</td><td>JTextField</td></tr>
100 * </table>
101 *
102 * @author Roman Kennke (kennke@aicas.com)
103 */
104public class FormView
105  extends ComponentView
106  implements ActionListener
107{
108
109  protected class MouseEventListener
110    extends MouseAdapter
111  {
112    /**
113     * Creates a new <code>MouseEventListener</code>.
114     */
115    protected MouseEventListener()
116    {
117      // Nothing to do here.
118    }
119
120    public void mouseReleased(MouseEvent ev)
121    {
122      String data = getImageData(ev.getPoint());
123      imageSubmit(data);
124    }
125  }
126
127  /**
128   * Actually submits the form data.
129   */
130  private class SubmitThread
131    extends Thread
132  {
133    /**
134     * The submit data.
135     */
136    private String data;
137
138    /**
139     * Creates a new SubmitThread.
140     *
141     * @param d the submit data
142     */
143    SubmitThread(String d)
144    {
145      data = d;
146    }
147
148    /**
149     * Actually performs the submit.
150     */
151    public void run()
152    {
153      if (data.length() > 0)
154        {
155          final String method = getMethod();
156          final URL actionURL = getActionURL();
157          final String target = getTarget();
158          URLConnection conn;
159          final JEditorPane editor = (JEditorPane) getContainer();
160          final HTMLDocument doc = (HTMLDocument) editor.getDocument();
161          HTMLEditorKit kit = (HTMLEditorKit) editor.getEditorKit();
162          if (kit.isAutoFormSubmission())
163            {
164              try
165                {
166                  final URL url;
167                  if (method != null && method.equals("post"))
168                    {
169                      // Perform POST.
170                      url = actionURL;
171                      conn = url.openConnection();
172                      postData(conn, data);
173                    }
174                  else
175                    {
176                      // Default to GET.
177                      url = new URL(actionURL + "?" + data);
178                    }
179                  Runnable loadDoc = new Runnable()
180                  {
181                    public void run()
182                    {
183                      if (doc.isFrameDocument())
184                        {
185                          editor.fireHyperlinkUpdate(createSubmitEvent(method,
186                                                                     actionURL,
187                                                                     target));
188                        }
189                      else
190                        {
191                          try
192                          {
193                            editor.setPage(url);
194                          }
195                          catch (IOException ex)
196                          {
197                            // Oh well.
198                            ex.printStackTrace();
199                          }
200                        }
201                    }
202                  };
203                  SwingUtilities.invokeLater(loadDoc);
204                }
205              catch (MalformedURLException ex)
206                {
207                  ex.printStackTrace();
208                }
209              catch (IOException ex)
210                {
211                  ex.printStackTrace();
212                }
213            }
214          else
215            {
216              editor.fireHyperlinkUpdate(createSubmitEvent(method,actionURL,
217                                                           target));
218            }
219        }
220    }
221
222    /**
223     * Determines the submit method.
224     *
225     * @return the submit method
226     */
227    private String getMethod()
228    {
229      AttributeSet formAtts = getFormAttributes();
230      String method = null;
231      if (formAtts != null)
232        {
233          method = (String) formAtts.getAttribute(HTML.Attribute.METHOD);
234        }
235      return method;
236    }
237
238    /**
239     * Determines the action URL.
240     *
241     * @return the action URL
242     */
243    private URL getActionURL()
244    {
245      AttributeSet formAtts = getFormAttributes();
246      HTMLDocument doc = (HTMLDocument) getElement().getDocument();
247      URL url = doc.getBase();
248      if (formAtts != null)
249        {
250          String action =
251            (String) formAtts.getAttribute(HTML.Attribute.ACTION);
252          if (action != null)
253            {
254              try
255                {
256                  url = new URL(url, action);
257                }
258              catch (MalformedURLException ex)
259                {
260                  url = null;
261                }
262            }
263        }
264      return url;
265    }
266
267    /**
268     * Fetches the target attribute.
269     *
270     * @return the target attribute or _self if none is present
271     */
272    private String getTarget()
273    {
274      AttributeSet formAtts = getFormAttributes();
275      String target = null;
276      if (formAtts != null)
277        {
278          target = (String) formAtts.getAttribute(HTML.Attribute.TARGET);
279          if (target != null)
280            target = target.toLowerCase();
281        }
282      if (target == null)
283        target = "_self";
284      return target;
285    }
286
287    /**
288     * Posts the form data over the specified connection.
289     *
290     * @param conn the connection
291     */
292    private void postData(URLConnection conn, String data)
293    {
294      conn.setDoOutput(true);
295      PrintWriter out = null;
296      try
297        {
298          out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream()));
299          out.print(data);
300          out.flush();
301        }
302      catch (IOException ex)
303        {
304          // Deal with this!
305          ex.printStackTrace();
306        }
307      finally
308        {
309          if (out != null)
310            out.close();
311        }
312    }
313
314    /**
315     * Determines the attributes from the relevant form tag.
316     *
317     * @return the attributes from the relevant form tag, <code>null</code>
318     *         when there is no form tag
319     */
320    private AttributeSet getFormAttributes()
321    {
322      AttributeSet atts = null;
323      Element form = getFormElement();
324      if (form != null)
325        atts = form.getAttributes();
326      return atts;
327    }
328
329    /**
330     * Creates the submit event that should be fired.
331     *
332     * This is package private to avoid accessor methods.
333     *
334     * @param method the submit method
335     * @param actionURL the action URL
336     * @param target the target
337     *
338     * @return the submit event
339     */
340    FormSubmitEvent createSubmitEvent(String method, URL actionURL,
341                                      String target)
342    {
343      FormSubmitEvent.MethodType m = "post".equals(method)
344                                     ? FormSubmitEvent.MethodType.POST
345                                     : FormSubmitEvent.MethodType.GET;
346      return new FormSubmitEvent(FormView.this,
347                                 HyperlinkEvent.EventType.ACTIVATED,
348                                 actionURL, getElement(), target, m, data);
349    }
350  }
351
352  /**
353   * If the value attribute of an <code>&lt;input type=&quot;submit&quot;&gt>
354   * tag is not specified, then this string is used.
355   *
356   * @deprecated As of JDK1.3 the value is fetched from the UIManager property
357   *             <code>FormView.submitButtonText</code>.
358   */
359  public static final String SUBMIT =
360    UIManager.getString("FormView.submitButtonText");
361
362  /**
363   * If the value attribute of an <code>&lt;input type=&quot;reset&quot;&gt>
364   * tag is not specified, then this string is used.
365   *
366   * @deprecated As of JDK1.3 the value is fetched from the UIManager property
367   *             <code>FormView.resetButtonText</code>.
368   */
369  public static final String RESET =
370    UIManager.getString("FormView.resetButtonText");
371
372  /**
373   * If this is true, the maximum size is set to the preferred size.
374   */
375  private boolean maxIsPreferred;
376
377  /**
378   * Creates a new <code>FormView</code>.
379   *
380   * @param el the element that is displayed by this view.
381   */
382  public FormView(Element el)
383  {
384    super(el);
385  }
386
387  /**
388   * Creates the correct AWT component for rendering the form element.
389   */
390  protected Component createComponent()
391  {
392    Component comp = null;
393    Element el = getElement();
394    AttributeSet atts = el.getAttributes();
395    Object tag = atts.getAttribute(StyleConstants.NameAttribute);
396    Object model = atts.getAttribute(StyleConstants.ModelAttribute);
397    if (tag.equals(HTML.Tag.INPUT))
398      {
399        String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
400        if (type.equals("button"))
401          {
402            String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
403            JButton b = new JButton(value);
404            if (model != null)
405              {
406                b.setModel((ButtonModel) model);
407                b.addActionListener(this);
408              }
409            comp = b;
410            maxIsPreferred = true;
411          }
412        else if (type.equals("checkbox"))
413          {
414            if (model instanceof ResetableToggleButtonModel)
415              {
416                ResetableToggleButtonModel m =
417                  (ResetableToggleButtonModel) model;
418                JCheckBox c = new JCheckBox();
419                c.setModel(m);
420                comp = c;
421                maxIsPreferred = true;
422              }
423          }
424        else if (type.equals("image"))
425          {
426            String src = (String) atts.getAttribute(HTML.Attribute.SRC);
427            JButton b;
428            try
429              {
430                URL base = ((HTMLDocument) el.getDocument()).getBase();
431                URL srcURL = new URL(base, src);
432                ImageIcon icon = new ImageIcon(srcURL);
433                b = new JButton(icon);
434              }
435            catch (MalformedURLException ex)
436              {
437                b = new JButton(src);
438              }
439            if (model != null)
440              {
441                b.setModel((ButtonModel) model);
442                b.addActionListener(this);
443              }
444            comp = b;
445            maxIsPreferred = true;
446          }
447        else if (type.equals("password"))
448          {
449            int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
450                                                     -1);
451            JTextField tf = new JPasswordField();
452            if (size > 0)
453              tf.setColumns(size);
454            else
455              tf.setColumns(20);
456            if (model != null)
457              tf.setDocument((Document) model);
458            tf.addActionListener(this);
459            comp = tf;
460            maxIsPreferred = true;
461          }
462        else if (type.equals("radio"))
463          {
464            if (model instanceof ResetableToggleButtonModel)
465              {
466                ResetableToggleButtonModel m =
467                  (ResetableToggleButtonModel) model;
468                JRadioButton c = new JRadioButton();
469                c.setModel(m);
470                comp = c;
471                maxIsPreferred = true;
472              }
473          }
474        else if (type.equals("reset"))
475          {
476            String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
477            if (value == null)
478              value = UIManager.getString("FormView.resetButtonText");
479            JButton b = new JButton(value);
480            if (model != null)
481              {
482                b.setModel((ButtonModel) model);
483                b.addActionListener(this);
484              }
485            comp = b;
486            maxIsPreferred = true;
487          }
488        else if (type.equals("submit"))
489          {
490            String value = (String) atts.getAttribute(HTML.Attribute.VALUE);
491            if (value == null)
492              value = UIManager.getString("FormView.submitButtonText");
493            JButton b = new JButton(value);
494            if (model != null)
495              {
496                b.setModel((ButtonModel) model);
497                b.addActionListener(this);
498              }
499            comp = b;
500            maxIsPreferred = true;
501          }
502        else if (type.equals("text"))
503          {
504            int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
505                                                     -1);
506            JTextField tf = new JTextField();
507            if (size > 0)
508              tf.setColumns(size);
509            else
510              tf.setColumns(20);
511            if (model != null)
512              tf.setDocument((Document) model);
513            tf.addActionListener(this);
514            comp = tf;
515            maxIsPreferred = true;
516          }
517      }
518    else if (tag == HTML.Tag.TEXTAREA)
519      {
520        JTextArea textArea = new JTextArea((Document) model);
521        int rows = HTML.getIntegerAttributeValue(atts, HTML.Attribute.ROWS, 1);
522        textArea.setRows(rows);
523        int cols = HTML.getIntegerAttributeValue(atts, HTML.Attribute.COLS, 20);
524        textArea.setColumns(cols);
525        maxIsPreferred = true;
526        comp = new JScrollPane(textArea,
527                               JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
528                               JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
529      }
530    else if (tag == HTML.Tag.SELECT)
531      {
532        if (model instanceof SelectListModel)
533          {
534            SelectListModel slModel = (SelectListModel) model;
535            JList list = new JList(slModel);
536            int size = HTML.getIntegerAttributeValue(atts, HTML.Attribute.SIZE,
537                                                     1);
538            list.setVisibleRowCount(size);
539            list.setSelectionModel(slModel.getSelectionModel());
540            comp = new JScrollPane(list);
541          }
542        else if (model instanceof SelectComboBoxModel)
543          {
544            SelectComboBoxModel scbModel = (SelectComboBoxModel) model;
545            comp = new JComboBox(scbModel);
546          }
547        maxIsPreferred = true;
548      }
549    return comp;
550  }
551
552  /**
553   * Determines the maximum span for this view on the specified axis.
554   *
555   * @param axis the axis along which to determine the span
556   *
557   * @return the maximum span for this view on the specified axis
558   *
559   * @throws IllegalArgumentException if the axis is invalid
560   */
561  public float getMaximumSpan(int axis)
562  {
563    float span;
564    if (maxIsPreferred)
565      span = getPreferredSpan(axis);
566    else
567      span = super.getMaximumSpan(axis);
568    return span;
569  }
570
571  /**
572   * Processes an action from the Swing component.
573   *
574   * If the action comes from a submit button, the form is submitted by calling
575   * {@link #submitData}. In the case of a reset button, the form is reset to
576   * the original state. If the action comes from a password or text field,
577   * then the input focus is transferred to the next input element in the form,
578   * unless this text/password field is the last one, in which case the form
579   * is submitted.
580   *
581   * @param ev the action event
582   */
583  public void actionPerformed(ActionEvent ev)
584  {
585    Element el = getElement();
586    Object tag = el.getAttributes().getAttribute(StyleConstants.NameAttribute);
587    if (tag.equals(HTML.Tag.INPUT))
588      {
589        AttributeSet atts = el.getAttributes();
590        String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
591        if (type.equals("submit"))
592          submitData(getFormData());
593        else if (type.equals("reset"))
594          resetForm();
595      }
596    // FIXME: Implement the remaining actions.
597  }
598
599  /**
600   * Submits the form data. A separate thread is created to do the
601   * transmission.
602   *
603   * @param data the form data
604   */
605  protected void submitData(String data)
606  {
607    SubmitThread submitThread = new SubmitThread(data);
608    submitThread.start();
609  }
610
611  /**
612   * Submits the form data in response to a click on a
613   * <code>&lt;input type=&quot;image&quot;&gt;</code> element.
614   *
615   * @param imageData the mouse click coordinates
616   */
617  protected void imageSubmit(String imageData)
618  {
619    // FIXME: Implement this.
620  }
621
622  /**
623   * Determines the image data that should be submitted in response to a
624   * mouse click on a image. This is either 'x=<p.x>&y=<p.y>' if the name
625   * attribute of the element is null or '' or
626   * <name>.x=<p.x>&<name>.y=<p.y>' when the name attribute is not empty.
627   *
628   * @param p the coordinates of the mouseclick
629   */
630  String getImageData(Point p)
631  {
632    String name = (String) getElement().getAttributes()
633                                            .getAttribute(HTML.Attribute.NAME);
634    String data;
635    if (name == null || name.equals(""))
636      {
637        data = "x=" + p.x + "&y=" + p.y;
638      }
639    else
640      {
641        data = name + ".x=" + p.x + "&" + name + ".y=" + p.y;
642      }
643    return data;
644  }
645
646  /**
647   * Determines and returns the enclosing form element if there is any.
648   *
649   * This is package private to avoid accessor methods.
650   *
651   * @return the enclosing form element, or <code>null</code> if there is no
652   *         enclosing form element
653   */
654  Element getFormElement()
655  {
656    Element form = null;
657    Element el = getElement();
658    while (el != null && form == null)
659      {
660        AttributeSet atts = el.getAttributes();
661        if (atts.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.FORM)
662          form = el;
663        else
664          el = el.getParentElement();
665      }
666    return form;
667  }
668
669  /**
670   * Determines the form data that is about to be submitted.
671   *
672   * @return the form data
673   */
674  private String getFormData()
675  {
676    Element form = getFormElement();
677    StringBuilder b = new StringBuilder();
678    if (form != null)
679      {
680        ElementIterator i = new ElementIterator(form);
681        Element next;
682        while ((next = i.next()) != null)
683          {
684            if (next.isLeaf())
685              {
686                AttributeSet atts = next.getAttributes();
687                String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
688                if (type != null && type.equals("submit")
689                    && next != getElement())
690                  {
691                    // Skip this. This is not the actual submit trigger.
692                  }
693                else if (type == null || ! type.equals("image"))
694                  {
695                    getElementFormData(next, b);
696                  }
697              }
698          }
699      }
700    return b.toString();
701  }
702
703  /**
704   * Fetches the form data from the specified element and appends it to
705   * the data string.
706   *
707   * @param el the element from which to fetch form data
708   * @param b the data string
709   */
710  private void getElementFormData(Element el, StringBuilder b)
711  {
712    AttributeSet atts = el.getAttributes();
713    String name = (String) atts.getAttribute(HTML.Attribute.NAME);
714    if (name != null)
715      {
716        String value = null;
717        HTML.Tag tag = (HTML.Tag) atts.getAttribute(StyleConstants.NameAttribute);
718        if (tag == HTML.Tag.SELECT)
719          {
720            getSelectData(atts, b);
721          }
722        else
723          {
724            if (tag == HTML.Tag.INPUT)
725              value = getInputFormData(atts);
726            else if (tag == HTML.Tag.TEXTAREA)
727              value = getTextAreaData(atts);
728            if (name != null && value != null)
729              {
730                addData(b, name, value);
731              }
732          }
733      }
734  }
735
736  /**
737   * Fetches form data from select boxes.
738   *
739   * @param atts the attributes of the element
740   *
741   * @param b the form data string to append to
742   */
743  private void getSelectData(AttributeSet atts, StringBuilder b)
744  {
745    String name = (String) atts.getAttribute(HTML.Attribute.NAME);
746    if (name != null)
747      {
748        Object m = atts.getAttribute(StyleConstants.ModelAttribute);
749        if (m instanceof SelectListModel)
750          {
751            SelectListModel sl = (SelectListModel) m;
752            ListSelectionModel lsm = sl.getSelectionModel();
753            for (int i = 0; i < sl.getSize(); i++)
754              {
755                if (lsm.isSelectedIndex(i))
756                  {
757                    Option o = (Option) sl.getElementAt(i);
758                    addData(b, name, o.getValue());
759                  }
760              }
761          }
762        else if (m instanceof SelectComboBoxModel)
763          {
764            SelectComboBoxModel scb = (SelectComboBoxModel) m;
765            Option o = (Option) scb.getSelectedItem();
766            if (o != null)
767              addData(b, name, o.getValue());
768          }
769      }
770  }
771
772  /**
773   * Fetches form data from a textarea.
774   *
775   * @param atts the attributes
776   *
777   * @return the form data
778   */
779  private String getTextAreaData(AttributeSet atts)
780  {
781    Document doc = (Document) atts.getAttribute(StyleConstants.ModelAttribute);
782    String data;
783    try
784      {
785        data = doc.getText(0, doc.getLength());
786      }
787    catch (BadLocationException ex)
788      {
789        data = null;
790      }
791    return data;
792  }
793
794  /**
795   * Fetches form data from an input tag.
796   *
797   * @param atts the attributes from which to fetch the data
798   *
799   * @return the field value
800   */
801  private String getInputFormData(AttributeSet atts)
802  {
803    String type = (String) atts.getAttribute(HTML.Attribute.TYPE);
804    Object model = atts.getAttribute(StyleConstants.ModelAttribute);
805    String value = null;
806    if (type.equals("text") || type.equals("password"))
807      {
808        Document doc = (Document) model;
809        try
810          {
811            value = doc.getText(0, doc.getLength());
812          }
813        catch (BadLocationException ex)
814          {
815            // Sigh.
816            assert false;
817          }
818      }
819    else if (type.equals("hidden") || type.equals("submit"))
820      {
821        value = (String) atts.getAttribute(HTML.Attribute.VALUE);
822        if (value == null)
823          value = "";
824      }
825    // TODO: Implement the others. radio, checkbox and file.
826    return value;
827  }
828
829  /**
830   * Actually adds the specified data to the string. It URL encodes
831   * the name and value and handles separation of the fields.
832   *
833   * @param b the string at which the form data to be added
834   * @param name the name of the field
835   * @param value the value
836   */
837  private void addData(StringBuilder b, String name, String value)
838  {
839    if (b.length() > 0)
840      b.append('&');
841    String encName = URLEncoder.encode(name);
842    b.append(encName);
843    b.append('=');
844    String encValue = URLEncoder.encode(value);
845    b.append(encValue);
846  }
847
848  /**
849   * Resets the form data to their initial state.
850   */
851  private void resetForm()
852  {
853    Element form = getFormElement();
854    if (form != null)
855      {
856        ElementIterator iter = new ElementIterator(form);
857        Element next;
858        while ((next = iter.next()) != null)
859          {
860            if (next.isLeaf())
861              {
862                AttributeSet atts = next.getAttributes();
863                Object m = atts.getAttribute(StyleConstants.ModelAttribute);
864                if (m instanceof ResetableModel)
865                  ((ResetableModel) m).reset();
866              }
867          }
868      }
869  }
870}