001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    package lapisx.swing;
016    
017    import javax.swing.*;
018    import javax.swing.text.*;
019    import javax.swing.text.html.HTML;
020    
021    // NIY: get/set selection of SELECT element (with multiple lines visible
022    // or multi select).  The relevant model is ListModel/ListSelectionModel.
023    
024    /**
025     * This class provides access to the form fields of an HTML document
026     * displayed by a JEditorPane, so that an HTML form can be used for input
027     * in a Java program.
028     * <p>
029     * Only certain types of form fields can be accessed by the get* and set*
030     * methods in this class:
031     * <ul>  <li> text fields (input type=text|password or textarea)
032     *       <li> single-selection lists (select)
033     *       <li> checkboxes (input type=checkbox)
034     *       <li> radio buttons (input type=radio)
035     * </ul>
036     * Other form fields (such as buttons and multiple-selection lists)
037     * can be accessed by calling getFieldModel() to obtain the field's
038     * model object and accessing it directly.
039     * <p>
040     * <b>Warning:</b> form fields are not accessible until JEditorPane
041     * has finished loading the HTML page.  To initialize form
042     * fields, you should use a PropertyChangeListener that listens for
043     * the "page" property to change:
044     * <pre>
045     * final JEditorPane editor = new JEditorPane (myURL);
046     * editor.addPropertyChangeListener (new PropertyChangeListener () {
047     *     public void propertyChange (PropertyChangeEvent event) {
048     *         if ("page".equals (event.getPropertyName ()))
049     *             HTMLFormAccess access = new HTMLFormAccess (editor);
050     *             access.setStringValue (name, value); // etc.
051     *         }
052     *     });
053     * </pre>
054     */
055    public class HTMLFormAccess {
056        JEditorPane editor;
057    
058        /**
059         * Make an HTMLFormAccess object.
060         * @param editor JEditorPane containing an HTML form to access.
061         */
062        public HTMLFormAccess (JEditorPane editor) {
063            this.editor = editor;
064        }
065    
066        /**
067         * Get value of a text field or single-selection list.
068         * @param name name attribute of form field to access (case-sensitive)
069         * @return value of form field (empty string if it's a list with no selection).
070         * @throws IllegalArgumentException if no field by that name is found
071         * @throws ClassCastException if the field is found but is not a text field or single-selection list
072         */
073        public String getStringValue (String name) {
074            Object model = getFieldModel (name);
075            if (model == null)
076                throw new IllegalArgumentException ("no field named '" + name + "'");
077    
078            if (model instanceof Document) {
079                // for INPUT type=text and TEXTAREA
080                Document field = (Document) model;
081                try {
082                    return field.getText (0, field.getLength ());
083                } catch (BadLocationException e) {
084                    throw new RuntimeException (e.getMessage ()); 
085                }
086            } else if (model instanceof ComboBoxModel) {
087                // for SELECT (single selection, only 1 line visible)
088                ComboBoxModel items = (ComboBoxModel)model;
089                Object item = items.getSelectedItem ();
090                if (item == null)
091                    return "";
092                else
093                    return item.toString ();
094            } else
095                throw new ClassCastException ("field '" + name + "' is not a string model");
096        }
097    
098        /**
099         * Set value of a text field or single-selection list.
100         * @param name name attribute of form field to access (case-sensitive)
101         * @param value value to store in form field
102         * @throws IllegalArgumentException if no field by that name is found,
103         * or if the field is a single-selection list and the value is not one
104         * of the choices
105         * @throws ClassCastException if the field is found but is not a text field or single-selection list
106         */
107        public void setStringValue (String name, String value) {
108            Object model = getFieldModel (name);
109            if (model == null)
110                throw new IllegalArgumentException ("no field named '" + name + "'");
111    
112            // Replace old contents with value
113            if (model instanceof Document) {
114                // for INPUT type=text and TEXTAREA
115                Document field = (Document) model;
116                try {
117                    field.remove (0, field.getLength ());
118                } catch (BadLocationException e) {
119                }
120                
121                try {
122                    field.insertString (0, value, null);
123                } catch (BadLocationException e) {
124                }
125            }
126            else if (model instanceof ComboBoxModel) {
127                // for SELECT (single selection, only 1 line visible)
128                ComboBoxModel items = (ComboBoxModel)model;
129            found: {
130                    for (int i = 0, n = items.getSize (); i < n; ++i) {
131                        Object item = items.getElementAt (i);
132                        if (item != null && item.toString ().equals (value)) {
133                            items.setSelectedItem (item);
134                            break found;
135                        }
136                    }
137                    throw new IllegalArgumentException
138                        ("field '" + name + "' doesn't contain an option '" 
139                         + value + "'");
140                }
141            } else
142                throw new ClassCastException ("field '" + name 
143                                              + "' is not a string");
144        }
145    
146        /**
147         * Gets value of a checkbox or radio button.
148         * @param name name attribute of form field to access (case-sensitive)
149         * @return true if form field is checked, false if not
150         * @throws IllegalArgumentException if no field by that name is found
151         * @throws ClassCastException if the field is found but is not a checkbox or radio button
152         */
153        public boolean getBooleanValue (String name) {
154            Object model = getFieldModel (name);
155            if (model == null)
156                throw new IllegalArgumentException ("no field named '" + name + "'");
157    
158            if (model instanceof ButtonModel)
159                // for INPUT=checkbox | radio
160                return ((ButtonModel) model).isSelected ();
161            else
162                throw new ClassCastException ("field '" + name 
163                                              + "' is not a boolean");
164        }
165    
166        /**
167         * Set value of a checkbox or radio button.
168         * @param name name attribute of form field to access (case-sensitive)
169         * @param value value to set in form field
170         * @throws IllegalArgumentException if no field by that name is found
171         * @throws ClassCastException if the field is found but is not a checkbox or radio button
172         */
173        public void setBooleanValue (String name, boolean value) {
174            Object model = getFieldModel (name);
175            if (model == null)
176                throw new IllegalArgumentException ("no field named '" + name + "'");
177    
178            // Replace old contents with value
179            if (model instanceof ButtonModel)
180                ((ButtonModel) model).setSelected (value);
181            else
182                throw new ClassCastException ("field '" + name 
183                                              + "' is not a boolean");
184        }
185    
186        /**
187         * Get the model for a form field.
188         * @param name name attribute of form field to access (case-sensitive)
189         * @return object representing the form field's model.  For text
190         * fields, the model is a Document.  For buttons, checkboxes, and
191         * radio buttons, it is a ButtonModel.  For lists, it is a
192         * ListModel and a ListSelectionModel (implements both).
193         * @throws IllegalArgumentException if no field by that name is found
194         */
195        public Object getFieldModel (String name) {
196            Document doc = editor.getDocument ();
197    
198            Element e = findElement (doc.getDefaultRootElement (),
199                                     HTML.Attribute.NAME, name);
200            if (e == null)
201                return null;
202    
203            Object model = e.getAttributes ()
204                            .getAttribute(StyleConstants.ModelAttribute);
205            return model;
206        }
207    
208        /**
209         * Search subtree rooted at element e for element with an attribute 
210         * attr with the given value.
211         */
212        Element findElement (Element e, 
213                             HTML.Attribute attr, 
214                             String value) {
215            if (e == null)
216                return null;
217    
218            String myValue = (String)e.getAttributes ().getAttribute (attr);
219            if (value.equals (myValue))
220                return e;
221    
222            // Search through children
223            for (int i = 0, n = e.getElementCount (); i < n; ++i) {
224                Element found = findElement (e.getElement (i), attr, value);
225                if (found != null)
226                    return found;
227            }
228    
229            return null;
230        }
231    
232    }