001/*
002 * $Id: DatePickerCellEditor.java 3927 2011-02-22 16:34:11Z kleopatra $
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 */
021package org.jdesktop.swingx.table;
022
023import java.awt.Component;
024import java.awt.event.ActionEvent;
025import java.awt.event.ActionListener;
026import java.awt.event.MouseEvent;
027import java.text.DateFormat;
028import java.text.ParseException;
029import java.util.Date;
030import java.util.EventObject;
031import java.util.logging.Level;
032import java.util.logging.Logger;
033
034import javax.swing.AbstractCellEditor;
035import javax.swing.BorderFactory;
036import javax.swing.JTable;
037import javax.swing.JTree;
038import javax.swing.UIManager;
039import javax.swing.table.TableCellEditor;
040import javax.swing.tree.DefaultMutableTreeNode;
041import javax.swing.tree.TreeCellEditor;
042
043import org.jdesktop.swingx.JXDatePicker;
044import 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 */
054public 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    @Override
114    public Date getCellEditorValue() {
115        return datePicker.getDate();
116    }
117
118    @Override
119    public boolean isCellEditable(EventObject anEvent) {
120        if (anEvent instanceof MouseEvent) {
121            return ((MouseEvent) anEvent).getClickCount() >= getClickCountToStart();
122        }
123        return super.isCellEditable(anEvent);
124    }
125
126    /**
127     * {@inheritDoc}
128     * <p>
129     * 
130     * Overridden to commit pending edits. If commit successful, returns super,
131     * else returns false.
132     * 
133     * 
134     */
135    @Override
136    public boolean stopCellEditing() {
137        ignoreAction = true;
138        boolean canCommit = commitChange();
139        ignoreAction = false;
140        if (canCommit) {
141            return super.stopCellEditing();
142        }
143        return false;
144    }
145
146    /**
147     * Specifies the number of clicks needed to start editing.
148     * 
149     * @param count an int specifying the number of clicks needed to start
150     *        editing
151     * @see #getClickCountToStart
152     */
153    public void setClickCountToStart(int count) {
154        clickCountToStart = count;
155    }
156
157    /**
158     * Returns the number of clicks needed to start editing.
159     *
160     * @return the number of clicks needed to start editing
161     */
162    public int getClickCountToStart() {
163        return clickCountToStart;
164    }
165
166
167//------------------------ TableCellEditor   
168    
169    @Override
170    public Component getTableCellEditorComponent(JTable table, Object value,
171            boolean isSelected, int row, int column) {
172        // PENDING JW: can remove the ignore flags here?
173        // the picker learnde to behave ...
174        ignoreAction = true;
175        datePicker.setDate(getValueAsDate(value));
176        // todo how to..
177        // SwingUtilities.invokeLater(new Runnable() {
178        // public void run() {
179        // datePicker.getEditor().selectAll();
180        // }
181        // });
182        ignoreAction = false;
183        return datePicker;
184    }
185
186    //-------------------------  TreeCellEditor
187    
188    @Override
189    public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) {
190        // PENDING JW: can remove the ignore flags here?
191        // the picker learnde to behave ...
192        ignoreAction = true;
193        datePicker.setDate(getValueAsDate(value));
194        // todo how to..
195        // SwingUtilities.invokeLater(new Runnable() {
196        // public void run() {
197        // datePicker.getEditor().selectAll();
198        // }
199        // });
200        ignoreAction = false;
201        return datePicker;
202    }
203
204//-------------------- helpers    
205    
206    /**
207     * Returns the given value as Date.
208     * 
209     * PENDING: abstract into something pluggable (like StringValue 
210     *   in ComponentProvider?)
211     *   
212     * @param value the value to map as Date
213     * @return the value as Date or null, if not successful.
214     * 
215     */
216    protected Date getValueAsDate(Object value) {
217        if (isEmpty(value)) return null;
218        if (value instanceof Date) {
219            return (Date) value;
220        } 
221        if (value instanceof Long) {
222            return new Date((Long) value);
223        }
224        if (value instanceof String) {
225            try {
226                // JW: why was the parsing synchronized?
227//              synchronized (dateFormat) {
228//              datePicker.setDate(dateFormat.parse((String) value));
229//          }
230                return dateFormat.parse((String) value);
231            } catch (ParseException e) {
232                handleParseException(e);
233            }
234        }
235        if (value instanceof DefaultMutableTreeNode) {
236            return getValueAsDate(((DefaultMutableTreeNode) value).getUserObject());
237        }
238        if (value instanceof AbstractMutableTreeTableNode) {
239            return getValueAsDate(((AbstractMutableTreeTableNode) value).getUserObject());
240        }
241        return null;
242    }
243
244    /**
245     * @param e
246     */
247    protected void handleParseException(ParseException e) {
248        logger.log(Level.SEVERE, e.getMessage(), e.getMessage());
249    }
250
251    protected boolean isEmpty(Object value) {
252        return value == null || value instanceof String
253                && ((String) value).length() == 0;
254    }
255
256//--------------- picker specifics    
257    /**
258     * Commits any pending edits and returns a boolean indicating whether the
259     * commit was successful.
260     * 
261     * @return true if the edit was valid, false otherwise.
262     */
263    protected boolean commitChange() {
264        try {
265            datePicker.commitEdit();
266            return true;
267        } catch (ParseException e) {
268        }
269        return false;
270    }
271
272    /**
273     * 
274     * @return the DatePicker's formats.
275     * 
276     * @see org.jdesktop.swingx.JXDatePicker#getFormats().
277     */
278    public DateFormat[] getFormats() {
279        return datePicker.getFormats();
280    }
281
282    /**
283     * 
284     * @param formats the formats to use in the datepicker.
285     * 
286     * @see org.jdesktop.swingx.JXDatePicker#setFormats(DateFormat...)
287     * 
288     */
289    public void setFormats(DateFormat... formats) {
290        datePicker.setFormats(formats);
291    }
292    /**
293     * Returns the ActionListener to add to the datePicker.
294     * 
295     * @return the action listener to listen for datePicker's
296     *    action events.
297     */
298    protected ActionListener getPickerActionListener() {
299        if (pickerActionListener == null) {
300            pickerActionListener = createPickerActionListener();
301        }
302        return pickerActionListener;
303    }
304
305    /**
306     * Creates and returns the ActionListener for the Picker.
307     * @return the ActionListener to listen for Picker's action events.
308     */
309    protected ActionListener createPickerActionListener() {
310        ActionListener l = new ActionListener() {
311            @Override
312            public void actionPerformed(final ActionEvent e) {
313                // avoid duplicate trigger from
314                // commit in stopCellEditing
315                if (ignoreAction)
316                    return;
317                // still need to invoke .. hmm 
318                // no ... with the table cooperating the
319                // invoke is contra-productive!
320                terminateEdit(e);                         
321            }
322
323            /**
324             * @param e
325             */
326            private void terminateEdit(final ActionEvent e) {
327                if ((e != null)
328                        && (JXDatePicker.COMMIT_KEY.equals(e.getActionCommand()))) {
329                    stopCellEditing();
330                } else {
331                    cancelCellEditing();
332                }
333            }
334        };
335        return l;
336    }
337
338
339}