001    package org.springframework.richclient.widget.table;
002    
003    import org.springframework.binding.form.FormModel;
004    import org.springframework.context.support.DefaultMessageSourceResolvable;
005    import org.springframework.richclient.form.binding.BindingFactory;
006    import org.springframework.richclient.form.binding.swing.SwingBindingFactory;
007    import org.springframework.richclient.util.RcpSupport;
008    import org.springframework.richclient.widget.editor.AbstractDataEditorWidget;
009    
010    import javax.swing.table.TableCellEditor;
011    import javax.swing.table.TableCellRenderer;
012    import java.util.Comparator;
013    
014    /**
015     * A column descriptor to access a specific property. Minimal configuration includes the propertyName and an
016     * {@link Accessor}. Other features can be set using the normal bean setters and getters or through a number
017     * of chaining methods. The latter is particularly useful to create columns by instantiating the column with
018     * the minimum requirements and then adding all settings in one go.
019     * 
020     * @author Jan Hoskens
021     * @since 0.5.0
022     */
023    public class PropertyColumn
024    {
025    
026        /** No specific width set. */
027        public static final int UNSPECIFIED_WIDTH = -1;
028    
029        /** Type of the property. */
030        private Class<?> type;
031    
032        /** Accessor for the property. */
033        private Accessor accessor;
034    
035        /** Name of the property. */
036        private String propertyName;
037    
038        /** Header keys used to fetch the column header label from the message resource. */
039        private String[] headerKeys;
040    
041        /** Maximum width of the column. */
042        private int maxWidth = UNSPECIFIED_WIDTH;
043    
044        /** Minimum width of the column. */
045        private int minWidth = UNSPECIFIED_WIDTH;
046    
047        /** Is it possible to resize column? */
048        private boolean resizable = true;
049    
050        /** A specific renderer for the column. */
051        private TableCellRenderer renderer;
052    
053        /** A specific editor for the column. */
054        private TableCellEditor editor;
055    
056        /** Comparator to use when sorting according to this column. */
057        private Comparator<?> comparator;
058    
059        /** Is this column a select column? */
060        private boolean isSelectColumn = false;
061    
062        /** Can this column be used when filtering locally? */
063        private boolean isFilterColumn = false;
064    
065        /** Is the column initially visible? */
066        private boolean visible = true;
067    
068        /** Header title fetched from header keys or manually set. */
069        private String header = null;
070    
071        public PropertyColumn(final String propertyName)
072        {
073            this.propertyName = propertyName;
074        }
075        
076        /**
077         * Minimal construction requires a propertyName and an {@link Accessor}.
078         * 
079         * @param propertyName
080         *            name of the property.
081         * @param accessor
082         *            read-access for the property.
083         */
084        public PropertyColumn(final String propertyName, final Accessor accessor, final Class<?> propertyType)
085        {
086            this.propertyName = propertyName;
087            this.accessor = accessor;
088            this.type = propertyType;
089        }
090    
091        /**
092         * Constructor taking all possible arguments.
093         * 
094         * @deprecated Please use the other system (minimal constructor + adding necessary elements)
095         */
096        public PropertyColumn(final Class<?> type, final Accessor accessor, final String propertyName,
097                final String[] headerKeys, final int minWidth, final int maxWidth, final boolean resizable,
098                TableCellRenderer renderer, TableCellEditor editor, final Comparator<?> comparator,
099                final boolean isSelectColumn, final boolean visible)
100        {
101            this.type = type;
102            this.accessor = accessor;
103            this.propertyName = propertyName;
104            this.headerKeys = headerKeys;
105            this.minWidth = minWidth;
106            this.maxWidth = maxWidth;
107            this.resizable = resizable;
108            this.editor = editor;
109            this.renderer = renderer;
110            this.comparator = comparator;
111            this.isSelectColumn = isSelectColumn;
112            this.visible = visible;
113        }
114    
115        /**
116         * Set the accessor to use when retrieving the property value.
117         * 
118         * @param accessor the {@link Accessor} to read the property value.
119         */
120        public void setAccessor(Accessor accessor)
121        {
122            this.accessor = accessor;
123        }
124        
125        /**
126         * The type of the property.
127         * 
128         * @param type the type of the property.
129         */
130        public void setType(Class<?> type)
131        {
132            this.type = type;
133        }
134        
135        /**
136         * Returns the header to be used as column title. If no explicit header was set, the headerKeys are used
137         * to fetch a message from the available resources.
138         */
139        public String getHeader()
140        {
141            if (this.header == null)
142            {
143                if (this.headerKeys == null)
144                {
145                    this.headerKeys = new String[2];
146                    this.headerKeys[0] = getPropertyName() + ".header";
147                    this.headerKeys[1] = getPropertyName();
148                }
149                this.header = RcpSupport.getMessage(new DefaultMessageSourceResolvable(this.headerKeys, null,
150                        this.headerKeys[this.headerKeys.length - 1]));
151            }
152            // JTableHeader has a reusable defaultHeaderRenderer on which the default height must be correct.
153            // when painting, the columns headers are processed in order and height is being calculated, 
154            // if label is null or empty string header height is 4 and thus leaves us with a very small
155            // table-header, fix this by returning a space (-> font-size is incorporated)
156            return "".equals(this.header) ? " " : this.header;
157        }
158    
159        /**
160         * Returns the header keys that are used to fetch the column title if the header property is not set.
161         */
162        public String[] getHeaderKeys()
163        {
164            return headerKeys == null ? null : headerKeys.clone();
165        }
166    
167        /**
168         * Chaining method to set header keys.
169         * 
170         * @param headerKeys
171         *            keys used to fetch the column title from the resources if no header is explicitly set.
172         * @return <code>this</code>
173         */
174        public PropertyColumn withHeaderKeys(String[] headerKeys)
175        {
176            setHeaderKeys(headerKeys);
177            return this;
178        }
179    
180        /**
181         * Set the keys used to fetch the column title from the resources if no header is explicitly set.
182         */
183        public void setHeaderKeys(String[] headerKeys)
184        {
185            this.headerKeys = headerKeys;
186        }
187    
188        /**
189         * Returns the maximum width for this column.
190         */
191        public int getMaxWidth()
192        {
193            return maxWidth;
194        }
195    
196        /**
197         * Chaining method to set the maximum width.
198         * 
199         * @param maxWidth
200         *            maximum width for this column
201         * @return <code>this</code>
202         */
203        public PropertyColumn withMaxWidth(int maxWidth)
204        {
205            setMaxWidth(maxWidth);
206            return this;
207        }
208    
209        /**
210         * Set the maximum width of this column.
211         */
212        public void setMaxWidth(int maxWidth)
213        {
214            this.maxWidth = maxWidth;
215        }
216    
217        /**
218         * Returns the minimum width for this column.
219         */
220        public int getMinWidth()
221        {
222            return minWidth;
223        }
224    
225        /**
226         * Chaining method to set the minimum width.
227         * 
228         * @param minWidth
229         *            minimum width for this column
230         * @return <code>this</code>
231         */
232        public PropertyColumn withMinWidth(int minWidth)
233        {
234            setMinWidth(minWidth);
235            return this;
236        }
237    
238        /**
239         * Set the minimum width for this column.
240         */
241        public void setMinWidth(int minWidth)
242        {
243            this.minWidth = minWidth;
244        }
245    
246        /**
247         * Chaining method to set the minimum and maximum width to the same value. This will create a fixed width
248         * column.
249         * 
250         * @param width
251         *            fixed width for this column
252         * @return <code>this</code>
253         */
254        public PropertyColumn withFixedWidth(int width)
255        {
256            setMinWidth(width);
257            setMaxWidth(width);
258            return this;
259        }
260    
261        public boolean isResizable()
262        {
263            return resizable;
264        }
265    
266        public PropertyColumn withResizable(boolean resizable)
267        {
268            setResizable(resizable);
269            return this;
270        }
271    
272        public void setResizable(boolean resizable)
273        {
274            this.resizable = resizable;
275        }
276    
277        public PropertyColumn withFilterColumn(boolean isFilterColumn)
278        {
279            setFilterColumn(isFilterColumn);
280            return this;
281        }
282    
283        public void setFilterColumn(boolean isFilterColumn)
284        {
285            this.isFilterColumn = isFilterColumn;
286        }
287    
288        public boolean isFilterColumn()
289        {
290            return isFilterColumn;
291        }
292    
293        public TableCellRenderer getRenderer()
294        {
295            return renderer;
296        }
297    
298        public PropertyColumn withRenderer(TableCellRenderer renderer)
299        {
300            setRenderer(renderer);
301            return this;
302        }
303    
304        public void setRenderer(TableCellRenderer renderer)
305        {
306            this.renderer = renderer;
307        }
308    
309        public TableCellEditor getEditor()
310        {
311            return editor;
312        }
313    
314        public PropertyColumn withEditor(TableCellEditor editor)
315        {
316            setEditor(editor);
317            return this;
318        }
319    
320        public PropertyColumn withEditor(BindingFactory bindingFactory)
321        {
322            return withEditor(new ValueModelTableCellEditor(bindingFactory.getFormModel(), getPropertyName(),
323                    bindingFactory.createBinding(getType(), getPropertyName()).getControl()));
324        }
325    
326        public PropertyColumn withEditor(FormModel formModel)
327        {
328            return withEditor(new SwingBindingFactory(formModel));
329        }
330        
331        public PropertyColumn withEditor(AbstractDataEditorWidget dataEditor)
332        {
333            FormModel formModel = dataEditor.getDetailForm().getFormModel();
334            BindingFactory bindingFactory = dataEditor.getDetailForm().getBindingFactory();
335            return withEditor(new ValueModelTableCellEditor(formModel, getPropertyName(),
336                    bindingFactory.createBinding(getType(), getPropertyName()).getControl(), dataEditor.getUpdateCommand()));
337        }
338        
339        public void setEditor(TableCellEditor editor)
340        {
341            this.editor = editor;
342        }
343    
344        public Comparator<?> getComparator()
345        {
346            return comparator;
347        }
348    
349        public PropertyColumn withComparator(Comparator<?> comparator)
350        {
351            setComparator(comparator);
352            return this;
353        }
354    
355        public void setComparator(Comparator<?> comparator)
356        {
357            this.comparator = comparator;
358        }
359    
360        public PropertyColumn withSelectColumn(boolean isSelectColumn)
361        {
362            setSelectColumn(isSelectColumn);
363            return this;
364        }
365    
366        public boolean isSelectColumn()
367        {
368            return isSelectColumn;
369        }
370    
371        public void setSelectColumn(boolean isSelectColumn)
372        {
373            this.isSelectColumn = isSelectColumn;
374        }
375    
376        public boolean isVisible()
377        {
378            return visible;
379        }
380    
381        public PropertyColumn withVisible(boolean visible)
382        {
383            setVisible(visible);
384            return this;
385        }
386    
387        public void setVisible(boolean visible)
388        {
389            this.visible = visible;
390        }
391    
392        public Class<?> getType()
393        {
394            return type;
395        }
396    
397        public Accessor getAccessor()
398        {
399            return accessor;
400        }
401    
402        public String getPropertyName()
403        {
404            return propertyName;
405        }
406    
407        public void setHeader(String header)
408        {
409            this.header = header;
410        }
411    
412    
413    }