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 }