001 /* 002 * Copyright 2002-2005 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; 017 018 import java.awt.BorderLayout; 019 import java.awt.Dimension; 020 import java.util.Comparator; 021 022 import javax.swing.JComponent; 023 import javax.swing.JPanel; 024 import javax.swing.JScrollPane; 025 import javax.swing.JSplitPane; 026 import javax.swing.JTable; 027 import javax.swing.ListSelectionModel; 028 import javax.swing.table.TableModel; 029 030 import ca.odell.glazedlists.EventList; 031 import ca.odell.glazedlists.FilterList; 032 import ca.odell.glazedlists.SortedList; 033 import ca.odell.glazedlists.matchers.Matcher; 034 import ca.odell.glazedlists.matchers.MatcherEditor; 035 import ca.odell.glazedlists.swing.EventSelectionModel; 036 import ca.odell.glazedlists.swing.TableComparatorChooser; 037 import org.springframework.beans.support.PropertyComparator; 038 import org.springframework.binding.form.HierarchicalFormModel; 039 import org.springframework.richclient.form.binding.swing.SwingBindingFactory; 040 import org.springframework.richclient.form.builder.TableFormBuilder; 041 import org.springframework.richclient.table.support.GlazedTableModel; 042 import org.springframework.richclient.util.PopupMenuMouseListener; 043 044 /** 045 * This is an abstract implementation of AbstractMasterForm that uses a GlazedTableModel 046 * and JTable to represent the master information. 047 * <p> 048 * Derived types must implement: 049 * <p> 050 * <dt>{@link #getColumnPropertyNames()}</dt> 051 * <dd>To specify the properties for the table columns</dd> 052 * <dt>{@link AbstractMasterForm#createDetailForm}</dt> 053 * <dd>To construct the detail half of this master/detail form pair</dd> 054 * 055 * @author Larry Streepy 056 */ 057 public abstract class AbstractTableMasterForm extends AbstractMasterForm { 058 059 private EventList eventList; 060 private JTable masterTable; 061 private Matcher matcher; 062 private MatcherEditor matcherEditor; 063 private Comparator comparator; 064 065 /** 066 * Construct a new AbstractTableMasterForm using the given parent form model and 067 * property path. The form model for this class will be constructed by getting the 068 * value model of the specified property from the parent form model and constructing a 069 * DeepCopyBufferedCollectionValueModel on top of it. Unless 070 * {@link AbstractMasterForm#getListListModel()} has been overriden, the table will 071 * contain all the elements in the domain object referenced by <code>property</code>. 072 * 073 * @param parentFormModel Parent form model to access for this form's data 074 * @param property Property containing this forms data (must be a collection or an 075 * array) 076 * @param formId Id of this form 077 * @param detailType Type of detail object managed by this master form 078 */ 079 public AbstractTableMasterForm(HierarchicalFormModel parentFormModel, String property, String formId, 080 Class detailType) { 081 super( parentFormModel, property, formId, detailType ); 082 } 083 084 /** 085 * Set the <code>Matcher</code> to be used in filtering the elements of the master 086 * set. Note that only one of a Matcher or MatcherEditor may be used, not both. If 087 * both are specified, then the Matcher will take precedence. 088 * 089 * @param matcher The Matcher to use to filter elements in the master set. 090 */ 091 public void setFilterMatcher(Matcher matcher) { 092 this.matcher = matcher; 093 } 094 095 /** 096 * Get the <code>Matcher</code> to be used in filtering the elements of the master 097 * set. 098 * 099 * @return matcher 100 */ 101 public Matcher getFilterMatcher() { 102 return matcher; 103 } 104 105 /** 106 * Set the <code>MatcherEditor</code> to be used in filtering the elements of the 107 * master set. Note that only one of a Matcher or MatcherEditor may be used, not both. 108 * If both are specified, then the Matcher will take precedence. 109 * 110 * @param matcherEditor The MatcherEditor to use to filter elements in the master set. 111 */ 112 public void setFilterMatcherEditor(MatcherEditor matcherEditor) { 113 this.matcherEditor = matcherEditor; 114 } 115 116 /** 117 * Get the <code>MatcherEditor</code> to be used in filtering the elements of the 118 * master set. 119 * 120 * @return matcherEditor 121 */ 122 public MatcherEditor getFilterMatcherEditor() { 123 return matcherEditor; 124 } 125 126 /** 127 * Set the comparator to use for sorting the table. 128 * 129 * @param comparator to use for sorting the table 130 */ 131 public void setSortComparator(Comparator comparator) { 132 this.comparator = comparator; 133 } 134 135 /** 136 * Get the comparator to use for sorting the table. 137 * 138 * @return comparator to use for sorting the table 139 */ 140 public Comparator getSortComparator() { 141 return comparator; 142 } 143 144 /** 145 * Set the name of the property on which to compare for sorting elements in the master 146 * table. 147 * 148 * @param propertyName Name of the property on which to sort. 149 */ 150 public void setSortProperty(String propertyName) { 151 setSortComparator( new PropertyComparator( propertyName, true, true ) ); 152 } 153 154 /** 155 * Default is false (unless you have given a Comparator), override this method to use the 156 * default SortedList from GlazedLists. 157 * @return false 158 */ 159 protected boolean useSortedList() { 160 return false; 161 } 162 163 /* 164 * (non-Javadoc) 165 * 166 * @see org.springframework.richclient.form.AbstractForm#createFormControl() 167 */ 168 protected JComponent createFormControl() { 169 170 configure(); // Configure all our sub-components 171 172 eventList = getRootEventList(); 173 174 // Install the matcher if configured (this will filter the list) 175 if( matcher != null ) { 176 eventList = new FilterList(eventList, matcher); 177 } else if( matcherEditor != null ) { 178 eventList = new FilterList(eventList, matcherEditor); 179 } 180 181 // Install the sorter if configured 182 SortedList sortedList = null; 183 if( comparator != null || useSortedList()) { 184 eventList = sortedList = new SortedList(eventList, comparator); 185 } 186 187 // Install this new event list configuration (sorting and filtering) 188 installEventList(eventList); 189 190 masterTable = createTable( createTableModel() ); 191 192 // Finish the sorting installation 193 if( comparator != null || useSortedList()) { 194 new TableComparatorChooser(masterTable, sortedList, true ); 195 } 196 197 // If we have either a sort or a filter, we need a special selection model 198 if( comparator != null || matcher != null || useSortedList()) { 199 EventSelectionModel selectionModel = new EventSelectionModel(eventList); 200 masterTable.setSelectionModel( selectionModel ); 201 } 202 203 // masterTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION ); 204 masterTable.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION ); 205 206 // Setup our selection listener so that it controls the detail form 207 installSelectionHandler(); 208 209 // Enable a popup menu 210 masterTable.addMouseListener( new PopupMenuMouseListener( getPopupMenu() ) ); 211 212 // Avoid the default viewport size of 450,400 213 Dimension ps = getMasterTablePreferredSize( masterTable.getPreferredSize() ); 214 masterTable.setPreferredScrollableViewportSize( ps ); 215 216 JScrollPane sp = new JScrollPane(masterTable); 217 218 JPanel panel = new JPanel(); 219 panel.setLayout( new BorderLayout() ); 220 221 panel.add( sp, BorderLayout.CENTER ); 222 panel.add( createButtonBar(), BorderLayout.SOUTH ); 223 224 // Now put the two forms into a split pane 225 JSplitPane splitter = new JSplitPane( JSplitPane.VERTICAL_SPLIT ); 226 configureSplitter(splitter, panel, getDetailForm().getControl()); 227 228 final SwingBindingFactory sbf = (SwingBindingFactory) getBindingFactory(); 229 TableFormBuilder formBuilder = new TableFormBuilder( sbf ); 230 formBuilder.getLayoutBuilder().cell( splitter, "align=default,default rowSpec=fill:default:g" ); 231 232 updateControlsForState(); 233 234 return formBuilder.getForm(); 235 } 236 237 /** 238 * Override this method is one needs to re-size/change the splitter details. 239 * @param splitter 240 * @param masterPanel 241 * @param detailPanel 242 */ 243 protected void configureSplitter(final JSplitPane splitter, final JPanel masterPanel, final JComponent detailPanel) { 244 splitter.add( masterPanel ); 245 splitter.add( detailPanel ); 246 splitter.setResizeWeight( 1.0d ); 247 } 248 249 /** 250 * Create the master table. 251 * @param tableModel to use in the table 252 * @return table, default implementation uses the component factory to create the table 253 */ 254 protected JTable createTable( TableModel tableModel ) { 255 return getComponentFactory().createTable(tableModel); 256 } 257 258 /** 259 * Create the table model for the master table. 260 * @return table model to install 261 */ 262 protected TableModel createTableModel() { 263 // Make this table model read-only 264 return new GlazedTableModel(eventList, getColumnPropertyNames(), getId() ) { 265 protected boolean isEditable(Object row, int column) { 266 return false; 267 } 268 }; 269 } 270 271 /** 272 * Get the preferred size of the master table. The current (requested) size is 273 * provided for reference. This default implementation just returns the provided 274 * current size. 275 * 276 * @param currentSize Current (requested) preferred size of the master table 277 * @return preferred size 278 */ 279 protected Dimension getMasterTablePreferredSize(Dimension currentSize) { 280 return currentSize; 281 } 282 283 /** 284 * Get the selection model for the master list representation. 285 * 286 * @return selection model or null if master table has not been constructed yet 287 */ 288 protected ListSelectionModel getSelectionModel() { 289 return masterTable != null ? masterTable.getSelectionModel() : null; 290 } 291 292 /** 293 * Get the property names to show in columns of the master table. 294 * 295 * @return String[] array of property names 296 */ 297 protected abstract String[] getColumnPropertyNames(); 298 299 /** 300 * Indicates that we are creating a new detail object. 301 */ 302 public void creatingNewObject() { 303 getSelectionModel().clearSelection(); 304 } 305 306 /** 307 * @return Returns the eventList. 308 */ 309 protected EventList getEventList() { 310 return eventList; 311 } 312 313 /** 314 * @param list The eventList to set. 315 */ 316 protected void setEventList(EventList list) { 317 eventList = list; 318 } 319 320 /** 321 * @return Returns the masterTable. 322 */ 323 protected JTable getMasterTable() { 324 return masterTable; 325 } 326 327 /** 328 * @return Returns the masterTableModel. 329 */ 330 protected TableModel getMasterTableModel() { 331 return getMasterTable() != null ? getMasterTable().getModel() : null; 332 } 333 334 }