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 }