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 }