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 }