/* GNU GENERAL LICENSE Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either verion 3 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 General License for more details. You should have received a copy of the GNU General Public along with this program. If not, see <http://www.gnu.org/licenses/>. Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it */ package org.lobobrowser.html.control; import java.awt.ComponentOrientation; 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 java.util.List; 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.html.renderer.HtmlController; import org.lobobrowser.util.gui.WrapperLayout; import org.lobobrowser.w3c.html.HTMLOptionElement; import org.lobobrowser.w3c.html.HTMLOptionsCollection; /** * The Class InputSelectControl. */ public class InputSelectControl extends BaseInputControl { /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The combo box. */ private final JComboBox<OptionItem> comboBox; /** The list. */ private final JList<OptionItem> list; /** The list model. */ private final DefaultListModel<OptionItem> listModel; /** The Constant STATE_NONE. */ private static final int STATE_NONE = 0; /** The Constant STATE_COMBO. */ private static final int STATE_COMBO = 1; /** The Constant STATE_LIST. */ private static final int STATE_LIST = 2; /** The state. */ private int state = STATE_NONE; /** The suspend selections. */ private boolean suspendSelections = false; /** The in selection event. */ private boolean inSelectionEvent; /** * Instantiates a new input select control. * * @param modelNode * the model node */ public InputSelectControl(final HTMLBaseInputElement modelNode) { super(modelNode); this.setLayout(WrapperLayout.getInstance()); final JComboBox<OptionItem> comboBox = new JComboBox<OptionItem>(); comboBox.addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { 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 { int selectedIndex = comboBox.getSelectedIndex(); 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<OptionItem>(); final JList<OptionItem> list = new JList<OptionItem>(listModel); this.listModel = listModel; list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.addListSelectionListener(new javax.swing.event.ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting() && !suspendSelections) { boolean changed = false; inSelectionEvent = true; try { int modelSize = listModel.getSize(); for (int i = 0; i < modelSize; i++) { OptionItem item = listModel.get(i); if (item != null) { boolean oldIsSelected = item.isSelected(); 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; if (modelNode.getTitle() != null) { comboBox.setToolTipText(modelNode.getTitle()); } comboBox.setVisible(modelNode.getHidden()); comboBox.applyComponentOrientation(direction(modelNode.getDir())); comboBox.setEditable(new Boolean( modelNode.getContentEditable() == null ? "true" : modelNode .getContentEditable())); comboBox.setEnabled(!modelNode.getDisabled()); this.list = list; this.resetItemList(); } /** * Reset item list. */ private void resetItemList() { HTMLSelectElementImpl selectElement = (HTMLSelectElementImpl) this.controlElement; boolean isMultiple = selectElement.getMultiple(); if (isMultiple && (this.state != STATE_LIST)) { this.state = STATE_LIST; this.removeAll(); 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 { HTMLOptionsCollection optionElements = selectElement.getOptions(); if (this.state == STATE_COMBO) { JComboBox<OptionItem> comboBox = this.comboBox; // First determine current selected option HTMLOptionElement priorSelectedOption = null; int priorIndex = selectElement.getSelectedIndex(); if (priorIndex != -1) { int numOptions = optionElements.getLength(); for (int index = 0; index < numOptions; index++) { HTMLOptionElement option = (HTMLOptionElement) optionElements .item(index); if (index == priorIndex) { priorSelectedOption = option; } } } comboBox.removeAllItems(); OptionItem defaultItem = null; OptionItem selectedItem = null; OptionItem firstItem = null; int numOptions = optionElements.getLength(); for (int index = 0; index < numOptions; index++) { HTMLOptionElement option = (HTMLOptionElement) optionElements .item(index); if (option != null) { 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 { JList<OptionItem> list = this.list; Collection<Integer> defaultSelectedIndexes = null; Collection<Integer> selectedIndexes = null; OptionItem firstItem = null; DefaultListModel<OptionItem> listModel = this.listModel; listModel.clear(); int numOptions = optionElements.getLength(); for (int index = 0; index < numOptions; index++) { HTMLOptionElement option = (HTMLOptionElement) optionElements .item(index); 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<Integer>(); } selectedIndexes.add(new Integer(index)); } if (option.getDefaultSelected()) { if (defaultSelectedIndexes == null) { defaultSelectedIndexes = new LinkedList<Integer>(); } defaultSelectedIndexes.add(new Integer(index)); } } if ((selectedIndexes != null) && (selectedIndexes.size() != 0)) { Iterator<Integer> sii = selectedIndexes.iterator(); while (sii.hasNext()) { Integer si = sii.next(); list.addSelectionInterval(si.intValue(), si.intValue()); } } else if ((defaultSelectedIndexes != null) && (defaultSelectedIndexes.size() != 0)) { Iterator<Integer> sii = defaultSelectedIndexes.iterator(); while (sii.hasNext()) { Integer si = sii.next(); list.addSelectionInterval(si.intValue(), si.intValue()); } } } } finally { this.suspendSelections = false; } } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#reset(int, int) */ @Override public void reset(int availWidth, int availHeight) { super.reset(availWidth, availHeight); // Need to do this here in case element was incomplete // when first rendered. this.resetItemList(); } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#getValue() */ @Override public String getValue() { if (this.state == STATE_COMBO) { OptionItem item = (OptionItem) this.comboBox.getSelectedItem(); return item == null ? null : item.getValue(); } else { OptionItem item = this.list.getSelectedValue(); return item == null ? null : item.getValue(); } } /** The selected index. */ private int selectedIndex = -1; /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#getSelectedIndex() */ @Override public int getSelectedIndex() { return this.selectedIndex; } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#setSelectedIndex(int) */ @Override public void setSelectedIndex(int value) { this.selectedIndex = value; 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) { JComboBox<OptionItem> comboBox = this.comboBox; if (comboBox.getSelectedIndex() != value) { // This check is done to avoid an infinite recursion // on ItemListener. int size = comboBox.getItemCount(); if (value < size) { comboBox.setSelectedIndex(value); } } } else { JList<OptionItem> list = this.list; int[] selectedIndices = list.getSelectedIndices(); if ((selectedIndices == null) || (selectedIndices.length != 1) || (selectedIndices[0] != value)) { // This check is done to avoid an infinite recursion // on ItemListener. int size = this.listModel.getSize(); if (value < size) { list.setSelectedIndex(value); } } } } } finally { this.suspendSelections = prevSuspend; } } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#getVisibleSize() */ @Override public int getVisibleSize() { return this.comboBox.getMaximumRowCount(); } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#setVisibleSize(int) */ @Override public void setVisibleSize(int value) { this.comboBox.setMaximumRowCount(value); } /* * (non-Javadoc) * @see org.lobobrowser.html.dombl.InputContext#resetInput() */ @Override public void resetInput() { this.list.setSelectedIndex(-1); this.comboBox.setSelectedIndex(-1); } /* * (non-Javadoc) * @see org.lobobrowser.html.control.BaseInputControl#getValues() */ @Override public String[] getValues() { if (this.state == STATE_COMBO) { OptionItem item = (OptionItem) this.comboBox.getSelectedItem(); return item == null ? null : new String[] {item.getValue()}; } else { List<OptionItem> values = this.list.getSelectedValuesList(); if (values == null) { return null; } ArrayList<String> al = new ArrayList<String>(); for (int i = 0; i < values.size(); i++) { OptionItem item = values.get(i); al.add(item.getValue()); } return al.toArray(new String[0]); } } /** * The Class OptionItem. */ private static class OptionItem { /** The option. */ private final HTMLOptionElement option; /** The caption. */ private final String caption; /** * Instantiates a new option item. * * @param option * the option */ public OptionItem(HTMLOptionElement option) { this.option = option; String label = option.getLabel(); if (label == null) { this.caption = option.getText(); } else { this.caption = label; } } /** Sets the selected. * * @param value * the new selected */ public void setSelected(boolean value) { this.option.setSelected(value); } /** Checks if is selected. * * @return true, if is selected */ public boolean isSelected() { return this.option.getSelected(); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return this.caption; } /** Gets the value. * * @return the value */ public String getValue() { String value = this.option.getValue(); if (value == null) { value = this.option.getText(); } return value; } } /** * Direction. * * @param dir * the dir * @return the component orientation */ private ComponentOrientation direction(String dir) { if ("ltr".equalsIgnoreCase(dir)) { return ComponentOrientation.LEFT_TO_RIGHT; } else if ("rtl".equalsIgnoreCase(dir)) { return ComponentOrientation.RIGHT_TO_LEFT; } else { return ComponentOrientation.UNKNOWN; } } }