/**
* 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.springframework.beans.support.PropertyComparator;
import org.springframework.util.Assert;
import org.valkyriercp.binding.form.ConfigurableFormModel;
import org.valkyriercp.binding.form.FormModel;
import org.valkyriercp.binding.form.support.DefaultFormModel;
import org.valkyriercp.binding.value.ObservableList;
import org.valkyriercp.binding.value.ValueModel;
import org.valkyriercp.binding.value.support.BufferedCollectionValueModel;
import org.valkyriercp.binding.value.support.ValueHolder;
import org.valkyriercp.component.ShuttleList;
import org.valkyriercp.form.binding.Binder;
import org.valkyriercp.form.binding.Binding;
import org.valkyriercp.form.binding.support.AbstractBindingFactory;
import org.valkyriercp.list.BeanPropertyValueComboBoxEditor;
import org.valkyriercp.list.BeanPropertyValueListRenderer;
import org.valkyriercp.rules.closure.Closure;
import javax.swing.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A convenient implementation of <code>BindingFactory</code>. Provides a set
* of methods that address the typical binding requirements of Swing based
* forms.
*
* @author Oliver Hutchison
*/
public class SwingBindingFactory extends AbstractBindingFactory {
public SwingBindingFactory(FormModel formModel) {
super(formModel);
}
public Binding createBoundTextField(String formProperty) {
return createBinding(JTextField.class, formProperty);
}
public Binding createBoundTextArea(String formProperty) {
return createBinding(JTextArea.class, formProperty);
}
public Binding createBoundTextArea(String formProperty, int rows, int columns) {
Map context = createContext(TextAreaBinder.ROWS_KEY, new Integer(rows));
context.put(TextAreaBinder.COLUMNS_KEY, new Integer(columns));
return createBinding(JTextArea.class, formProperty, context);
}
public Binding createBoundFormattedTextField(String formProperty) {
return createBinding(JFormattedTextField.class, formProperty);
}
public Binding createBoundFormattedTextField(String formProperty, JFormattedTextField.AbstractFormatterFactory formatterFactory) {
Map context = createContext(FormattedTextFieldBinder.FORMATTER_FACTORY_KEY, formatterFactory);
return createBinding(JFormattedTextField.class, formProperty, context);
}
public Binding createBoundSpinner(String formProperty) {
return createBinding(JSpinner.class, formProperty);
}
public Binding createBoundLabel(String formProperty) {
return createBinding(JLabel.class, formProperty);
}
public Binding createBoundToggleButton(String formProperty) {
return createBinding(JToggleButton.class, formProperty);
}
public Binding createBoundCheckBox(String formProperty) {
return createBinding(JCheckBox.class, formProperty);
}
public Binding createBoundComboBox(String formProperty) {
return createBinding(JComboBox.class, formProperty);
}
/**
*
* @param formProperty the property to be bound
* @param selectableItems a Collection or array containing the list of items
* that may be selected
*/
public Binding createBoundComboBox(String formProperty, Object selectableItems) {
Map context = createContext(ComboBoxBinder.SELECTABLE_ITEMS_KEY, selectableItems);
return createBinding(JComboBox.class, formProperty, context);
}
public Binding createBoundComboBox(String formProperty, ValueModel selectableItemsHolder) {
return createBoundComboBox(formProperty, (Object)selectableItemsHolder);
}
public Binding createBoundComboBox(String formProperty, String selectableItemsProperty, String renderedItemProperty) {
return createBoundComboBox(formProperty, getFormModel().getValueModel(selectableItemsProperty),
renderedItemProperty);
}
public Binding createBoundComboBox(String formProperty, Object selectableItems, String renderedProperty) {
Map context = createContext(ComboBoxBinder.SELECTABLE_ITEMS_KEY, selectableItems);
context.put(ComboBoxBinder.RENDERER_KEY, new BeanPropertyValueListRenderer(renderedProperty));
context.put(ComboBoxBinder.EDITOR_KEY, new BeanPropertyEditorClosure(renderedProperty));
context.put(ComboBoxBinder.COMPARATOR_KEY, new PropertyComparator(renderedProperty, true, true));
return createBinding(JComboBox.class, formProperty, context);
}
public Binding createBoundComboBox(String formProperty, ValueModel selectableItemsHolder, String renderedProperty) {
return createBoundComboBox(formProperty, (Object)selectableItemsHolder, renderedProperty);
}
/**
* This method will most likely move over to FormModel
*
* @deprecated
*/
public ObservableList createBoundListModel(String formProperty) {
final ConfigurableFormModel formModel = ((ConfigurableFormModel)getFormModel());
ValueModel valueModel = formModel.getValueModel(formProperty);
if (!(valueModel instanceof BufferedCollectionValueModel)) {
// XXX: HACK!
valueModel = new BufferedCollectionValueModel((((DefaultFormModel) formModel).getFormObjectPropertyAccessStrategy()).getPropertyValueModel(
formProperty), formModel.getFieldMetadata(formProperty).getPropertyType());
formModel.add(formProperty, valueModel);
}
return (ObservableList)valueModel.getValue();
}
public Binding createBoundList(String formProperty) {
Map context = createContext(ListBinder.SELECTABLE_ITEMS_KEY, createBoundListModel(formProperty));
return createBinding(JList.class, formProperty, context);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItems</code> to a {@link JList}, with any
* user selection being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered as a String. Note that the selection in the
* bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code>
* is not empty when the list is bound, then its content will be used
* for the initial selection. This method uses default behavior to
* determine the selection mode of the resulting <code>JList</code>:
* if <code>selectionFormProperty</code> refers to a
* {@link java.util.Collection} type property, then
* {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
* be used, otherwise
* {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
*
* @param selectionFormProperty form property to hold user's selection.
* This property must either be compatible
* with the item objects contained in
* <code>selectableItemsHolder</code> (in
* which case only single selection makes
* sense), or must be a
* <code>Collection</code> type, which allows
* for multiple selection.
* @param selectableItems a Collection or array containing the items
* with which to populate the list.
* @return
*/
public Binding createBoundList(String selectionFormProperty, Object selectableItems) {
return createBoundList(selectionFormProperty, new ValueHolder(selectableItems));
}
public Binding createBoundList(String selectionFormProperty, Object selectableItems, String renderedProperty) {
return createBoundList(selectionFormProperty, new ValueHolder(selectableItems), renderedProperty);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItemsHolder</code> to a {@link JList}, with any
* user selection being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered by looking up a property on the item by the name contained
* in <code>renderedProperty</code>, retrieving the value of the property,
* and rendering that value in the UI. Note that the selection in the
* bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code>
* is not empty when the list is bound, then its content will be used
* for the initial selection. This method uses default behavior to
* determine the selection mode of the resulting <code>JList</code>:
* if <code>selectionFormProperty</code> refers to a
* {@link java.util.Collection} type property, then
* {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
* be used, otherwise
* {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
*
* @param selectionFormProperty form property to hold user's selection.
* This property must either be compatible
* with the item objects contained in
* <code>selectableItemsHolder</code> (in
* which case only single selection makes
* sense), or must be a
* <code>Collection</code> type, which allows
* for multiple selection.
* @param selectableItemsHolder <code>ValueModel</code> containing the
* items with which to populate the list.
* @param renderedProperty the property to be queried for each item
* in the list, the result of which will be
* used to render that item in the UI
*
* @return
*/
public Binding createBoundList(String selectionFormProperty, ValueModel selectableItemsHolder,
String renderedProperty) {
return createBoundList(selectionFormProperty, selectableItemsHolder, renderedProperty, null);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItemsHolder</code> to a {@link JList}, with any
* user selection being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered as a String. Note that the selection in the
* bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code>
* is not empty when the list is bound, then its content will be used
* for the initial selection. This method uses default behavior to
* determine the selection mode of the resulting <code>JList</code>:
* if <code>selectionFormProperty</code> refers to a
* {@link java.util.Collection} type property, then
* {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
* be used, otherwise
* {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
*
* @param selectionFormProperty form property to hold user's selection.
* This property must either be compatible
* with the item objects contained in
* <code>selectableItemsHolder</code> (in
* which case only single selection makes
* sense), or must be a
* <code>Collection</code> type, which allows
* for multiple selection.
* @param selectableItemsHolder <code>ValueModel</code> containing the
* items with which to populate the list.
*
* @return
*/
public Binding createBoundList(String selectionFormProperty, ValueModel selectableItemsHolder) {
return createBoundList(selectionFormProperty, selectableItemsHolder, null, null);
}
/**
* Binds the value(s) specified in <code>selectableItems</code> to
* a {@link JList}, with any
* user selection being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered by looking up a property on the item by the name contained
* in <code>renderedProperty</code>, retrieving the value of the property,
* and rendering that value in the UI. Note that the selection in the
* bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code>
* is not empty when the list is bound, then its content will be used
* for the initial selection.
*
* @param selectionFormProperty form property to hold user's selection.
* This property must either be compatible
* with the item objects contained in
* <code>selectableItemsHolder</code> (in
* which case only single selection makes
* sense), or must be a
* <code>Collection</code> type, which allows
* for multiple selection.
* @param selectableItems <code>Object</code> containing the
* item(s) with which to populate the list.
* Can be an instance Collection, Object[],
* a ValueModel or Object
* @param renderedProperty the property to be queried for each item
* in the list, the result of which will be
* used to render that item in the UI.
* May be null, in which case the selectable
* items will be rendered as strings.
* @param forceSelectMode forces the list selection mode. Must be
* one of the constants defined in
* {@link javax.swing.ListSelectionModel} or
* <code>null</code> for default behavior.
* If <code>null</code>, then
* {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION}
* will be used if
* <code>selectionFormProperty</code> refers
* to a {@link java.util.Collection} type
* property, otherwise
* {@link javax.swing.ListSelectionModel#SINGLE_SELECTION}
* will be used.
*
* @return
*/
public Binding createBoundList(String selectionFormProperty, Object selectableItems,
String renderedProperty, Integer forceSelectMode) {
final Map context = new HashMap();
if (forceSelectMode != null) {
context.put(ListBinder.SELECTION_MODE_KEY, forceSelectMode);
}
context.put(ListBinder.SELECTABLE_ITEMS_KEY, selectableItems);
if (renderedProperty != null) {
context.put(ListBinder.RENDERER_KEY, new BeanPropertyValueListRenderer(renderedProperty));
context.put(ListBinder.COMPARATOR_KEY, new PropertyComparator(renderedProperty, true, true));
}
return createBinding(JList.class, selectionFormProperty, context);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItemsHolder</code> to a {@link ShuttleList}, with any
* user selection being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered by looking up a property on the item by the name contained in
* <code>renderedProperty</code>, retrieving the value of the property,
* and rendering that value in the UI.
* <p>
* Note that the selection in the bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code> is
* not empty when the list is bound, then its content will be used for the
* initial selection.
*
* @param selectionFormProperty form property to hold user's selection. This
* property must be a <code>Collection</code> or array type.
* @param selectableItemsHolder <code>ValueModel</code> containing the
* items with which to populate the list.
* @param renderedProperty the property to be queried for each item in the
* list, the result of which will be used to render that item in the
* UI. May be null, in which case the selectable items will be
* rendered as strings.
* @return constructed {@link Binding}. Note that the bound control is of
* type {@link ShuttleList}. Access this component to set specific
* display properties.
*/
public Binding createBoundShuttleList( String selectionFormProperty, ValueModel selectableItemsHolder,
String renderedProperty ) {
Map context = ShuttleListBinder.createBindingContext(getFormModel(), selectionFormProperty,
selectableItemsHolder, renderedProperty);
return createBinding(ShuttleList.class, selectionFormProperty, context);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItems</code> (which will be wrapped in a
* {@link ValueHolder} to a {@link ShuttleList}, with any user selection
* being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered by looking up a property on the item by the name contained in
* <code>renderedProperty</code>, retrieving the value of the property,
* and rendering that value in the UI.
* <p>
* Note that the selection in the bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code> is
* not empty when the list is bound, then its content will be used for the
* initial selection.
*
* @param selectionFormProperty form property to hold user's selection. This
* property must be a <code>Collection</code> or array type.
* @param selectableItems Collection or array containing the items with
* which to populate the selectable list (this object will be wrapped
* in a ValueHolder).
* @param renderedProperty the property to be queried for each item in the
* list, the result of which will be used to render that item in the
* UI. May be null, in which case the selectable items will be
* rendered as strings.
* @return constructed {@link Binding}. Note that the bound control is of
* type {@link ShuttleList}. Access this component to set specific
* display properties.
*/
public Binding createBoundShuttleList( String selectionFormProperty, Object selectableItems, String renderedProperty ) {
return createBoundShuttleList(selectionFormProperty, new ValueHolder(selectableItems), renderedProperty);
}
/**
* Binds the values specified in the collection contained within
* <code>selectableItems</code> (which will be wrapped in a
* {@link ValueHolder} to a {@link ShuttleList}, with any user selection
* being placed in the form property referred to by
* <code>selectionFormProperty</code>. Each item in the list will be
* rendered as a String.
* <p>
* Note that the selection in the bound list will track any changes to the
* <code>selectionFormProperty</code>. This is especially useful to
* preselect items in the list - if <code>selectionFormProperty</code> is
* not empty when the list is bound, then its content will be used for the
* initial selection.
*
* @param selectionFormProperty form property to hold user's selection. This
* property must be a <code>Collection</code> or array type.
* @param selectableItems Collection or array containing the items with
* which to populate the selectable list (this object will be wrapped
* in a ValueHolder).
* @return constructed {@link Binding}. Note that the bound control is of
* type {@link ShuttleList}. Access this component to set specific
* display properties.
*/
public Binding createBoundShuttleList( String selectionFormProperty, Object selectableItems ) {
return createBoundShuttleList(selectionFormProperty, new ValueHolder(selectableItems), null);
}
/**
* @see #createBinding(String, String, Map)
*/
public Binding createBinding(String propertyPath, String binderId)
{
return this.createBinding(propertyPath, binderId, Collections.EMPTY_MAP);
}
/**
* Create a binding based on a specific binder id.
*
* @param propertyPath Path to property
* @param binderId Id of the binder
* @param context Context data (can be empty map)
* @return Specific binding
*/
public Binding createBinding(String propertyPath, String binderId, Map context)
{
Assert.notNull(context, "Context must not be null");
Binder binder = ((SwingBinderSelectionStrategy)getBinderSelectionStrategy()).getIdBoundBinder(binderId);
Binding binding = binder.bind(getFormModel(), propertyPath, context);
interceptBinding(binding);
return binding;
}
protected static class BeanPropertyEditorClosure implements Closure {
private final String renderedProperty;
public BeanPropertyEditorClosure(String renderedProperty) {
this.renderedProperty = renderedProperty;
}
public Object call(Object argument) {
Assert.isInstanceOf(ComboBoxEditor.class, argument);
return new BeanPropertyValueComboBoxEditor((ComboBoxEditor) argument, renderedProperty);
}
String getRenderedProperty() {
return renderedProperty;
}
}
}