package org.lobobrowser.html.renderer;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import javax.swing.DefaultListModel;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import org.lobobrowser.html.domimpl.HTMLBaseInputElement;
import org.lobobrowser.html.domimpl.HTMLSelectElementImpl;
import org.lobobrowser.util.gui.WrapperLayout;
import org.w3c.dom.html.HTMLCollection;
import org.w3c.dom.html.HTMLOptionElement;
class InputSelectControl extends BaseInputControl {
private static final long serialVersionUID = 286101283473109265L;
private final JComboBox<OptionItem> comboBox;
private final JList<OptionItem> list;
private final DefaultListModel<OptionItem> listModel;
private boolean inSelectionEvent;
public InputSelectControl(final HTMLBaseInputElement modelNode) {
super(modelNode);
this.setLayout(WrapperLayout.getInstance());
final JComboBox<OptionItem> comboBox = new JComboBox<>();
comboBox.addItemListener(new ItemListener() {
public void itemStateChanged(final ItemEvent e) {
final OptionItem item = (OptionItem) e.getItem();
if (item != null) {
switch (e.getStateChange()) {
case ItemEvent.SELECTED:
if (!suspendSelections) {
// In this case it's better to change the
// selected index. We don't want multiple selections.
inSelectionEvent = true;
try {
final int selectedIndex = comboBox.getSelectedIndex();
final HTMLSelectElementImpl selectElement = (HTMLSelectElementImpl) modelNode;
selectElement.setSelectedIndex(selectedIndex);
} finally {
inSelectionEvent = false;
}
HtmlController.getInstance().onChange(modelNode);
}
break;
case ItemEvent.DESELECTED:
// Ignore deselection here. It must necessarily
// be followed by combo-box selection. If we deselect, that
// changes the state of the control.
break;
}
}
}
});
final DefaultListModel<OptionItem> listModel = new DefaultListModel<>();
final JList<OptionItem> list = new JList<>(listModel);
this.listModel = listModel;
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
list.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
public void valueChanged(final ListSelectionEvent e) {
if (!e.getValueIsAdjusting() && !suspendSelections) {
boolean changed = false;
inSelectionEvent = true;
try {
final int modelSize = listModel.getSize();
for (int i = 0; i < modelSize; i++) {
final OptionItem item = listModel.get(i);
if (item != null) {
final boolean oldIsSelected = item.isSelected();
final boolean newIsSelected = list.isSelectedIndex(i);
if (oldIsSelected != newIsSelected) {
changed = true;
item.setSelected(newIsSelected);
}
}
}
} finally {
inSelectionEvent = false;
}
if (changed) {
HtmlController.getInstance().onChange(modelNode);
}
}
}
});
// Note: Value attribute cannot be set in reset() method.
// Otherwise, layout revalidation causes typed values to
// be lost (including revalidation due to hover.)
this.comboBox = comboBox;
this.list = list;
this.resetItemList();
}
private static final int STATE_NONE = 0;
private static final int STATE_COMBO = 1;
private static final int STATE_LIST = 2;
private int state = STATE_NONE;
private boolean suspendSelections = false;
private void resetItemList() {
final HTMLSelectElementImpl selectElement = (HTMLSelectElementImpl) this.controlElement;
final boolean isMultiple = selectElement.getMultiple();
if (isMultiple && (this.state != STATE_LIST)) {
this.state = STATE_LIST;
this.removeAll();
final JScrollPane scrollPane = new JScrollPane(this.list);
this.add(scrollPane);
} else if (!isMultiple && (this.state != STATE_COMBO)) {
this.state = STATE_COMBO;
this.removeAll();
this.add(this.comboBox);
}
this.suspendSelections = true;
try {
final HTMLCollection optionElements = selectElement.getOptions();
if (this.state == STATE_COMBO) {
final JComboBox<OptionItem> comboBox = this.comboBox;
// First determine current selected option
HTMLOptionElement priorSelectedOption = null;
final int priorIndex = selectElement.getSelectedIndex();
if (priorIndex != -1) {
final int numOptions = optionElements.getLength();
for (int index = 0; index < numOptions; index++) {
final HTMLOptionElement option = (HTMLOptionElement) optionElements.item(index);
if (index == priorIndex) {
priorSelectedOption = option;
}
}
}
comboBox.removeAllItems();
OptionItem defaultItem = null;
OptionItem selectedItem = null;
OptionItem firstItem = null;
final int numOptions = optionElements.getLength();
for (int index = 0; index < numOptions; index++) {
final HTMLOptionElement option = (HTMLOptionElement) optionElements.item(index);
if (option != null) {
final OptionItem item = new OptionItem(option);
if (firstItem == null) {
firstItem = item;
comboBox.addItem(item);
// Undo automatic selection that occurs
// when adding the first item.
// This might set the deferred index as well.
selectElement.setSelectedIndex(-1);
if (priorSelectedOption != null) {
priorSelectedOption.setSelected(true);
}
} else {
comboBox.addItem(item);
}
if (option.getSelected()) {
selectedItem = item;
}
if (option.getDefaultSelected()) {
defaultItem = item;
}
}
}
if (selectedItem != null) {
comboBox.setSelectedItem(selectedItem);
} else if (defaultItem != null) {
comboBox.setSelectedItem(defaultItem);
} else if (firstItem != null) {
comboBox.setSelectedItem(firstItem);
}
} else {
final JList<OptionItem> list = this.list;
Collection<Integer> defaultSelectedIndexes = null;
Collection<Integer> selectedIndexes = null;
OptionItem firstItem = null;
final DefaultListModel<OptionItem> listModel = this.listModel;
listModel.clear();
final int numOptions = optionElements.getLength();
for (int index = 0; index < numOptions; index++) {
final HTMLOptionElement option = (HTMLOptionElement) optionElements.item(index);
final OptionItem item = new OptionItem(option);
if (firstItem == null) {
firstItem = item;
listModel.addElement(item);
// Do not select first item automatically.
list.setSelectedIndex(-1);
} else {
listModel.addElement(item);
}
if (option.getSelected()) {
if (selectedIndexes == null) {
selectedIndexes = new LinkedList<>();
}
selectedIndexes.add(new Integer(index));
}
if (option.getDefaultSelected()) {
if (defaultSelectedIndexes == null) {
defaultSelectedIndexes = new LinkedList<>();
}
defaultSelectedIndexes.add(new Integer(index));
}
}
if ((selectedIndexes != null) && (selectedIndexes.size() != 0)) {
final Iterator<Integer> sii = selectedIndexes.iterator();
while (sii.hasNext()) {
final Integer si = sii.next();
list.addSelectionInterval(si.intValue(), si.intValue());
}
} else if ((defaultSelectedIndexes != null) && (defaultSelectedIndexes.size() != 0)) {
final Iterator<Integer> sii = defaultSelectedIndexes.iterator();
while (sii.hasNext()) {
final Integer si = sii.next();
list.addSelectionInterval(si.intValue(), si.intValue());
}
}
}
} finally {
this.suspendSelections = false;
}
}
@Override
public void reset(final int availWidth, final int availHeight) {
super.reset(availWidth, availHeight);
// Need to do this here in case element was incomplete
// when first rendered.
this.resetItemList();
}
@Override
public String getValue() {
if (this.state == STATE_COMBO) {
final OptionItem item = (OptionItem) this.comboBox.getSelectedItem();
return item == null ? null : item.getValue();
} else {
final OptionItem item = this.list.getSelectedValue();
return item == null ? null : item.getValue();
}
}
private int selectedIndex = -1;
@Override
public int getSelectedIndex() {
return this.selectedIndex;
}
@Override
public void setSelectedIndex(final int value) {
this.selectedIndex = value;
final boolean prevSuspend = this.suspendSelections;
this.suspendSelections = true;
// Note that neither IE nor FireFox generate selection
// events when the selection is changed programmatically.
try {
if (!this.inSelectionEvent) {
if (this.state == STATE_COMBO) {
final JComboBox<OptionItem> comboBox = this.comboBox;
if (comboBox.getSelectedIndex() != value) {
// This check is done to avoid an infinite recursion
// on ItemListener.
final int size = comboBox.getItemCount();
if (value < size) {
comboBox.setSelectedIndex(value);
}
}
} else {
final JList<OptionItem> list = this.list;
final int[] selectedIndices = list.getSelectedIndices();
if ((selectedIndices == null) || (selectedIndices.length != 1) || (selectedIndices[0] != value)) {
// This check is done to avoid an infinite recursion
// on ItemListener.
final int size = this.listModel.getSize();
if (value < size) {
list.setSelectedIndex(value);
}
}
}
}
} finally {
this.suspendSelections = prevSuspend;
}
}
@Override
public int getVisibleSize() {
return this.comboBox.getMaximumRowCount();
}
@Override
public void setVisibleSize(final int value) {
this.comboBox.setMaximumRowCount(value);
}
public void resetInput() {
this.list.setSelectedIndex(-1);
this.comboBox.setSelectedIndex(-1);
}
@Override
public String[] getValues() {
if (this.state == STATE_COMBO) {
final OptionItem item = (OptionItem) this.comboBox.getSelectedItem();
return item == null ? null : new String[] { item.getValue() };
} else {
final Object[] values = this.list.getSelectedValues();
if (values == null) {
return null;
}
final ArrayList<String> al = new ArrayList<>();
for (final Object value2 : values) {
final OptionItem item = (OptionItem) value2;
al.add(item.getValue());
}
return al.toArray(new String[0]);
}
}
private static class OptionItem {
private final HTMLOptionElement option;
private final String caption;
public OptionItem(final HTMLOptionElement option) {
this.option = option;
final String label = option.getLabel();
if (label == null) {
this.caption = option.getText();
} else {
this.caption = label;
}
}
public void setSelected(final boolean value) {
this.option.setSelected(value);
}
public boolean isSelected() {
return this.option.getSelected();
}
@Override
public String toString() {
return this.caption;
}
public String getValue() {
String value = this.option.getValue();
if (value == null) {
value = this.option.getText();
}
return value;
}
}
}