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 fr.ifremer.quadrige3.ui.swing.component.date;
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  import fr.ifremer.quadrige3.ui.swing.component.date.constraints.DateSelectionConstraint;
54  
55  import javax.swing.*;
56  import java.awt.*;
57  import java.awt.event.*;
58  import java.beans.PropertyChangeEvent;
59  import java.beans.PropertyChangeListener;
60  import java.text.DateFormat;
61  import java.text.SimpleDateFormat;
62  import java.util.Date;
63  import java.util.HashSet;
64  import java.util.Set;
65  
66  /**
67   * Created on 25 Mar 2004
68   * Refactored 21 Jun 2004
69   * Refactored 14 May 2009
70   * Refactored 16 April 2010
71   * Updated 26 April 2010
72   * Updated 10 August 2012
73   * Updated 6 Jun 2015
74   * Updated 20/11/2015
75   *
76   * @author Juan Heyns
77   * @author JC Oosthuizen
78   * @author Yue Huang
79   * @author Ludovic Pecquot
80   * <p>
81   * TODO this component will be deprecated soon , use JLocalDatePicker
82   */
83  public class JDatePicker extends JComponent {
84  
85      private static final long serialVersionUID = 2814777654384974503L;
86  
87      /**
88       * Constant <code>COMMIT_DATE="JDatePanel.COMMIT_DATE"</code>
89       */
90      public static final String COMMIT_DATE = JDatePanel.COMMIT_DATE;
91  
92      public static final String POPUP_LOST = "popupLost";
93  
94      private Popup popup;
95      private final FittingPopupFactory popupFactory = new FittingPopupFactory();
96      private final JFormattedTextField formattedTextField;
97      private DateComponentFormatter dateComponentFormatter;
98      private final JButton button;
99  
100     private final JDatePanel datePanel;
101 
102     private final Set<ActionListener> actionListeners;
103     private final AWTEventListener awtEventListener;
104 
105     /**
106      * Create a JDatePicker with a default calendar model.
107      */
108     public JDatePicker() {
109         this(new JDatePanel());
110     }
111 
112     /**
113      * Create a JDatePicker with a custom date model.
114      *
115      * @param model a custom date model
116      */
117     public JDatePicker(DateModel model) {
118         this(new JDatePanel(model));
119     }
120 
121     /**
122      * You are able to set the format of the date being displayed on the label.
123      * Formatting is described at:
124      *
125      * @param datePanel The DatePanel to use
126      */
127     private JDatePicker(JDatePanel datePanel) {
128         this.datePanel = datePanel;
129         datePanel.setBorder(BorderFactory.createLineBorder(datePanel.getColors().getColor(ComponentColorDefaults.Key.POPUP_BORDER)));
130 
131         //Initialise Variables
132         actionListeners = new HashSet<>();
133 
134         //Create Layout
135         setLayout(new BorderLayout());
136 
137         //Add and Configure TextField
138         formattedTextField = createTextField();
139         add(formattedTextField, BorderLayout.CENTER);
140 
141         //Add and Configure Button
142         button = createButton();
143         add(button, BorderLayout.LINE_END);
144 
145         //Add event listeners
146         addHierarchyBoundsListener(new HierarchyBoundsListener() {
147             @Override
148             public void ancestorMoved(HierarchyEvent e) {
149                 hidePopup();
150             }
151 
152             @Override
153             public void ancestorResized(HierarchyEvent e) {
154                 hidePopup();
155             }
156         });
157 //TODO        addAncestorListener(listener) ?
158 
159         ActionListener actionListener = e -> {
160             if (e.getSource() == button) {
161                 if (popup == null) {
162                     showPopup();
163                 } else {
164                     hidePopup();
165                 }
166             } else if (e.getSource() == datePanel) {
167                 hidePopup();
168             }
169         };
170         button.addActionListener(actionListener);
171         datePanel.addActionListener(actionListener);
172 
173         PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
174             private boolean modelAdjusting;
175 
176             @Override
177             public void propertyChange(PropertyChangeEvent evt) {
178 
179                 if (modelAdjusting) {
180                     return;
181                 }
182 
183                 try {
184                     modelAdjusting = true;
185 
186                     if (evt.getSource() == formattedTextField) {
187                         // Short circuit if the following cases are found
188                         if (evt.getOldValue() == null && evt.getNewValue() == null) {
189                             return;
190                         }
191                         if (evt.getOldValue() != null && evt.getOldValue().equals(evt.getNewValue())) {
192                             return;
193                         }
194                         if (!formattedTextField.isEditable()) {
195                             return;
196                         }
197 
198                         // If the field is editable and we need to parse the date entered
199                         if (evt.getNewValue() instanceof Date) {
200                             Date value = (Date) evt.getNewValue();
201                             DateModel tempModel = new DateModel(value);
202                             // check constraints
203                             if (!datePanel.checkConstraints(tempModel)) {
204                                 // rollback
205                                 formattedTextField.setValue(evt.getOldValue());
206                                 return;
207                             }
208                             getModel().setDate(tempModel.getDate());
209 
210                         } else {
211 
212                             // Clearing textfield will also fire change event
213                             getModel().setDate(null);
214                         }
215 
216                         // fire action
217                         fireActionPerformed();
218 
219                     } else if (evt.getSource() == getModel()) {
220 
221                         if (DateModel.PROPERTY_VALUE.equals(evt.getPropertyName())) {
222                             formattedTextField.setValue(getModel().getDate());
223                         }
224 
225                     }
226 
227                 } finally {
228                     modelAdjusting = false;
229                 }
230             }
231         };
232         formattedTextField.addPropertyChangeListener("value", propertyChangeListener);
233         datePanel.getModel().addPropertyChangeListener(propertyChangeListener);
234 
235         awtEventListener = event -> {
236             if (MouseEvent.MOUSE_CLICKED == event.getID() && event.getSource() != button) {
237                 Set<Component> components = getAllComponents(datePanel);
238                 boolean clickInPopup = false;
239                 for (Component component : components) {
240                     if (event.getSource() == component) {
241                         clickInPopup = true;
242                     }
243                 }
244                 if (!clickInPopup) {
245                     boolean hidden = hidePopup();
246                     firePropertyChange(POPUP_LOST, false, hidden);
247                 }
248             }
249         };
250     }
251 
252     /**
253      * <p>createTextField.</p>
254      *
255      * @return a {@link javax.swing.JFormattedTextField} object.
256      */
257     protected JFormattedTextField createTextField() {
258         dateComponentFormatter = new DateComponentFormatter();
259         JFormattedTextField textField = new JFormattedTextField(dateComponentFormatter);
260         textField.setValue(datePanel.getModel().getDate());
261         textField.setEditable(false);
262         return textField;
263     }
264 
265     /**
266      * <p>createButton.</p>
267      *
268      * @return a {@link javax.swing.JButton} object.
269      */
270     protected JButton createButton() {
271         JButton b = new JButton();
272         b.setRolloverEnabled(true);
273         b.setFocusable(false);
274         Icon icon = ComponentIconDefaults.getInstance().getPopupButtonIcon();
275         b.setIcon(icon);
276         b.setText(icon == null ? "..." : "");
277         return b;
278     }
279 
280     /**
281      * <p>getDate.</p>
282      *
283      * @return a {@link java.util.Date} object.
284      */
285     public Date getDate() {
286         return getModel().getDate();
287     }
288 
289     /**
290      * <p>setDate.</p>
291      *
292      * @param date a {@link java.util.Date} object.
293      */
294     public void setDate(Date date) {
295         getModel().setDate(date);
296     }
297 
298     /**
299      * <p>addActionListener.</p>
300      *
301      * @param actionListener a {@link java.awt.event.ActionListener} object.
302      */
303     public void addActionListener(ActionListener actionListener) {
304         actionListeners.add(actionListener);
305         datePanel.addActionListener(actionListener);
306     }
307 
308     /**
309      * <p>removeActionListener.</p>
310      *
311      * @param actionListener a {@link java.awt.event.ActionListener} object.
312      */
313     public void removeActionListener(ActionListener actionListener) {
314         actionListeners.remove(actionListener);
315         datePanel.removeActionListener(actionListener);
316     }
317 
318     /**
319      * <p>getModel.</p>
320      *
321      * @return a {@link DateModel} object.
322      */
323     public DateModel getModel() {
324         return datePanel.getModel();
325     }
326 
327     /**
328      * <p>getEditor.</p>
329      *
330      * @return a {@link javax.swing.JFormattedTextField} object.
331      */
332     public JFormattedTextField getEditor() {
333         return formattedTextField;
334     }
335 
336     /**
337      * <p>setTextEditable.</p>
338      *
339      * @param editable a boolean.
340      */
341     public void setTextEditable(boolean editable) {
342         formattedTextField.setEditable(editable);
343     }
344 
345     /**
346      * <p>isTextEditable.</p>
347      *
348      * @return a boolean.
349      */
350     public boolean isTextEditable() {
351         return formattedTextField.isEditable();
352     }
353 
354     /**
355      * <p>setButtonFocusable.</p>
356      *
357      * @param focusable a boolean.
358      */
359     public void setButtonFocusable(boolean focusable) {
360         button.setFocusable(focusable);
361     }
362 
363     /**
364      * <p>getButtonFocusable.</p>
365      *
366      * @return a boolean.
367      */
368     public boolean getButtonFocusable() {
369         return button.isFocusable();
370     }
371 
372     /**
373      * Called internally to popup the dates.
374      */
375     private void showPopup() {
376         if (popup == null) {
377             datePanel.setVisible(true);
378             Point loc = getLocationOnScreen();
379             popup = popupFactory.getPopup(this, datePanel, loc.x, loc.y + getHeight());
380             SwingUtilities.invokeLater(() -> {
381                 popup.show();
382                 Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListener, MouseEvent.MOUSE_PRESSED);
383             });
384         }
385     }
386 
387     /**
388      * Called internally to hide the popup dates.
389      *
390      * @return true if popup has been hidden
391      */
392     public boolean hidePopup() {
393         if (popup != null) {
394             Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListener);
395             popup.hide();
396             popup = null;
397             return true;
398         }
399         return false;
400     }
401 
402     private Set<Component> getAllComponents(Component component) {
403         Set<Component> children = new HashSet<>();
404         children.add(component);
405         if (component instanceof Container) {
406             Container container = (Container) component;
407             Component[] components = container.getComponents();
408             for (Component component1 : components) {
409                 children.addAll(getAllComponents(component1));
410             }
411         }
412         return children;
413     }
414 
415     /**
416      * <p>isDoubleClickAction.</p>
417      *
418      * @return a boolean.
419      */
420     public boolean isDoubleClickAction() {
421         return datePanel.isDoubleClickAction();
422     }
423 
424     /**
425      * <p>isShowYearButtons.</p>
426      *
427      * @return a boolean.
428      */
429     public boolean isShowYearButtons() {
430         return datePanel.isShowYearButtons();
431     }
432 
433     /**
434      * <p>setDoubleClickAction.</p>
435      *
436      * @param doubleClickAction a boolean.
437      */
438     public void setDoubleClickAction(boolean doubleClickAction) {
439         datePanel.setDoubleClickAction(doubleClickAction);
440     }
441 
442     /**
443      * <p>setShowYearButtons.</p>
444      *
445      * @param showYearButtons a boolean.
446      */
447     public void setShowYearButtons(boolean showYearButtons) {
448         datePanel.setShowYearButtons(showYearButtons);
449     }
450 
451     /**
452      * <p>addDateSelectionConstraint.</p>
453      *
454      * @param constraint a {@link DateSelectionConstraint} object.
455      */
456     public void addDateSelectionConstraint(DateSelectionConstraint constraint) {
457         datePanel.addDateSelectionConstraint(constraint);
458     }
459 
460     /**
461      * <p>removeDateSelectionConstraint.</p>
462      *
463      * @param constraint a {@link DateSelectionConstraint} object.
464      */
465     public void removeDateSelectionConstraint(DateSelectionConstraint constraint) {
466         datePanel.removeDateSelectionConstraint(constraint);
467     }
468 
469     /**
470      * <p>removeAllDateSelectionConstraints.</p>
471      */
472     public void removeAllDateSelectionConstraints() {
473         datePanel.removeAllDateSelectionConstraints();
474     }
475 
476     /**
477      * <p>getDateSelectionConstraints.</p>
478      *
479      * @return a {@link java.util.Set} object.
480      */
481     public Set<DateSelectionConstraint> getDateSelectionConstraints() {
482         return datePanel.getDateSelectionConstraints();
483     }
484 
485     /**
486      * {@inheritDoc}
487      */
488     @Override
489     public void setVisible(boolean aFlag) {
490         if (!aFlag) {
491             hidePopup();
492         }
493         super.setVisible(aFlag);
494     }
495 
496     /**
497      * {@inheritDoc}
498      */
499     @Override
500     public void setEnabled(boolean enabled) {
501         button.setEnabled(enabled);
502         datePanel.setEnabled(enabled);
503         formattedTextField.setEnabled(enabled);
504 
505         super.setEnabled(enabled);
506     }
507 
508     /**
509      * <p>setDateFormat.</p>
510      *
511      * @param datePattern            a {@link java.lang.String} object.
512      * @param default2DigitYearStart a {@link java.util.Date} object.
513      */
514     public void setDateFormat(String datePattern, Date default2DigitYearStart) {
515 
516         SimpleDateFormat outputDateFormat = new SimpleDateFormat(datePattern);
517 
518         SimpleDateFormat inputDateFormat;
519         if (datePattern.contains("yyyy")) {
520             inputDateFormat = new SimpleDateFormat(datePattern.replace("yyyy", "yy"));
521         } else {
522             inputDateFormat = outputDateFormat;
523         }
524         inputDateFormat.set2DigitYearStart(default2DigitYearStart);
525 
526         setFormat(AbstractComponentFormat.Key.OUTPUT_DATE_FIELD, outputDateFormat);
527         setFormat(AbstractComponentFormat.Key.INPUT_DATE_FIELD, inputDateFormat);
528     }
529 
530     /**
531      * <p>setFormat.</p>
532      *
533      * @param formatKey a {@link AbstractComponentFormat.Key} object.
534      * @param format    a {@link java.text.DateFormat} object.
535      */
536     public void setFormat(AbstractComponentFormat.Key formatKey, DateFormat format) {
537         dateComponentFormatter.setFormat(formatKey, format);
538     }
539 
540     /**
541      * <p>setColor.</p>
542      *
543      * @param colorKey a {@link AbstractComponentColor.Key} object.
544      * @param color    a {@link java.awt.Color} object.
545      */
546     public void setColor(AbstractComponentColor.Key colorKey, Color color) {
547         datePanel.getColors().setColor(colorKey, color);
548     }
549 
550     private void fireActionPerformed() {
551         for (ActionListener actionListener : actionListeners) {
552             actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, COMMIT_DATE));
553         }
554     }
555 
556 }