1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package fr.ifremer.quadrige3.ui.swing.component.date;
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class JDatePicker extends JComponent {
84
85 private static final long serialVersionUID = 2814777654384974503L;
86
87
88
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
107
108 public JDatePicker() {
109 this(new JDatePanel());
110 }
111
112
113
114
115
116
117 public JDatePicker(DateModel model) {
118 this(new JDatePanel(model));
119 }
120
121
122
123
124
125
126
127 private JDatePicker(JDatePanel datePanel) {
128 this.datePanel = datePanel;
129 datePanel.setBorder(BorderFactory.createLineBorder(datePanel.getColors().getColor(ComponentColorDefaults.Key.POPUP_BORDER)));
130
131
132 actionListeners = new HashSet<>();
133
134
135 setLayout(new BorderLayout());
136
137
138 formattedTextField = createTextField();
139 add(formattedTextField, BorderLayout.CENTER);
140
141
142 button = createButton();
143 add(button, BorderLayout.LINE_END);
144
145
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
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
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
199 if (evt.getNewValue() instanceof Date) {
200 Date value = (Date) evt.getNewValue();
201 DateModel tempModel = new DateModel(value);
202
203 if (!datePanel.checkConstraints(tempModel)) {
204
205 formattedTextField.setValue(evt.getOldValue());
206 return;
207 }
208 getModel().setDate(tempModel.getDate());
209
210 } else {
211
212
213 getModel().setDate(null);
214 }
215
216
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
254
255
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
267
268
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
282
283
284
285 public Date getDate() {
286 return getModel().getDate();
287 }
288
289
290
291
292
293
294 public void setDate(Date date) {
295 getModel().setDate(date);
296 }
297
298
299
300
301
302
303 public void addActionListener(ActionListener actionListener) {
304 actionListeners.add(actionListener);
305 datePanel.addActionListener(actionListener);
306 }
307
308
309
310
311
312
313 public void removeActionListener(ActionListener actionListener) {
314 actionListeners.remove(actionListener);
315 datePanel.removeActionListener(actionListener);
316 }
317
318
319
320
321
322
323 public DateModel getModel() {
324 return datePanel.getModel();
325 }
326
327
328
329
330
331
332 public JFormattedTextField getEditor() {
333 return formattedTextField;
334 }
335
336
337
338
339
340
341 public void setTextEditable(boolean editable) {
342 formattedTextField.setEditable(editable);
343 }
344
345
346
347
348
349
350 public boolean isTextEditable() {
351 return formattedTextField.isEditable();
352 }
353
354
355
356
357
358
359 public void setButtonFocusable(boolean focusable) {
360 button.setFocusable(focusable);
361 }
362
363
364
365
366
367
368 public boolean getButtonFocusable() {
369 return button.isFocusable();
370 }
371
372
373
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
389
390
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
417
418
419
420 public boolean isDoubleClickAction() {
421 return datePanel.isDoubleClickAction();
422 }
423
424
425
426
427
428
429 public boolean isShowYearButtons() {
430 return datePanel.isShowYearButtons();
431 }
432
433
434
435
436
437
438 public void setDoubleClickAction(boolean doubleClickAction) {
439 datePanel.setDoubleClickAction(doubleClickAction);
440 }
441
442
443
444
445
446
447 public void setShowYearButtons(boolean showYearButtons) {
448 datePanel.setShowYearButtons(showYearButtons);
449 }
450
451
452
453
454
455
456 public void addDateSelectionConstraint(DateSelectionConstraint constraint) {
457 datePanel.addDateSelectionConstraint(constraint);
458 }
459
460
461
462
463
464
465 public void removeDateSelectionConstraint(DateSelectionConstraint constraint) {
466 datePanel.removeDateSelectionConstraint(constraint);
467 }
468
469
470
471
472 public void removeAllDateSelectionConstraints() {
473 datePanel.removeAllDateSelectionConstraints();
474 }
475
476
477
478
479
480
481 public Set<DateSelectionConstraint> getDateSelectionConstraints() {
482 return datePanel.getDateSelectionConstraints();
483 }
484
485
486
487
488 @Override
489 public void setVisible(boolean aFlag) {
490 if (!aFlag) {
491 hidePopup();
492 }
493 super.setVisible(aFlag);
494 }
495
496
497
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
510
511
512
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
532
533
534
535
536 public void setFormat(AbstractComponentFormat.Key formatKey, DateFormat format) {
537 dateComponentFormatter.setFormat(formatKey, format);
538 }
539
540
541
542
543
544
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 }