/**
* Copyright (C) 2015 Valkyrie RCP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.valkyriercp.form.binding.swing;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.convert.ConversionException;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.util.Assert;
import org.valkyriercp.binding.form.FormModel;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
public class ListBinding extends AbstractListBinding {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final Object[] EMPTY_VALUES = new Object[0];
private final ListSelectionListener selectionListener = new SelectionListener();
private final PropertyChangeListener valueModelListener = new ValueModelListener();
private ConversionExecutor conversionExecutor;
boolean selectingValues;
public ListBinding(JList list, FormModel formModel, String formFieldPath, Class requiredSourceClass) {
super(list, formModel, formFieldPath, requiredSourceClass);
}
public JList getList() {
return (JList) getComponent();
}
public void setSelectionMode(int selectionMode) {
Assert.isTrue(ListSelectionModel.SINGLE_SELECTION == selectionMode || isPropertyConversionExecutorAvailable());
getList().setSelectionMode(selectionMode);
}
public int getSelectionMode() {
return getList().getSelectionMode();
}
/**
* Returns a conversion executor which converts a value of the given sourceType into the fieldType
*
* @return true if a converter is available, otherwise false
*
* @see #getPropertyType()
*/
protected ConversionExecutor getPropertyConversionExecutor() {
if (conversionExecutor == null) {
conversionExecutor = getConversionService().getConversionExecutor(Object[].class, getPropertyType());
}
return conversionExecutor;
}
protected boolean isPropertyConversionExecutorAvailable() {
try {
getConversionService().getConversionExecutor(Object[].class, getPropertyType());
} catch (IllegalArgumentException e) {
return false;
} catch (ConversionException e) {
return false;
}
return true;
}
protected void updateSelectedItemsFromSelectionModel() {
if (getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
Object singleValue = getList().getSelectedValue();
Class propertyType = getPropertyType();
if(singleValue == null || propertyType.isAssignableFrom(singleValue.getClass())) {
getValueModel().setValueSilently(singleValue, valueModelListener);
} else {
getValueModel().setValueSilently(convertValue(singleValue, propertyType), valueModelListener);
}
} else {
Object[] values = getList().getSelectedValues();
getValueModel().setValueSilently(convertSelectedValues(values), valueModelListener);
}
}
/**
* Converts the given values into a type that matches the fieldType
*
* @param selectedValues
* the selected values
* @return the value which can be assigned to the type of the field
*/
protected Object convertSelectedValues(Object[] selectedValues) {
return getPropertyConversionExecutor().execute(selectedValues);
}
protected void doBindControl(ListModel bindingModel) {
JList list = getList();
list.setModel(bindingModel);
list.getSelectionModel().addListSelectionListener(selectionListener);
getValueModel().addValueChangeListener(valueModelListener);
if (!isPropertyConversionExecutorAvailable() && getSelectionMode() != ListSelectionModel.SINGLE_SELECTION) {
if (logger.isWarnEnabled()) {
logger.warn("Selection mode for list field " + getProperty() + " forced to single selection."
+ " If multiple selection is needed use a collection type (List, Collection, Object[])"
+ " or provide a suitable converter to convert Object[] instances to property type "
+ getPropertyType());
}
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
updateSelectedItemsFromValueModel();
}
/**
* Updates the selection model with the selected values from the value model.
*/
protected void updateSelectedItemsFromValueModel() {
Object value = getValue();
Object[] selectedValues = EMPTY_VALUES;
if (value != null) {
selectedValues = (Object[]) convertValue(value, Object[].class);
}
// flag is used to avoid a round trip while we are selecting the values
selectingValues = true;
try {
ListSelectionModel selectionModel = getList().getSelectionModel();
selectionModel.setValueIsAdjusting(true);
try {
int[] valueIndexes = determineValueIndexes(selectedValues);
int selectionMode = getSelectionMode();
if (selectionMode == ListSelectionModel.SINGLE_SELECTION && valueIndexes.length > 1) {
getList().setSelectedIndex(valueIndexes[0]);
} else {
getList().setSelectedIndices(valueIndexes);
}
// update value model if selectedValues contain elements which where not found in the list model
// elements
if (valueIndexes.length != selectedValues.length && !isReadOnly() && isEnabled()
|| (selectionMode == ListSelectionModel.SINGLE_SELECTION && valueIndexes.length > 1)) {
updateSelectedItemsFromSelectionModel();
}
} finally {
selectionModel.setValueIsAdjusting(false);
}
} finally {
selectingValues = false;
}
}
/**
* @param values
* @return
*/
protected int[] determineValueIndexes(Object[] values) {
int[] indexes = new int[values.length];
if (values.length == 0)
return indexes;
Collection lookupValues = new HashSet(Arrays.asList(values));
ListModel model = getList().getModel();
int i = 0;
for (int index = 0, size = model.getSize(); index < size && !lookupValues.isEmpty(); index++) {
if (lookupValues.remove(model.getElementAt(index))) {
indexes[i++] = index;
}
}
int[] result;
if (i != values.length) {
result = new int[i];
System.arraycopy(indexes, 0, result, 0, i);
} else {
result = indexes;
}
return result;
}
public void setRenderer(ListCellRenderer renderer) {
getList().setCellRenderer(renderer);
}
public ListCellRenderer getRenderer() {
return getList().getCellRenderer();
}
protected class SelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
if (!selectingValues && !e.getValueIsAdjusting()) {
updateSelectedItemsFromSelectionModel();
}
}
}
protected class ValueModelListener implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent evt) {
updateSelectedItemsFromValueModel();
}
}
protected ListModel getDefaultModel() {
return getList().getModel();
}
}