org.xhtmlrenderer.simple.extend.form.SelectField Maven / Gradle / Ivy
Show all versions of flying-saucer-core Show documentation
/*
* {{{ header & license
* Copyright (c) 2007 Sean Bright
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
* }}}
*/
package org.xhtmlrenderer.simple.extend.form;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xhtmlrenderer.layout.LayoutContext;
import org.xhtmlrenderer.render.BlockBox;
import org.xhtmlrenderer.simple.extend.XhtmlForm;
import org.xhtmlrenderer.util.GeneralUtil;
class SelectField extends FormField {
public SelectField(Element e, XhtmlForm form, LayoutContext context, BlockBox box) {
super(e, form, context, box);
}
public JComponent create() {
List optionList = createList();
// Either a select list or a drop down/combobox
if (shouldRenderAsList()) {
JList select = new JList(optionList.toArray());
applyComponentStyle(select);
select.setCellRenderer(new CellRenderer());
select.addListSelectionListener(new HeadingItemListener());
if (hasAttribute("multiple") && getAttribute("multiple").equalsIgnoreCase("true")) {
select.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
} else {
select.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
int size = 0;
if (hasAttribute("size")) {
size = GeneralUtil.parseIntRelaxed(getAttribute("size"));
}
if (size == 0) {
// Default to the number of items in the list or 20 - whichever is less
select.setVisibleRowCount(Math.min(select.getModel().getSize(), 20));
} else {
select.setVisibleRowCount(size);
}
return new JScrollPane(select);
} else {
JComboBox select = new JComboBox(optionList.toArray());
applyComponentStyle(select);
select.setEditable(false);
select.setRenderer(new CellRenderer());
select.addItemListener(new HeadingItemListener());
return select;
}
}
protected FormFieldState loadOriginalState() {
ArrayList list = new ArrayList();
NodeList options = getElement().getElementsByTagName("option");
for (int i = 0; i < options.getLength(); i++) {
Element option = (Element) options.item(i);
if (option.hasAttribute("selected") && option.getAttribute("selected").equalsIgnoreCase("selected")) {
list.add(new Integer(i));
}
}
return FormFieldState.fromList(list);
}
protected void applyOriginalState() {
if (shouldRenderAsList()) {
JList select = (JList) ((JScrollPane) getComponent()).getViewport().getView();
select.setSelectedIndices(getOriginalState().getSelectedIndices());
} else {
JComboBox select = (JComboBox) getComponent();
// This looks strange, but basically since this is a single select, and
// someone might have put selected="selected" on more than a single option
// I believe that the correct play here is to select the _last_ option with
// that attribute.
int [] indices = getOriginalState().getSelectedIndices();
if (indices.length == 0) {
select.setSelectedIndex(0);
} else {
select.setSelectedIndex(indices[indices.length - 1]);
}
}
}
protected String[] getFieldValues() {
if (shouldRenderAsList()) {
JList select = (JList) ((JScrollPane) getComponent()).getViewport().getView();
Object [] selectedValues = select.getSelectedValues();
String [] submitValues = new String [selectedValues.length];
for (int i = 0; i < selectedValues.length; i++) {
NameValuePair pair = (NameValuePair) selectedValues[i];
if (pair.getValue()!=null)
submitValues[i] = pair.getValue();
}
return submitValues;
} else {
JComboBox select = (JComboBox) getComponent();
NameValuePair selectedValue = (NameValuePair) select.getSelectedItem();
if (selectedValue != null) {
if (selectedValue.getValue()!=null)
return new String [] { selectedValue.getValue() };
}
}
return new String [] {};
}
private List createList() {
List list = new ArrayList();
addChildren(list, getElement(), 0);
return list;
}
private void addChildren(List list, Element e, int indent) {
NodeList children = e.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (!(children.item(i) instanceof Element)) continue;
Element child = (Element) children.item(i);
if ("option".equals(child.getNodeName())) {
// option tag, add it
String optionText = XhtmlForm.collectText(child);
String optionValue = optionText;
if (child.hasAttribute("value")) {
optionValue = child.getAttribute("value");
}
list.add(new NameValuePair(optionText, optionValue, indent));
} else if ("optgroup".equals(child.getNodeName())) {
// optgroup tag, append heading and indent children
String titleText = child.getAttribute("label");
list.add(new NameValuePair(titleText, null, indent));
addChildren(list, child, indent+1);
}
}
}
private boolean shouldRenderAsList() {
boolean result = false;
if (hasAttribute("multiple") && getAttribute("multiple").equalsIgnoreCase("true")) {
result = true;
} else if (hasAttribute("size")) {
int size = GeneralUtil.parseIntRelaxed(getAttribute("size"));
if (size > 0) {
result = true;
}
}
return result;
}
/**
* Provides a simple container for name/value data, such as that used
* by the <option> elements in a <select> list.
*
* When the value is {@code null}, this pair is used as a heading and
* should not be selected by itself.
*
* The indent property was added to support indentation of items as
* children below headings.
*/
private static class NameValuePair {
private String _name;
private String _value;
private int _indent;
public NameValuePair(String name, String value, int indent) {
_name = name;
_value = value;
_indent = indent;
}
public String getName() {
return _name;
}
public String getValue() {
return _value;
}
public int getIndent() {
return _indent;
}
public String toString() {
String txt = getName();
for (int i = 0; i < getIndent(); i++)
txt = " " + txt;
return txt;
}
}
/**
* Renderer for ordinary items and headings in a List.
*/
private static class CellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
NameValuePair pair = (NameValuePair)value;
if (pair!=null && pair.getValue()==null) {
// render as heading as such
super.getListCellRendererComponent(list, value, index, false, false);
Font fold = getFont();
Font fnew = new Font(fold.getName(), Font.BOLD | Font.ITALIC, fold.getSize());
setFont(fnew);
} else {
// other items as usuall
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
}
return this;
}
}
/**
* Helper class that makes headings inside a list unselectable
*
* This is an {@linkplain ItemListener} for comboboxes, and a
* {@linkplain ListSelectionListener} for lists.
*/
private static class HeadingItemListener implements ItemListener, ListSelectionListener {
private Object oldSelection = null;
private int[] oldSelections = new int[0];
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED)
return;
// only for comboboxes
if (! (e.getSource() instanceof JComboBox) )
return;
JComboBox combo = (JComboBox)e.getSource();
if (((NameValuePair)e.getItem()).getValue() == null) {
// header selected: revert to old selection
combo.setSelectedItem(oldSelection);
} else {
// store old selection
oldSelection = e.getItem();
}
}
public void valueChanged(ListSelectionEvent e) {
// only for lists
if (! (e.getSource() instanceof JList) )
return;
JList list = (JList)e.getSource();
ListModel model = list.getModel();
// deselect all headings
for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
if (!list.isSelectedIndex(i)) continue;
NameValuePair pair = (NameValuePair) model.getElementAt(i);
if ( pair!=null && pair.getValue() == null) {
// We have a heading, remove it. As this handler is called
// as a result of the resulting removal and we do process
// the events while the value is adjusting, we don't need
// to process any other headings here.
// BUT if there'll be no selection anymore because by selecting
// this one the old selection was cleared, restore the old
// selection.
if (list.getSelectedIndices().length==1)
list.setSelectedIndices(oldSelections);
else
list.removeSelectionInterval(i, i);
return;
}
}
// if final selection: store it
if (!e.getValueIsAdjusting())
oldSelections = list.getSelectedIndices();
}
}
}