001    /*
002     * $Id: DatePickerCellEditor.java 2476 2007-11-25 15:52:59Z kschaefe $
003     * 
004     * Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     *
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx.table;
022    
023    import java.awt.Component;
024    import java.awt.event.ActionEvent;
025    import java.awt.event.ActionListener;
026    import java.awt.event.MouseEvent;
027    import java.text.DateFormat;
028    import java.text.ParseException;
029    import java.util.Date;
030    import java.util.EventObject;
031    import java.util.logging.Level;
032    import java.util.logging.Logger;
033    
034    import javax.swing.AbstractCellEditor;
035    import javax.swing.BorderFactory;
036    import javax.swing.JTable;
037    import javax.swing.JTree;
038    import javax.swing.UIManager;
039    import javax.swing.table.TableCellEditor;
040    import javax.swing.tree.DefaultMutableTreeNode;
041    import javax.swing.tree.TreeCellEditor;
042    
043    import org.jdesktop.swingx.JXDatePicker;
044    import org.jdesktop.swingx.treetable.AbstractMutableTreeTableNode;
045    
046    /**
047     * A CellEditor using a JXDatePicker as editor component.<p>
048     * 
049     * NOTE: this class will be moved!
050     * 
051     * @author Richard Osbald
052     * @author Jeanette Winzenburg
053     */
054    public class DatePickerCellEditor extends AbstractCellEditor implements
055            TableCellEditor, TreeCellEditor {
056    
057        protected JXDatePicker datePicker;
058    
059        protected DateFormat dateFormat;
060    
061        protected int clickCountToStart = 2;
062    
063        private ActionListener pickerActionListener;
064    
065        protected boolean ignoreAction;
066    
067        private static Logger logger = Logger.getLogger(DatePickerCellEditor.class
068                .getName());
069    
070        private static final long serialVersionUID = -1L;
071    
072        /**
073         * Instantiates a editor with the default dateFormat.
074         * 
075         * PENDING: always override default from DatePicker?
076         *
077         */
078        public DatePickerCellEditor() {
079            this(null);
080        }
081    
082        /**
083         * Instantiates an editor with the given dateFormat. If
084         * null, the datePickers default is used.
085         * 
086         * @param dateFormat
087         */
088        public DatePickerCellEditor(DateFormat dateFormat) {
089            // JW: the copy is used to synchronize .. can 
090            // we use something else?
091            this.dateFormat = dateFormat != null ? dateFormat : 
092                DateFormat.getDateInstance();
093            datePicker = new JXDatePicker();
094            // default border crushes the editor/combo
095            datePicker.getEditor().setBorder(
096                    BorderFactory.createEmptyBorder(0, 1, 0, 1)); 
097            // should be fixed by j2se 6.0
098            datePicker.setFont(UIManager.getDefaults().getFont("TextField.font")); 
099            if (dateFormat != null) {
100                datePicker.setFormats(dateFormat);
101            }
102            datePicker.addActionListener(getPickerActionListener());
103        }
104    
105    //-------------------- CellEditor
106        
107        /**
108         * Returns the pickers date. 
109         * 
110         * Note: the date is only meaningful after a stopEditing and 
111         *   before the next call to getTableCellEditorComponent.
112         */
113        public Date getCellEditorValue() {
114            return datePicker.getDate();
115        }
116    
117        @Override
118        public boolean isCellEditable(EventObject anEvent) {
119            if (anEvent instanceof MouseEvent) {
120                return ((MouseEvent) anEvent).getClickCount() >= getClickCountToStart();
121            }
122            return super.isCellEditable(anEvent);
123        }
124    
125        /**
126         * {@inheritDoc}
127         * <p>
128         * 
129         * Overridden to commit pending edits. If commit successful, returns super,
130         * else returns false.
131         * 
132         * 
133         */
134        @Override
135        public boolean stopCellEditing() {
136            ignoreAction = true;
137            boolean canCommit = commitChange();
138            ignoreAction = false;
139            if (canCommit) {
140                return super.stopCellEditing();
141            }
142            return false;
143        }
144    
145        /**
146         * Specifies the number of clicks needed to start editing.
147         * 
148         * @param count an int specifying the number of clicks needed to start
149         *        editing
150         * @see #getClickCountToStart
151         */
152        public void setClickCountToStart(int count) {
153            clickCountToStart = count;
154        }
155    
156        /**
157         * Returns the number of clicks needed to start editing.
158         *
159         * @return the number of clicks needed to start editing
160         */
161        public int getClickCountToStart() {
162            return clickCountToStart;
163        }
164    
165    
166    //------------------------ TableCellEditor   
167        
168        public Component getTableCellEditorComponent(JTable table, Object value,
169                boolean isSelected, int row, int column) {
170            // PENDING JW: can remove the ignore flags here?
171            // the picker learnde to behave ...
172            ignoreAction = true;
173            datePicker.setDate(getValueAsDate(value));
174            // todo how to..
175            // SwingUtilities.invokeLater(new Runnable() {
176            // public void run() {
177            // datePicker.getEditor().selectAll();
178            // }
179            // });
180            ignoreAction = false;
181            return datePicker;
182        }
183    
184        //-------------------------  TreeCellEditor
185        
186        public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
187            // PENDING JW: can remove the ignore flags here?
188            // the picker learnde to behave ...
189            ignoreAction = true;
190            datePicker.setDate(getValueAsDate(value));
191            // todo how to..
192            // SwingUtilities.invokeLater(new Runnable() {
193            // public void run() {
194            // datePicker.getEditor().selectAll();
195            // }
196            // });
197            ignoreAction = false;
198            return datePicker;
199        }
200    
201    //-------------------- helpers    
202        
203        /**
204         * Returns the given value as Date.
205         * 
206         * PENDING: abstract into something pluggable (like StringValue 
207         *   in ComponentProvider?)
208         *   
209         * @param value the value to map as Date
210         * @return the value as Date or null, if not successful.
211         * 
212         */
213        protected Date getValueAsDate(Object value) {
214            if (isEmpty(value)) return null;
215            if (value instanceof Date) {
216                return (Date) value;
217            } 
218            if (value instanceof Long) {
219                return new Date((Long) value);
220            }
221            if (value instanceof String) {
222                try {
223                    // JW: why was the parsing synchronized?
224    //              synchronized (dateFormat) {
225    //              datePicker.setDate(dateFormat.parse((String) value));
226    //          }
227                    return dateFormat.parse((String) value);
228                } catch (ParseException e) {
229                    handleParseException(e);
230                }
231            }
232            if (value instanceof DefaultMutableTreeNode) {
233                return getValueAsDate(((DefaultMutableTreeNode) value).getUserObject());
234            }
235            if (value instanceof AbstractMutableTreeTableNode) {
236                return getValueAsDate(((AbstractMutableTreeTableNode) value).getUserObject());
237            }
238            return null;
239        }
240    
241        /**
242         * @param e
243         */
244        protected void handleParseException(ParseException e) {
245            logger.log(Level.SEVERE, e.getMessage(), e.getMessage());
246        }
247    
248        protected boolean isEmpty(Object value) {
249            return value == null || value instanceof String
250                    && ((String) value).length() == 0;
251        }
252    
253    //--------------- picker specifics    
254        /**
255         * Commits any pending edits and returns a boolean indicating whether the
256         * commit was successful.
257         * 
258         * @return true if the edit was valid, false otherwise.
259         */
260        protected boolean commitChange() {
261            try {
262                datePicker.commitEdit();
263                return true;
264            } catch (ParseException e) {
265            }
266            return false;
267        }
268    
269        /**
270         * 
271         * @return the DatePicker's formats.
272         * 
273         * @see org.jdesktop.swingx.JXDatePicker#getFormats().
274         */
275        public DateFormat[] getFormats() {
276            return datePicker.getFormats();
277        }
278    
279        /**
280         * 
281         * @param formats the formats to use in the datepicker.
282         * 
283         * @see org.jdesktop.swingx.JXDatePicker#setFormats(DateFormat...)
284         * 
285         */
286        public void setFormats(DateFormat... formats) {
287            datePicker.setFormats(formats);
288        }
289        /**
290         * Returns the ActionListener to add to the datePicker.
291         * 
292         * @return the action listener to listen for datePicker's
293         *    action events.
294         */
295        protected ActionListener getPickerActionListener() {
296            if (pickerActionListener == null) {
297                pickerActionListener = createPickerActionListener();
298            }
299            return pickerActionListener;
300        }
301    
302        /**
303         * Creates and returns the ActionListener for the Picker.
304         * @return the ActionListener to listen for Picker's action events.
305         */
306        protected ActionListener createPickerActionListener() {
307            ActionListener l = new ActionListener() {
308                public void actionPerformed(final ActionEvent e) {
309                    // avoid duplicate trigger from
310                    // commit in stopCellEditing
311                    if (ignoreAction)
312                        return;
313                    // still need to invoke .. hmm 
314                    // no ... with the table cooperating the
315                    // invoke is contra-productive!
316                    terminateEdit(e);                         
317                }
318    
319                /**
320                 * @param e
321                 */
322                private void terminateEdit(final ActionEvent e) {
323                    if ((e != null)
324                            && (JXDatePicker.COMMIT_KEY.equals(e.getActionCommand()))) {
325                        stopCellEditing();
326                    } else {
327                        cancelCellEditing();
328                    }
329                }
330            };
331            return l;
332        }
333    
334    
335    }