001    /*
002     * Copyright 2002-2004 the original author or authors.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     * 
008     * http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.form.binding.swing;
017    
018    import java.util.HashMap;
019    import java.util.Map;
020    import java.util.Collections;
021    
022    import javax.swing.ComboBoxEditor;
023    import javax.swing.JCheckBox;
024    import javax.swing.JComboBox;
025    import javax.swing.JFormattedTextField;
026    import javax.swing.JLabel;
027    import javax.swing.JList;
028    import javax.swing.JSpinner;
029    import javax.swing.JTextArea;
030    import javax.swing.JTextField;
031    import javax.swing.JToggleButton;
032    import javax.swing.JFormattedTextField.AbstractFormatterFactory;
033    
034    import org.springframework.beans.support.PropertyComparator;
035    import org.springframework.binding.form.ConfigurableFormModel;
036    import org.springframework.binding.form.FormModel;
037    import org.springframework.binding.form.support.DefaultFormModel;
038    import org.springframework.binding.value.ValueModel;
039    import org.springframework.binding.value.support.BufferedCollectionValueModel;
040    import org.springframework.binding.value.support.ObservableList;
041    import org.springframework.binding.value.support.ValueHolder;
042    import org.springframework.rules.closure.Closure;
043    import org.springframework.richclient.form.binding.Binding;
044    import org.springframework.richclient.form.binding.Binder;
045    import org.springframework.richclient.form.binding.support.AbstractBindingFactory;
046    import org.springframework.richclient.list.BeanPropertyValueComboBoxEditor;
047    import org.springframework.richclient.list.BeanPropertyValueListRenderer;
048    import org.springframework.richclient.components.ShuttleList;
049    import org.springframework.util.Assert;
050    
051    /**
052     * A convenient implementation of <code>BindingFactory</code>. Provides a set
053     * of methods that address the typical binding requirements of Swing based 
054     * forms.
055     * 
056     * @author Oliver Hutchison
057     */
058    public class SwingBindingFactory extends AbstractBindingFactory {
059    
060        public SwingBindingFactory(FormModel formModel) {
061            super(formModel);
062        }
063    
064        public Binding createBoundTextField(String formProperty) {
065            return createBinding(JTextField.class, formProperty);
066        }
067    
068        public Binding createBoundTextArea(String formProperty) {
069            return createBinding(JTextArea.class, formProperty);
070        }
071    
072        public Binding createBoundTextArea(String formProperty, int rows, int columns) {
073            Map context = createContext(TextAreaBinder.ROWS_KEY, new Integer(rows));
074            context.put(TextAreaBinder.COLUMNS_KEY, new Integer(columns));
075            return createBinding(JTextArea.class, formProperty, context);
076        }
077    
078        public Binding createBoundFormattedTextField(String formProperty) {
079            return createBinding(JFormattedTextField.class, formProperty);
080        }
081    
082        public Binding createBoundFormattedTextField(String formProperty, AbstractFormatterFactory formatterFactory) {
083            Map context = createContext(FormattedTextFieldBinder.FORMATTER_FACTORY_KEY, formatterFactory);
084            return createBinding(JFormattedTextField.class, formProperty, context);
085        }
086    
087        public Binding createBoundSpinner(String formProperty) {
088            return createBinding(JSpinner.class, formProperty);
089        }
090    
091        public Binding createBoundLabel(String formProperty) {
092            return createBinding(JLabel.class, formProperty);
093        }
094    
095        public Binding createBoundToggleButton(String formProperty) {
096            return createBinding(JToggleButton.class, formProperty);
097        }
098    
099        public Binding createBoundCheckBox(String formProperty) {
100            return createBinding(JCheckBox.class, formProperty);
101        }
102    
103        public Binding createBoundComboBox(String formProperty) {
104            return createBinding(JComboBox.class, formProperty);
105        }
106    
107        /**
108         * 
109         * @param formProperty the property to be bound
110         * @param selectableItems a Collection or array containing the list of items 
111         * that may be selected
112         */
113        public Binding createBoundComboBox(String formProperty, Object selectableItems) {
114            Map context = createContext(ComboBoxBinder.SELECTABLE_ITEMS_KEY, selectableItems);
115            return createBinding(JComboBox.class, formProperty, context);
116        }
117    
118        public Binding createBoundComboBox(String formProperty, ValueModel selectableItemsHolder) {
119            return createBoundComboBox(formProperty, (Object)selectableItemsHolder);
120        }
121    
122        public Binding createBoundComboBox(String formProperty, String selectableItemsProperty, String renderedItemProperty) {
123            return createBoundComboBox(formProperty, getFormModel().getValueModel(selectableItemsProperty),
124                    renderedItemProperty);
125        }
126    
127        public Binding createBoundComboBox(String formProperty, Object selectableItems, String renderedProperty) {
128            Map context = createContext(ComboBoxBinder.SELECTABLE_ITEMS_KEY, selectableItems);
129            context.put(ComboBoxBinder.RENDERER_KEY, new BeanPropertyValueListRenderer(renderedProperty));
130            context.put(ComboBoxBinder.EDITOR_KEY, new BeanPropertyEditorClosure(renderedProperty));
131            context.put(ComboBoxBinder.COMPARATOR_KEY, new PropertyComparator(renderedProperty, true, true));
132            return createBinding(JComboBox.class, formProperty, context);
133        }
134    
135        public Binding createBoundComboBox(String formProperty, ValueModel selectableItemsHolder, String renderedProperty) {
136            return createBoundComboBox(formProperty, (Object)selectableItemsHolder, renderedProperty);
137        }
138    
139        /**
140         * This method will most likely move over to FormModel
141         * 
142         * @deprecated
143         */
144        public ObservableList createBoundListModel(String formProperty) {
145            final ConfigurableFormModel formModel = ((ConfigurableFormModel)getFormModel());
146            ValueModel valueModel = formModel.getValueModel(formProperty);
147            if (!(valueModel instanceof BufferedCollectionValueModel)) {
148                // XXX: HACK!
149                valueModel = new BufferedCollectionValueModel((((DefaultFormModel) formModel).getFormObjectPropertyAccessStrategy()).getPropertyValueModel(
150                        formProperty), formModel.getFieldMetadata(formProperty).getPropertyType());
151                formModel.add(formProperty, valueModel);
152            }
153            return (ObservableList)valueModel.getValue();
154        }
155    
156        public Binding createBoundList(String formProperty) {
157            Map context = createContext(ListBinder.SELECTABLE_ITEMS_KEY, createBoundListModel(formProperty));
158            return createBinding(JList.class, formProperty, context);
159        }
160    
161        /**
162         * Binds the values specified in the collection contained within
163         * <code>selectableItems</code> to a {@link JList}, with any
164         * user selection being placed in the form property referred to by
165         * <code>selectionFormProperty</code>.  Each item in the list will be
166         * rendered as a String.  Note that the selection in the
167         * bound list will track any changes to the
168         * <code>selectionFormProperty</code>.  This is especially useful to
169         * preselect items in the list - if <code>selectionFormProperty</code>
170         * is not empty when the list is bound, then its content will be used
171         * for the initial selection.  This method uses default behavior to
172         * determine the selection mode of the resulting <code>JList</code>:
173         * if <code>selectionFormProperty</code> refers to a
174         * {@link java.util.Collection} type property, then
175         * {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
176         * be used, otherwise
177         * {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
178         * 
179         * @param selectionFormProperty form property to hold user's selection.
180         *                              This property must either be compatible
181         *                              with the item objects contained in
182         *                              <code>selectableItemsHolder</code> (in
183         *                              which case only single selection makes
184         *                              sense), or must be a
185         *                              <code>Collection</code> type, which allows
186         *                              for multiple selection.
187         * @param selectableItems           a Collection or array containing the items 
188         *                              with which to populate the list.              
189         * @return
190         */
191        public Binding createBoundList(String selectionFormProperty, Object selectableItems) {
192            return createBoundList(selectionFormProperty, new ValueHolder(selectableItems));
193        }
194    
195        public Binding createBoundList(String selectionFormProperty, Object selectableItems, String renderedProperty) {
196            return createBoundList(selectionFormProperty, new ValueHolder(selectableItems), renderedProperty);
197        }
198    
199        /**
200         * Binds the values specified in the collection contained within
201         * <code>selectableItemsHolder</code> to a {@link JList}, with any
202         * user selection being placed in the form property referred to by
203         * <code>selectionFormProperty</code>.  Each item in the list will be
204         * rendered by looking up a property on the item by the name contained
205         * in <code>renderedProperty</code>, retrieving the value of the property,
206         * and rendering that value in the UI.  Note that the selection in the
207         * bound list will track any changes to the
208         * <code>selectionFormProperty</code>.  This is especially useful to
209         * preselect items in the list - if <code>selectionFormProperty</code>
210         * is not empty when the list is bound, then its content will be used
211         * for the initial selection.  This method uses default behavior to
212         * determine the selection mode of the resulting <code>JList</code>:
213         * if <code>selectionFormProperty</code> refers to a
214         * {@link java.util.Collection} type property, then
215         * {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
216         * be used, otherwise
217         * {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
218         * 
219         * @param selectionFormProperty form property to hold user's selection.
220         *                              This property must either be compatible
221         *                              with the item objects contained in
222         *                              <code>selectableItemsHolder</code> (in
223         *                              which case only single selection makes
224         *                              sense), or must be a
225         *                              <code>Collection</code> type, which allows
226         *                              for multiple selection.
227         * @param selectableItemsHolder <code>ValueModel</code> containing the
228         *                              items with which to populate the list. 
229         * @param renderedProperty      the property to be queried for each item
230         *                              in the list, the result of which will be
231         *                              used to render that item in the UI
232         *                              
233         * @return
234         */
235        public Binding createBoundList(String selectionFormProperty, ValueModel selectableItemsHolder,
236                String renderedProperty) {
237            return createBoundList(selectionFormProperty, selectableItemsHolder, renderedProperty, null);
238        }
239    
240        /**
241         * Binds the values specified in the collection contained within
242         * <code>selectableItemsHolder</code> to a {@link JList}, with any
243         * user selection being placed in the form property referred to by
244         * <code>selectionFormProperty</code>.  Each item in the list will be
245         * rendered as a String.  Note that the selection in the
246         * bound list will track any changes to the
247         * <code>selectionFormProperty</code>.  This is especially useful to
248         * preselect items in the list - if <code>selectionFormProperty</code>
249         * is not empty when the list is bound, then its content will be used
250         * for the initial selection.  This method uses default behavior to
251         * determine the selection mode of the resulting <code>JList</code>:
252         * if <code>selectionFormProperty</code> refers to a
253         * {@link java.util.Collection} type property, then
254         * {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION} will
255         * be used, otherwise
256         * {@link javax.swing.ListSelectionModel#SINGLE_SELECTION} will be used.
257         * 
258         * @param selectionFormProperty form property to hold user's selection.
259         *                              This property must either be compatible
260         *                              with the item objects contained in
261         *                              <code>selectableItemsHolder</code> (in
262         *                              which case only single selection makes
263         *                              sense), or must be a
264         *                              <code>Collection</code> type, which allows
265         *                              for multiple selection.
266         * @param selectableItemsHolder <code>ValueModel</code> containing the
267         *                              items with which to populate the list. 
268         *                              
269         * @return
270         */
271        public Binding createBoundList(String selectionFormProperty, ValueModel selectableItemsHolder) {
272            return createBoundList(selectionFormProperty, selectableItemsHolder, null, null);
273        }
274    
275        /**
276         * Binds the value(s) specified in <code>selectableItems</code> to 
277         * a {@link JList}, with any
278         * user selection being placed in the form property referred to by
279         * <code>selectionFormProperty</code>.  Each item in the list will be
280         * rendered by looking up a property on the item by the name contained
281         * in <code>renderedProperty</code>, retrieving the value of the property,
282         * and rendering that value in the UI.  Note that the selection in the
283         * bound list will track any changes to the
284         * <code>selectionFormProperty</code>.  This is especially useful to
285         * preselect items in the list - if <code>selectionFormProperty</code>
286         * is not empty when the list is bound, then its content will be used
287         * for the initial selection.
288         * 
289         * @param selectionFormProperty form property to hold user's selection.
290         *                              This property must either be compatible
291         *                              with the item objects contained in
292         *                              <code>selectableItemsHolder</code> (in
293         *                              which case only single selection makes
294         *                              sense), or must be a
295         *                              <code>Collection</code> type, which allows
296         *                              for multiple selection.
297         * @param selectableItems       <code>Object</code> containing the
298         *                              item(s) with which to populate the list. 
299         *                              Can be an instance Collection, Object[], 
300         *                              a ValueModel or Object 
301         * @param renderedProperty      the property to be queried for each item
302         *                              in the list, the result of which will be
303         *                              used to render that item in the UI.
304         *                              May be null, in which case the selectable
305         *                              items will be rendered as strings.
306         * @param forceSelectMode       forces the list selection mode.  Must be
307         *                              one of the constants defined in
308         *                              {@link javax.swing.ListSelectionModel} or
309         *                              <code>null</code> for default behavior.
310         *                              If <code>null</code>, then
311         *                              {@link javax.swing.ListSelectionModel#MULTIPLE_INTERVAL_SELECTION}
312         *                              will be used if
313         *                              <code>selectionFormProperty</code> refers
314         *                              to a {@link java.util.Collection} type
315         *                              property, otherwise
316         *                              {@link javax.swing.ListSelectionModel#SINGLE_SELECTION}
317         *                              will be used.
318         *                              
319         * @return
320         */
321        public Binding createBoundList(String selectionFormProperty, Object selectableItems,
322                String renderedProperty, Integer forceSelectMode) {
323            final Map context = new HashMap();
324            if (forceSelectMode != null) {
325                context.put(ListBinder.SELECTION_MODE_KEY, forceSelectMode);
326            }
327            context.put(ListBinder.SELECTABLE_ITEMS_KEY, selectableItems);
328            if (renderedProperty != null) {
329                context.put(ListBinder.RENDERER_KEY, new BeanPropertyValueListRenderer(renderedProperty));
330                context.put(ListBinder.COMPARATOR_KEY, new PropertyComparator(renderedProperty, true, true));
331            }
332            return createBinding(JList.class, selectionFormProperty, context);
333        }
334    
335        /**
336         * Binds the values specified in the collection contained within
337         * <code>selectableItemsHolder</code> to a {@link org.springframework.richclient.components.ShuttleList}, with any
338         * user selection being placed in the form property referred to by
339         * <code>selectionFormProperty</code>. Each item in the list will be
340         * rendered by looking up a property on the item by the name contained in
341         * <code>renderedProperty</code>, retrieving the value of the property,
342         * and rendering that value in the UI.
343         * <p>
344         * Note that the selection in the bound list will track any changes to the
345         * <code>selectionFormProperty</code>. This is especially useful to
346         * preselect items in the list - if <code>selectionFormProperty</code> is
347         * not empty when the list is bound, then its content will be used for the
348         * initial selection.
349         *
350         * @param selectionFormProperty form property to hold user's selection. This
351         *        property must be a <code>Collection</code> or array type.
352         * @param selectableItemsHolder <code>ValueModel</code> containing the
353         *        items with which to populate the list.
354         * @param renderedProperty the property to be queried for each item in the
355         *        list, the result of which will be used to render that item in the
356         *        UI. May be null, in which case the selectable items will be
357         *        rendered as strings.
358         * @return constructed {@link Binding}. Note that the bound control is of
359         *         type {@link org.springframework.richclient.components.ShuttleList}. Access this component to set specific
360         *         display properties.
361         */
362        public Binding createBoundShuttleList( String selectionFormProperty, ValueModel selectableItemsHolder,
363                String renderedProperty ) {
364            Map context = ShuttleListBinder.createBindingContext(getFormModel(), selectionFormProperty,
365                    selectableItemsHolder, renderedProperty);
366            return createBinding(ShuttleList.class, selectionFormProperty, context);
367        }
368    
369        /**
370         * Binds the values specified in the collection contained within
371         * <code>selectableItems</code> (which will be wrapped in a
372         * {@link ValueHolder} to a {@link ShuttleList}, with any user selection
373         * being placed in the form property referred to by
374         * <code>selectionFormProperty</code>. Each item in the list will be
375         * rendered by looking up a property on the item by the name contained in
376         * <code>renderedProperty</code>, retrieving the value of the property,
377         * and rendering that value in the UI.
378         * <p>
379         * Note that the selection in the bound list will track any changes to the
380         * <code>selectionFormProperty</code>. This is especially useful to
381         * preselect items in the list - if <code>selectionFormProperty</code> is
382         * not empty when the list is bound, then its content will be used for the
383         * initial selection.
384         *
385         * @param selectionFormProperty form property to hold user's selection. This
386         *        property must be a <code>Collection</code> or array type.
387         * @param selectableItems Collection or array containing the items with
388         *        which to populate the selectable list (this object will be wrapped
389         *        in a ValueHolder).
390         * @param renderedProperty the property to be queried for each item in the
391         *        list, the result of which will be used to render that item in the
392         *        UI. May be null, in which case the selectable items will be
393         *        rendered as strings.
394         * @return constructed {@link Binding}. Note that the bound control is of
395         *         type {@link ShuttleList}. Access this component to set specific
396         *         display properties.
397         */
398        public Binding createBoundShuttleList( String selectionFormProperty, Object selectableItems, String renderedProperty ) {
399            return createBoundShuttleList(selectionFormProperty, new ValueHolder(selectableItems), renderedProperty);
400        }
401    
402        /**
403         * Binds the values specified in the collection contained within
404         * <code>selectableItems</code> (which will be wrapped in a
405         * {@link ValueHolder} to a {@link ShuttleList}, with any user selection
406         * being placed in the form property referred to by
407         * <code>selectionFormProperty</code>. Each item in the list will be
408         * rendered as a String.
409         * <p>
410         * Note that the selection in the bound list will track any changes to the
411         * <code>selectionFormProperty</code>. This is especially useful to
412         * preselect items in the list - if <code>selectionFormProperty</code> is
413         * not empty when the list is bound, then its content will be used for the
414         * initial selection.
415         *
416         * @param selectionFormProperty form property to hold user's selection. This
417         *        property must be a <code>Collection</code> or array type.
418         * @param selectableItems Collection or array containing the items with
419         *        which to populate the selectable list (this object will be wrapped
420         *        in a ValueHolder).
421         * @return constructed {@link Binding}. Note that the bound control is of
422         *         type {@link ShuttleList}. Access this component to set specific
423         *         display properties.
424         */
425        public Binding createBoundShuttleList( String selectionFormProperty, Object selectableItems ) {
426            return createBoundShuttleList(selectionFormProperty, new ValueHolder(selectableItems), null);
427        }
428    
429        /**
430         * @see #createBinding(String, String, Map)
431         */
432        public Binding createBinding(String propertyPath, String binderId)
433        {
434            return this.createBinding(propertyPath, binderId, Collections.EMPTY_MAP);
435        }
436    
437        /**
438         * Create a binding based on a specific binder id.
439         *
440         * @param propertyPath Path to property
441         * @param binderId Id of the binder
442         * @param context Context data (can be empty map)
443         * @return Specific binding
444         */
445        public Binding createBinding(String propertyPath, String binderId, Map context)
446        {
447            Assert.notNull(context, "Context must not be null");
448            Binder binder = ((SwingBinderSelectionStrategy)getBinderSelectionStrategy()).getIdBoundBinder(binderId);
449            Binding binding = binder.bind(getFormModel(), propertyPath, context);
450            interceptBinding(binding);
451            return binding;
452        }
453    
454        protected static class BeanPropertyEditorClosure implements Closure {
455    
456            private final String renderedProperty;
457    
458            public BeanPropertyEditorClosure(String renderedProperty) {
459                this.renderedProperty = renderedProperty;
460            }
461    
462            public Object call(Object argument) {
463                Assert.isInstanceOf(ComboBoxEditor.class, argument);
464                return new BeanPropertyValueComboBoxEditor((ComboBoxEditor) argument, renderedProperty);
465            }
466            
467            String getRenderedProperty() {
468                return renderedProperty;
469            }
470    
471        }
472    }