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}