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 }