/*
* Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text.html;
import java.net.*;
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
/**
* Component decorator that implements the view interface
* for form elements, <input>, <textarea>,
* and <select>. The model for the component is stored
* as an attribute of the the element (using StyleConstants.ModelAttribute),
* and is used to build the component of the view. The type
* of the model is assumed to of the type that would be set by
* HTMLDocument.HTMLReader.FormAction
. If there are
* multiple views mapped over the document, they will share the
* embedded component models.
*
* The following table shows what components get built * by this view. *
Element Type | *Component built | *
---|---|
input, type button | *JButton | *
input, type checkbox | *JCheckBox | *
input, type image | *JButton | *
input, type password | *JPasswordField | *
input, type radio | *JRadioButton | *
input, type reset | *JButton | *
input, type submit | *JButton | *
input, type text | *JTextField | *
select, size > 1 or multiple attribute defined | *JList in a JScrollPane | *
select, size unspecified or 1 | *JComboBox | *
textarea | *JTextArea in a JScrollPane | *
input, type file | *JTextField | *
FORM
.
*/
private Element getFormElement() {
Element elem = getElement();
while (elem != null) {
if (elem.getAttributes().getAttribute
(StyleConstants.NameAttribute) == HTML.Tag.FORM) {
return elem;
}
elem = elem.getParentElement();
}
return null;
}
/**
* Iterates over the
* element hierarchy, extracting data from the
* models associated with the relevant form elements.
* "Relevant" means the form elements that are part
* of the same form whose element triggered the submit
* action.
*
* @param buffer the buffer that contains that data to submit
* @param targetElement the element that triggered the
* form submission
*/
private void getFormData(StringBuilder buffer) {
Element formE = getFormElement();
if (formE != null) {
ElementIterator it = new ElementIterator(formE);
Element next;
while ((next = it.next()) != null) {
if (isControl(next)) {
String type = (String)next.getAttributes().getAttribute
(HTML.Attribute.TYPE);
if (type != null && type.equals("submit") &&
next != getElement()) {
// do nothing - this submit isnt the trigger
} else if (type == null || !type.equals("image")) {
// images only result in data if they triggered
// the submit and they require that the mouse click
// coords be appended to the data. Hence its
// processing is handled by the view.
loadElementDataIntoBuffer(next, buffer);
}
}
}
}
}
/**
* Loads the data
* associated with the element into the buffer.
* The format in which data is appended depends
* on the type of the form element. Essentially
* data is loaded in name/value pairs.
*
*/
private void loadElementDataIntoBuffer(Element elem, StringBuilder buffer) {
AttributeSet attr = elem.getAttributes();
String name = (String)attr.getAttribute(HTML.Attribute.NAME);
if (name == null) {
return;
}
String value = null;
HTML.Tag tag = (HTML.Tag)elem.getAttributes().getAttribute
(StyleConstants.NameAttribute);
if (tag == HTML.Tag.INPUT) {
value = getInputElementData(attr);
} else if (tag == HTML.Tag.TEXTAREA) {
value = getTextAreaData(attr);
} else if (tag == HTML.Tag.SELECT) {
loadSelectData(attr, buffer);
}
if (name != null && value != null) {
appendBuffer(buffer, name, value);
}
}
/**
* Returns the data associated with an <INPUT> form
* element. The value of "type" attributes is
* used to determine the type of the model associated
* with the element and then the relevant data is
* extracted.
*/
private String getInputElementData(AttributeSet attr) {
Object model = attr.getAttribute(StyleConstants.ModelAttribute);
String type = (String) attr.getAttribute(HTML.Attribute.TYPE);
String value = null;
if (type.equals("text") || type.equals("password")) {
Document doc = (Document)model;
try {
value = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
value = null;
}
} else if (type.equals("submit") || type.equals("hidden")) {
value = (String) attr.getAttribute(HTML.Attribute.VALUE);
if (value == null) {
value = "";
}
} else if (type.equals("radio") || type.equals("checkbox")) {
ButtonModel m = (ButtonModel)model;
if (m.isSelected()) {
value = (String) attr.getAttribute(HTML.Attribute.VALUE);
if (value == null) {
value = "on";
}
}
} else if (type.equals("file")) {
Document doc = (Document)model;
String path;
try {
path = doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
path = null;
}
if (path != null && path.length() > 0) {
value = path;
}
}
return value;
}
/**
* Returns the data associated with the <TEXTAREA> form
* element. This is done by getting the text stored in the
* Document model.
*/
private String getTextAreaData(AttributeSet attr) {
Document doc = (Document)attr.getAttribute(StyleConstants.ModelAttribute);
try {
return doc.getText(0, doc.getLength());
} catch (BadLocationException e) {
return null;
}
}
/**
* Loads the buffer with the data associated with the Select
* form element. Basically, only items that are selected
* and have their name attribute set are added to the buffer.
*/
private void loadSelectData(AttributeSet attr, StringBuilder buffer) {
String name = (String)attr.getAttribute(HTML.Attribute.NAME);
if (name == null) {
return;
}
Object m = attr.getAttribute(StyleConstants.ModelAttribute);
if (m instanceof OptionListModel) {
OptionListModel model = (OptionListModel)m;
for (int i = 0; i < model.getSize(); i++) {
if (model.isSelectedIndex(i)) {
Option option = (Option) model.getElementAt(i);
appendBuffer(buffer, name, option.getValue());
}
}
} else if (m instanceof ComboBoxModel) {
ComboBoxModel model = (ComboBoxModel)m;
Option option = (Option)model.getSelectedItem();
if (option != null) {
appendBuffer(buffer, name, option.getValue());
}
}
}
/**
* Appends name / value pairs into the
* buffer. Both names and values are encoded using the
* URLEncoder.encode() method before being added to the
* buffer.
*/
private void appendBuffer(StringBuilder buffer, String name, String value) {
if (buffer.length() > 0) {
buffer.append('&');
}
String encodedName = URLEncoder.encode(name);
buffer.append(encodedName);
buffer.append('=');
String encodedValue = URLEncoder.encode(value);
buffer.append(encodedValue);
}
/**
* Returns true if the Element elem
represents a control.
*/
private boolean isControl(Element elem) {
return elem.isLeaf();
}
/**
* Iterates over the element hierarchy to determine if
* the element parameter, which is assumed to be an
* <INPUT> element of type password or text, is the last
* one of either kind, in the form to which it belongs.
*/
boolean isLastTextOrPasswordField() {
Element parent = getFormElement();
Element elem = getElement();
if (parent != null) {
ElementIterator it = new ElementIterator(parent);
Element next;
boolean found = false;
while ((next = it.next()) != null) {
if (next == elem) {
found = true;
}
else if (found && isControl(next)) {
AttributeSet elemAttr = next.getAttributes();
if (HTMLDocument.matchNameAttribute
(elemAttr, HTML.Tag.INPUT)) {
String type = (String)elemAttr.getAttribute
(HTML.Attribute.TYPE);
if ("text".equals(type) || "password".equals(type)) {
return false;
}
}
}
}
}
return true;
}
/**
* Resets the form
* to its initial state by reinitializing the models
* associated with each form element to their initial
* values.
*
* param elem the element that triggered the reset
*/
void resetForm() {
Element parent = getFormElement();
if (parent != null) {
ElementIterator it = new ElementIterator(parent);
Element next;
while((next = it.next()) != null) {
if (isControl(next)) {
AttributeSet elemAttr = next.getAttributes();
Object m = elemAttr.getAttribute(StyleConstants.
ModelAttribute);
if (m instanceof TextAreaDocument) {
TextAreaDocument doc = (TextAreaDocument)m;
doc.reset();
} else if (m instanceof PlainDocument) {
try {
PlainDocument doc = (PlainDocument)m;
doc.remove(0, doc.getLength());
if (HTMLDocument.matchNameAttribute
(elemAttr, HTML.Tag.INPUT)) {
String value = (String)elemAttr.
getAttribute(HTML.Attribute.VALUE);
if (value != null) {
doc.insertString(0, value, null);
}
}
} catch (BadLocationException e) {
}
} else if (m instanceof OptionListModel) {
OptionListModel model = (OptionListModel) m;
int size = model.getSize();
for (int i = 0; i < size; i++) {
model.removeIndexInterval(i, i);
}
BitSet selectionRange = model.getInitialSelection();
for (int i = 0; i < selectionRange.size(); i++) {
if (selectionRange.get(i)) {
model.addSelectionInterval(i, i);
}
}
} else if (m instanceof OptionComboBoxModel) {
OptionComboBoxModel model = (OptionComboBoxModel) m;
Option option = model.getInitialSelection();
if (option != null) {
model.setSelectedItem(option);
}
} else if (m instanceof JToggleButton.ToggleButtonModel) {
boolean checked = ((String)elemAttr.getAttribute
(HTML.Attribute.CHECKED) != null);
JToggleButton.ToggleButtonModel model =
(JToggleButton.ToggleButtonModel)m;
model.setSelected(checked);
}
}
}
}
}
/**
* BrowseFileAction is used for input type == file. When the user
* clicks the button a JFileChooser is brought up allowing the user
* to select a file in the file system. The resulting path to the selected
* file is set in the text field (actually an instance of Document).
*/
private class BrowseFileAction implements ActionListener {
private AttributeSet attrs;
private Document model;
BrowseFileAction(AttributeSet attrs, Document model) {
this.attrs = attrs;
this.model = model;
}
public void actionPerformed(ActionEvent ae) {
// PENDING: When mime support is added to JFileChooser use the
// accept value of attrs.
JFileChooser fc = new JFileChooser();
fc.setMultiSelectionEnabled(false);
if (fc.showOpenDialog(getContainer()) ==
JFileChooser.APPROVE_OPTION) {
File selected = fc.getSelectedFile();
if (selected != null) {
try {
if (model.getLength() > 0) {
model.remove(0, model.getLength());
}
model.insertString(0, selected.getPath(), null);
} catch (BadLocationException ble) {}
}
}
}
}
}