/**
* 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.valkyriercp.binding.form.FormModel;
import org.valkyriercp.form.binding.support.CustomBinding;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
/**
* Binding which maps a property to a comboBox.
*
* @author jh
*
*/
public class StringSelectionListBinding extends CustomBinding
{
/** Labels to show in comboBox. */
private Object[] selectionListLabelMessages;
/** Keys associated with each label in the comboBox. */
private Object[] selectionListKeys;
/** ComboBox with selectionList. */
private JComboBox combobox = null;
/** Id used to configure the comboBox, including labels. */
private String id = null;
/** Temporary disconnecting valueModel from comboBox for internal workings. */
private boolean settingValueModel = false;
/** The default index to select when property doesn't map to the list. */
private int defaultKeyIndex = -1;
/**
* Constructor.
*
* @param comboBox
* JComboBox to use with this binding.
* @param formModel
* formModel containing the property.
* @param formPropertyPath
* path to property.
* @param readOnly
* force readOnly.
*/
public StringSelectionListBinding(JComboBox comboBox, FormModel formModel, String formPropertyPath,
boolean readOnly)
{
super(formModel, formPropertyPath, null);
this.combobox = comboBox;
setReadOnly(readOnly);
}
/**
* Will resolve keys to messages using 'id'.'key'.label as a message
* resource key.
*
* @param id
* id to use with message key resolving.
* @param keys
* keys to extract messages from.
* @return array containing labels/messages for each key.
*/
private String[] createLabels(final String id, final Object[] keys)
{
String[] messages = new String[keys.length];
for (int i = 0; i < messages.length; ++i)
{
messages[i] = getApplicationConfig().messageResolver().getMessage(id, keys[i] == null ? "null" : keys[i].toString(),
"label");
}
return messages;
}
/**
* Will extract labels from a map in sorted order.
*
* @param sortedKeys
* collection with keys in the appropriate order. Each key
* corresponds to one in the map.
* @param keysAndLabels
* map containing both, keys and labels, from which the labels
* have to be extracted.
* @return a collection containing the labels corresponding to the
* collection of sortedKeys.
*/
public static Collection getLabelsFromMap(Collection sortedKeys, Map keysAndLabels)
{
Collection<Object> labels = new ArrayList<Object>();
Iterator keyIt = sortedKeys.iterator();
while (keyIt.hasNext())
{
labels.add(keysAndLabels.get(keyIt.next()));
}
return labels;
}
@Override
protected void valueModelChanged(Object newValue)
{
preSelectItem();
selectItem(newValue);
readOnlyChanged();
}
/**
* Selects the given item in the current selectionList of the comboBoxModel.
* When the item doesn't map to an entry in the comboBoxModel, select a
* default one. See {@link #getDefaultKeyIndex()} and
* {@link #setDefaultKeyIndex(int)} for default.
*
* @param item
* object that needs to be selected in the comboBox.
*/
protected void selectItem(Object item)
{
if (this.selectionListLabelMessages != null && this.selectionListLabelMessages.length > 0)
{
int itemIndex = search(this.selectionListKeys, item);
if (itemIndex > -1) // item found in list (may/can be null)
{
settingValueModel = true;
this.combobox.setSelectedItem(this.selectionListLabelMessages[itemIndex]);
settingValueModel = false;
}
else
// newValue not in list, select defaultIndex or nothing
{
this.combobox.setSelectedIndex(getDefaultKeyIndex());
}
}
}
/**
* Arrays.binarySearch(..) does exist in java, but assumes that array is
* sorted, we don't have this restriction so dumb search from first to
* last...
*
* @param keys
* @param toFind
* @return
*/
private int search(Object[] keys, Object toFind)
{
for (int i = 0; i < keys.length; ++i)
{
if (toFind == null)
{
if (keys[i] == null)
return i;
}
else if (toFind.equals(keys[i]))
return i;
}
return -1; // niets gevonden
}
/**
* @return Default index in list
*/
public int getDefaultKeyIndex()
{
return defaultKeyIndex;
}
/**
* Set index to use when value of property doesn't match to an item in the
* comboBox list.
*
* @param defaultKeyIndex
* default index to select.
*/
public void setDefaultKeyIndex(int defaultKeyIndex)
{
this.defaultKeyIndex = defaultKeyIndex;
}
@Override
protected JComponent doBindControl()
{
this.combobox.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if (!settingValueModel && combobox.isEnabled() && combobox.getSelectedIndex() != -1)
controlValueChanged(selectionListKeys[combobox.getSelectedIndex()]);
}
});
valueModelChanged(getValue());
return this.combobox;
}
@Override
protected void readOnlyChanged()
{
combobox.setEnabled(isEnabled() && !isReadOnly());
}
@Override
protected void enabledChanged()
{
readOnlyChanged();
}
/**
* PreSelect hook. Define additional things to be executed before selecting
* the propertyValue in the comboBox list.
*/
protected void preSelectItem()
{
}
/**
* @param keys
* array of keys to set, use keys as labels.
* @see #setSelectionList(Object[], Object[])
*/
public final void setSelectionList(Object[] keys)
{
setSelectionList(keys, keys);
}
/**
* Replace the comboBoxModel in order to use the new keys/labels.
*
* @param keys
* array of objects to use as keys.
* @param labels
* array of objects to use as labels in the comboBox.
*/
public final void setSelectionList(Object[] keys, Object[] labels)
{
selectionListKeys = keys;
selectionListLabelMessages = createLabels(getId(), labels);
combobox.setModel(new DefaultComboBoxModel(selectionListLabelMessages));
selectItem(getValue());
}
/**
* @param keys
* collection of keys to set, use keys as labels.
* @see #setSelectionList(Object[], Object[])
*/
public final void setSelectionList(Collection keys)
{
setSelectionList(keys.toArray());
}
/**
* @param keys
* collection of keys to use, corresponding to the labels.
* @param labels
* collection of labels to use, corresponding to the keys.
* @see #setSelectionList(Object[], Object[])
*/
public final void setSelectionList(Collection keys, Collection labels)
{
setSelectionList(keys.toArray(), labels.toArray());
}
/**
* @param sortedKeys
* sorted subcollection of map containing the needed keys.
* @param keysAndLabels
* map containing the keys (supercollection) and mapped labels.
* @see #setSelectionList(Object[], Object[])
*/
public final void setSelectionList(Collection sortedKeys, Map keysAndLabels)
{
setSelectionList(sortedKeys, getLabelsFromMap(sortedKeys, keysAndLabels));
}
/**
* @param keysAndLabels
* map containing keys and labels to set in comboBox.
* @see #setSelectionList(Object[], Object[])
*/
public final void setSelectionList(Map keysAndLabels)
{
setSelectionList(keysAndLabels.keySet(), keysAndLabels.values());
}
/**
* @return id to resolve messages.
*/
public String getId()
{
return this.id;
}
/**
* Set id to resolve messages.
*
* @param id
* to use for message keys.
*/
public void setId(String id)
{
this.id = id;
}
}