View Javadoc
1   /**
2    * Copyright 2004 Juan Heyns. All rights reserved.
3    * <p/>
4    * Redistribution and use in source and binary forms, with or without modification, are
5    * permitted provided that the following conditions are met:
6    * <p/>
7    * 1. Redistributions of source code must retain the above copyright notice, this list of
8    * conditions and the following disclaimer.
9    * <p/>
10   * 2. Redistributions in binary form must reproduce the above copyright notice, this list
11   * of conditions and the following disclaimer in the documentation and/or other materials
12   * provided with the distribution.
13   * <p/>
14   * THIS SOFTWARE IS PROVIDED BY JUAN HEYNS ``AS IS'' AND ANY EXPRESS OR IMPLIED
15   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
16   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JUAN HEYNS OR
17   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
21   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23   * <p/>
24   * The views and conclusions contained in the software and documentation are those of the
25   * authors and should not be interpreted as representing official policies, either expressed
26   * or implied, of Juan Heyns.
27   */
28  package org.jdatepicker;
29  
30  /*
31   * #%L
32   * Reef DB :: UI
33   * $Id:$
34   * $HeadURL:$
35   * %%
36   * Copyright (C) 2014 - 2015 Ifremer
37   * %%
38   * This program is free software: you can redistribute it and/or modify
39   * it under the terms of the GNU Affero General Public License as published by
40   * the Free Software Foundation, either version 3 of the License, or
41   * (at your option) any later version.
42   * 
43   * This program is distributed in the hope that it will be useful,
44   * but WITHOUT ANY WARRANTY; without even the implied warranty of
45   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46   * GNU General Public License for more details.
47   * 
48   * You should have received a copy of the GNU Affero General Public License
49   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
50   * #L%
51   */
52  
53  
54  import org.jdatepicker.constraints.DateSelectionConstraint;
55  
56  import javax.swing.*;
57  import java.awt.*;
58  import java.awt.event.*;
59  import java.beans.PropertyChangeEvent;
60  import java.beans.PropertyChangeListener;
61  import java.text.DateFormat;
62  import java.text.SimpleDateFormat;
63  import java.util.Date;
64  import java.util.HashSet;
65  import java.util.Set;
66  
67  /**
68   * Created on 25 Mar 2004
69   * Refactored 21 Jun 2004
70   * Refactored 14 May 2009
71   * Refactored 16 April 2010
72   * Updated 26 April 2010
73   * Updated 10 August 2012
74   * Updated 6 Jun 2015
75   * Updated 20/11/2015
76   *
77   * @author Juan Heyns
78   * @author JC Oosthuizen
79   * @author Yue Huang
80   * @author Ludovic Pecquot
81   */
82  public class JDatePicker extends JComponent {
83  
84      private static final long serialVersionUID = 2814777654384974503L;
85  
86      /** Constant <code>COMMIT_DATE="JDatePanel.COMMIT_DATE"</code> */
87      public static final String COMMIT_DATE = JDatePanel.COMMIT_DATE;
88  
89      private Popup popup;
90      private final FittingPopupFactory popupFactory = new FittingPopupFactory();
91      private final JFormattedTextField formattedTextField;
92      private DateComponentFormatter dateComponentFormatter;
93      private final JButton button;
94  
95      private final JDatePanel datePanel;
96  
97      private final Set<ActionListener> actionListeners;
98  
99      /**
100      * Create a JDatePicker with a default calendar model.
101      */
102     public JDatePicker() {
103         this(new JDatePanel());
104     }
105 
106     /**
107      * Create a JDatePicker with a custom date model.
108      *
109      * @param model a custom date model
110      */
111     public JDatePicker(DateModel model) {
112         this(new JDatePanel(model));
113     }
114 
115     /**
116      * You are able to set the format of the date being displayed on the label.
117      * Formatting is described at:
118      *
119      * @param datePanel The DatePanel to use
120      */
121     private JDatePicker(JDatePanel datePanel) {
122         this.datePanel = datePanel;
123         datePanel.setBorder(BorderFactory.createLineBorder(datePanel.getColors().getColor(ComponentColorDefaults.Key.POPUP_BORDER)));
124 
125         //Initialise Variables
126         actionListeners = new HashSet<>();
127 
128         //Create Layout
129         setLayout(new BorderLayout());
130 
131         //Add and Configure TextField
132         formattedTextField = createTextField();
133         add(formattedTextField, BorderLayout.CENTER);
134 
135         //Add and Configure Button
136         button = createButton();
137         add(button, BorderLayout.LINE_END);
138 
139         //Add event listeners
140         InternalEventHandler internalEventHandler = new InternalEventHandler();
141         addHierarchyBoundsListener(internalEventHandler);
142 //TODO        addAncestorListener(listener)
143         button.addActionListener(internalEventHandler);
144         formattedTextField.addPropertyChangeListener("value", internalEventHandler);
145         datePanel.addActionListener(internalEventHandler);
146         datePanel.getModel().addPropertyChangeListener(internalEventHandler);
147         long eventMask = MouseEvent.MOUSE_PRESSED;
148         Toolkit.getDefaultToolkit().addAWTEventListener(internalEventHandler, eventMask);
149     }
150 
151     /**
152      * <p>createTextField.</p>
153      *
154      * @return a {@link javax.swing.JFormattedTextField} object.
155      */
156     protected JFormattedTextField createTextField() {
157         dateComponentFormatter = new DateComponentFormatter();
158         JFormattedTextField textField = new JFormattedTextField(dateComponentFormatter);
159         textField.setValue(datePanel.getModel().getDate());
160         textField.setEditable(false);
161         return textField;
162     }
163 
164     /**
165      * <p>createButton.</p>
166      *
167      * @return a {@link javax.swing.JButton} object.
168      */
169     protected JButton createButton() {
170         JButton b = new JButton();
171         b.setRolloverEnabled(true);
172         b.setFocusable(false);
173         Icon icon = ComponentIconDefaults.getInstance().getPopupButtonIcon();
174         b.setIcon(icon);
175         b.setText(icon == null ? "..." : "");
176         return b;
177     }
178 
179     /**
180      * <p>getDate.</p>
181      *
182      * @return a {@link java.util.Date} object.
183      */
184     public Date getDate() {
185         return getModel().getDate();
186     }
187 
188     /**
189      * <p>setDate.</p>
190      *
191      * @param date a {@link java.util.Date} object.
192      */
193     public void setDate(Date date) {
194         getModel().setDate(date);
195     }
196 
197     /**
198      * <p>addActionListener.</p>
199      *
200      * @param actionListener a {@link java.awt.event.ActionListener} object.
201      */
202     public void addActionListener(ActionListener actionListener) {
203         actionListeners.add(actionListener);
204         datePanel.addActionListener(actionListener);
205     }
206 
207     /**
208      * <p>removeActionListener.</p>
209      *
210      * @param actionListener a {@link java.awt.event.ActionListener} object.
211      */
212     public void removeActionListener(ActionListener actionListener) {
213         actionListeners.remove(actionListener);
214         datePanel.removeActionListener(actionListener);
215     }
216 
217     /**
218      * <p>getModel.</p>
219      *
220      * @return a {@link org.jdatepicker.DateModel} object.
221      */
222     public DateModel getModel() {
223         return datePanel.getModel();
224     }
225 
226     /**
227      * <p>getEditor.</p>
228      *
229      * @return a {@link javax.swing.JFormattedTextField} object.
230      */
231     public JFormattedTextField getEditor() {
232         return formattedTextField;
233     }
234 
235     /**
236      * <p>setTextEditable.</p>
237      *
238      * @param editable a boolean.
239      */
240     public void setTextEditable(boolean editable) {
241         formattedTextField.setEditable(editable);
242     }
243 
244     /**
245      * <p>isTextEditable.</p>
246      *
247      * @return a boolean.
248      */
249     public boolean isTextEditable() {
250         return formattedTextField.isEditable();
251     }
252 
253     /**
254      * <p>setButtonFocusable.</p>
255      *
256      * @param focusable a boolean.
257      */
258     public void setButtonFocusable(boolean focusable) {
259         button.setFocusable(focusable);
260     }
261 
262     /**
263      * <p>getButtonFocusable.</p>
264      *
265      * @return a boolean.
266      */
267     public boolean getButtonFocusable() {
268         return button.isFocusable();
269     }
270 
271     /**
272      * Called internally to popup the dates.
273      */
274     private void showPopup() {
275         if (popup == null) {
276             datePanel.setVisible(true);
277             Point loc = getLocationOnScreen();
278             popup = popupFactory.getPopup(this, datePanel, loc.x, loc.y + getHeight());
279             SwingUtilities.invokeLater(new Runnable() {
280                 @Override
281                 public void run() {
282                     popup.show();
283                 }
284             });
285         }
286     }
287 
288     /**
289      * Called internally to hide the popup dates.
290      */
291     private void hidePopup() {
292         if (popup != null) {
293             popup.hide();
294             popup = null;
295         }
296     }
297 
298     private Set<Component> getAllComponents(Component component) {
299         Set<Component> children = new HashSet<>();
300         children.add(component);
301         if (component instanceof Container) {
302             Container container = (Container) component;
303             Component[] components = container.getComponents();
304             for (Component component1 : components) {
305                 children.addAll(getAllComponents(component1));
306             }
307         }
308         return children;
309     }
310 
311     /**
312      * <p>isDoubleClickAction.</p>
313      *
314      * @return a boolean.
315      */
316     public boolean isDoubleClickAction() {
317         return datePanel.isDoubleClickAction();
318     }
319 
320     /**
321      * <p>isShowYearButtons.</p>
322      *
323      * @return a boolean.
324      */
325     public boolean isShowYearButtons() {
326         return datePanel.isShowYearButtons();
327     }
328 
329     /**
330      * <p>setDoubleClickAction.</p>
331      *
332      * @param doubleClickAction a boolean.
333      */
334     public void setDoubleClickAction(boolean doubleClickAction) {
335         datePanel.setDoubleClickAction(doubleClickAction);
336     }
337 
338     /**
339      * <p>setShowYearButtons.</p>
340      *
341      * @param showYearButtons a boolean.
342      */
343     public void setShowYearButtons(boolean showYearButtons) {
344         datePanel.setShowYearButtons(showYearButtons);
345     }
346 
347     /**
348      * <p>addDateSelectionConstraint.</p>
349      *
350      * @param constraint a {@link org.jdatepicker.constraints.DateSelectionConstraint} object.
351      */
352     public void addDateSelectionConstraint(DateSelectionConstraint constraint) {
353         datePanel.addDateSelectionConstraint(constraint);
354     }
355 
356     /**
357      * <p>removeDateSelectionConstraint.</p>
358      *
359      * @param constraint a {@link org.jdatepicker.constraints.DateSelectionConstraint} object.
360      */
361     public void removeDateSelectionConstraint(DateSelectionConstraint constraint) {
362         datePanel.removeDateSelectionConstraint(constraint);
363     }
364 
365     /**
366      * <p>removeAllDateSelectionConstraints.</p>
367      */
368     public void removeAllDateSelectionConstraints() {
369         datePanel.removeAllDateSelectionConstraints();
370     }
371 
372     /**
373      * <p>getDateSelectionConstraints.</p>
374      *
375      * @return a {@link java.util.Set} object.
376      */
377     public Set<DateSelectionConstraint> getDateSelectionConstraints() {
378         return datePanel.getDateSelectionConstraints();
379     }
380 
381     /** {@inheritDoc} */
382     @Override
383     public void setVisible(boolean aFlag) {
384         if (!aFlag) {
385             hidePopup();
386         }
387         super.setVisible(aFlag);
388     }
389 
390     /** {@inheritDoc} */
391     @Override
392     public void setEnabled(boolean enabled) {
393         button.setEnabled(enabled);
394         datePanel.setEnabled(enabled);
395         formattedTextField.setEnabled(enabled);
396 
397         super.setEnabled(enabled);
398     }
399 
400     /**
401      * <p>setDateFormat.</p>
402      *
403      * @param datePattern a {@link java.lang.String} object.
404      * @param default2DigitYearStart a {@link java.util.Date} object.
405      */
406     public void setDateFormat(String datePattern, Date default2DigitYearStart) {
407 
408         SimpleDateFormat outputDateFormat = new SimpleDateFormat(datePattern);
409 
410         SimpleDateFormat inputDateFormat;
411         if (datePattern.contains("yyyy")) {
412             inputDateFormat = new SimpleDateFormat(datePattern.replace("yyyy", "yy"));
413         } else {
414             inputDateFormat = outputDateFormat;
415         }
416         inputDateFormat.set2DigitYearStart(default2DigitYearStart);
417 
418         setFormat(AbstractComponentFormat.Key.OUTPUT_DATE_FIELD, outputDateFormat);
419         setFormat(AbstractComponentFormat.Key.INPUT_DATE_FIELD, inputDateFormat);
420     }
421 
422     /**
423      * <p>setFormat.</p>
424      *
425      * @param formatKey a {@link org.jdatepicker.AbstractComponentFormat.Key} object.
426      * @param format a {@link java.text.DateFormat} object.
427      */
428     public void setFormat(AbstractComponentFormat.Key formatKey, DateFormat format) {
429         dateComponentFormatter.setFormat(formatKey, format);
430     }
431 
432     /**
433      * <p>setColor.</p>
434      *
435      * @param colorKey a {@link org.jdatepicker.AbstractComponentColor.Key} object.
436      * @param color a {@link java.awt.Color} object.
437      */
438     public void setColor(AbstractComponentColor.Key colorKey, Color color) {
439         datePanel.getColors().setColor(colorKey, color);
440     }
441 
442     private void fireActionPerformed() {
443         for (ActionListener actionListener : actionListeners) {
444             actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, COMMIT_DATE));
445         }
446     }
447 
448     /**
449      * This internal class hides the public event methods from the outside
450      */
451     private class InternalEventHandler implements ActionListener, HierarchyBoundsListener, PropertyChangeListener, AWTEventListener {
452 
453         private boolean modelAdjusting;
454 
455         public void ancestorMoved(HierarchyEvent e) {
456             hidePopup();
457         }
458 
459         public void ancestorResized(HierarchyEvent e) {
460             hidePopup();
461         }
462 
463         public void actionPerformed(ActionEvent e) {
464             if (e.getSource() == button) {
465                 if (popup == null) {
466                     showPopup();
467                 } else {
468                     hidePopup();
469                 }
470             } else if (e.getSource() == datePanel) {
471                 hidePopup();
472             }
473         }
474 
475         public void propertyChange(PropertyChangeEvent evt) {
476 
477             if (modelAdjusting) {
478                 return;
479             }
480 
481             try {
482                 modelAdjusting = true;
483 
484                 if (evt.getSource() == formattedTextField) {
485                     // Short circuit if the following cases are found
486                     if (evt.getOldValue() == null && evt.getNewValue() == null) {
487                         return;
488                     }
489                     if (evt.getOldValue() != null && evt.getOldValue().equals(evt.getNewValue())) {
490                         return;
491                     }
492                     if (!formattedTextField.isEditable()) {
493                         return;
494                     }
495 
496                     // If the field is editable and we need to parse the date entered
497                     if (evt.getNewValue() instanceof Date) {
498                         Date value = (Date) evt.getNewValue();
499                         DateModel tempModel = new DateModel(value);
500                         // check constraints
501                         if (!datePanel.checkConstraints(tempModel)) {
502                             // rollback
503                             formattedTextField.setValue(evt.getOldValue());
504                             return;
505                         }
506                         getModel().setDate(tempModel.getDate());
507 
508                     } else {
509 
510                         // Clearing textfield will also fire change event
511                         getModel().setDate(null);
512                     }
513 
514                     // fire action
515                     fireActionPerformed();
516 
517                 } else if (evt.getSource() == getModel()) {
518 
519                     if (DateModel.PROPERTY_VALUE.equals(evt.getPropertyName())) {
520                         formattedTextField.setValue(getModel().getDate());
521                     }
522 
523                 }
524 
525             } finally {
526                 modelAdjusting = false;
527             }
528         }
529 
530         public void eventDispatched(AWTEvent event) {
531             if (MouseEvent.MOUSE_CLICKED == event.getID() && event.getSource() != button) {
532                 Set<Component> components = getAllComponents(datePanel);
533                 boolean clickInPopup = false;
534                 for (Component component : components) {
535                     if (event.getSource() == component) {
536                         clickInPopup = true;
537                     }
538                 }
539                 if (!clickInPopup) {
540                     hidePopup();
541                 }
542             }
543         }
544 
545     }
546 
547 }