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    }