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.time.LocalDate;
63 import java.time.ZoneId;
64 import java.util.Date;
65 import java.util.HashSet;
66 import java.util.Set;
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 public class JLocalDatePicker extends JComponent {
84
85 private static final long serialVersionUID = 2814777654384974503L;
86
87
88
89
90 public static final String COMMIT_DATE = JLocalDatePanel.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 JLocalDatePanel datePanel;
101
102 private final Set<ActionListener> actionListeners;
103 private final AWTEventListener awtEventListener;
104
105
106
107
108 public JLocalDatePicker() {
109 this(new JLocalDatePanel());
110 }
111
112
113
114
115
116
117 public JLocalDatePicker(LocalDateModel model) {
118 this(new JLocalDatePanel(model));
119 }
120
121
122
123
124
125
126
127 private JLocalDatePicker(JLocalDatePanel 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 setTextFieldValue(datePanel.getModel().getLocalDate());
140 add(formattedTextField, BorderLayout.CENTER);
141
142
143 button = createButton();
144 add(button, BorderLayout.LINE_END);
145
146
147 addHierarchyBoundsListener(new HierarchyBoundsListener() {
148 @Override
149 public void ancestorMoved(HierarchyEvent e) {
150 hidePopup();
151 }
152
153 @Override
154 public void ancestorResized(HierarchyEvent e) {
155 hidePopup();
156 }
157 });
158
159
160 ActionListener actionListener = e -> {
161 if (e.getSource() == button) {
162 if (popup == null) {
163 showPopup();
164 } else {
165 hidePopup();
166 }
167 } else if (e.getSource() == datePanel) {
168 hidePopup();
169 }
170 };
171 button.addActionListener(actionListener);
172 datePanel.addActionListener(actionListener);
173
174 PropertyChangeListener propertyChangeListener = new PropertyChangeListener() {
175
176 private boolean modelAdjusting;
177
178 @Override
179 public void propertyChange(PropertyChangeEvent evt) {
180
181 if (modelAdjusting) {
182 return;
183 }
184
185 try {
186 modelAdjusting = true;
187
188 if (evt.getSource() == formattedTextField) {
189
190 if (evt.getOldValue() == null && evt.getNewValue() == null) {
191 return;
192 }
193 if (evt.getOldValue() != null && evt.getOldValue().equals(evt.getNewValue())) {
194 return;
195 }
196 if (!formattedTextField.isEditable()) {
197 return;
198 }
199
200
201 if (evt.getNewValue() != null) {
202
203 LocalDate localDate = null;
204 if (evt.getNewValue() instanceof Date) {
205
206 Date value = (Date) evt.getNewValue();
207 localDate = value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
208
209 } else if (evt.getNewValue() instanceof LocalDate) {
210 localDate = (LocalDate) evt.getNewValue();
211 }
212 LocalDateModel tempModel = new LocalDateModel(localDate);
213
214
215 if (!datePanel.checkConstraints(tempModel)) {
216
217 setTextFieldValue(evt.getOldValue());
218 return;
219 }
220 getModel().setLocalDate(tempModel.getLocalDate());
221 } else {
222
223
224 getModel().setLocalDate(null);
225 }
226
227
228 fireActionPerformed();
229
230 } else if (evt.getSource() == getModel()) {
231
232 if (LocalDateModel.PROPERTY_VALUE.equals(evt.getPropertyName())) {
233 setTextFieldValue(getModel().getLocalDate());
234 }
235
236 }
237
238 } finally {
239 modelAdjusting = false;
240 }
241 }
242 };
243 formattedTextField.addPropertyChangeListener("value", propertyChangeListener);
244 datePanel.getModel().addPropertyChangeListener(propertyChangeListener);
245
246 awtEventListener = event -> {
247 if (MouseEvent.MOUSE_CLICKED == event.getID() && event.getSource() != button) {
248 Set<Component> components = getAllComponents(datePanel);
249 boolean clickInPopup = false;
250 for (Component component : components) {
251 if (event.getSource() == component) {
252 clickInPopup = true;
253 }
254 }
255 if (!clickInPopup) {
256 boolean hidden = hidePopup();
257 firePropertyChange(POPUP_LOST, false, hidden);
258 }
259 }
260 };
261 }
262
263
264
265
266
267
268 protected JFormattedTextField createTextField() {
269 dateComponentFormatter = new DateComponentFormatter();
270 JFormattedTextField textField = new JFormattedTextField(dateComponentFormatter);
271 textField.setEditable(false);
272 return textField;
273 }
274
275 protected void setTextFieldValue(Object value) {
276 if (value instanceof LocalDate) {
277 formattedTextField.setValue(Date.from(((LocalDate) value).atStartOfDay(ZoneId.systemDefault()).toInstant()));
278 } else if (value instanceof Date) {
279 formattedTextField.setValue(value);
280 } else {
281 formattedTextField.setValue(null);
282 }
283 }
284
285
286
287
288
289
290 protected JButton createButton() {
291 JButton b = new JButton();
292 b.setRolloverEnabled(true);
293 b.setFocusable(false);
294 Icon icon = ComponentIconDefaults.getInstance().getPopupButtonIcon();
295 b.setIcon(icon);
296 b.setText(icon == null ? "..." : "");
297 return b;
298 }
299
300
301
302
303
304
305 public LocalDate getLocalDate() {
306 return getModel().getLocalDate();
307 }
308
309
310
311
312
313
314 public void setLocalDate(LocalDate date) {
315 getModel().setLocalDate(date);
316 }
317
318
319
320
321
322
323 public void addActionListener(ActionListener actionListener) {
324 actionListeners.add(actionListener);
325 datePanel.addActionListener(actionListener);
326 }
327
328
329
330
331
332
333 public void removeActionListener(ActionListener actionListener) {
334 actionListeners.remove(actionListener);
335 datePanel.removeActionListener(actionListener);
336 }
337
338
339
340
341
342
343 public LocalDateModel getModel() {
344 return datePanel.getModel();
345 }
346
347
348
349
350
351
352 public JFormattedTextField getEditor() {
353 return formattedTextField;
354 }
355
356
357
358
359
360
361 public void setTextEditable(boolean editable) {
362 formattedTextField.setEditable(editable);
363 }
364
365
366
367
368
369
370 public boolean isTextEditable() {
371 return formattedTextField.isEditable();
372 }
373
374
375
376
377
378
379 public void setButtonFocusable(boolean focusable) {
380 button.setFocusable(focusable);
381 }
382
383
384
385
386
387
388 public boolean getButtonFocusable() {
389 return button.isFocusable();
390 }
391
392
393
394
395 private void showPopup() {
396 if (popup == null) {
397 datePanel.setVisible(true);
398 Point loc = getLocationOnScreen();
399 popup = popupFactory.getPopup(this, datePanel, loc.x, loc.y + getHeight());
400 SwingUtilities.invokeLater(() -> {
401 popup.show();
402 Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListener, MouseEvent.MOUSE_PRESSED);
403 });
404 }
405 }
406
407
408
409
410
411
412 public boolean hidePopup() {
413 if (popup != null) {
414 Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListener);
415 popup.hide();
416 popup = null;
417 return true;
418 }
419 return false;
420 }
421
422 private Set<Component> getAllComponents(Component component) {
423 Set<Component> children = new HashSet<>();
424 children.add(component);
425 if (component instanceof Container) {
426 Container container = (Container) component;
427 Component[] components = container.getComponents();
428 for (Component component1 : components) {
429 children.addAll(getAllComponents(component1));
430 }
431 }
432 return children;
433 }
434
435
436
437
438
439
440 public boolean isDoubleClickAction() {
441 return datePanel.isDoubleClickAction();
442 }
443
444
445
446
447
448
449 public boolean isShowYearButtons() {
450 return datePanel.isShowYearButtons();
451 }
452
453
454
455
456
457
458 public void setDoubleClickAction(boolean doubleClickAction) {
459 datePanel.setDoubleClickAction(doubleClickAction);
460 }
461
462
463
464
465
466
467 public void setShowYearButtons(boolean showYearButtons) {
468 datePanel.setShowYearButtons(showYearButtons);
469 }
470
471
472
473
474
475
476 public void addDateSelectionConstraint(DateSelectionConstraint constraint) {
477 datePanel.addDateSelectionConstraint(constraint);
478 }
479
480
481
482
483
484
485 public void removeDateSelectionConstraint(DateSelectionConstraint constraint) {
486 datePanel.removeDateSelectionConstraint(constraint);
487 }
488
489
490
491
492 public void removeAllDateSelectionConstraints() {
493 datePanel.removeAllDateSelectionConstraints();
494 }
495
496
497
498
499
500
501 public Set<DateSelectionConstraint> getDateSelectionConstraints() {
502 return datePanel.getDateSelectionConstraints();
503 }
504
505
506
507
508 @Override
509 public void setVisible(boolean aFlag) {
510 if (!aFlag) {
511 hidePopup();
512 }
513 super.setVisible(aFlag);
514 }
515
516
517
518
519 @Override
520 public void setEnabled(boolean enabled) {
521 button.setEnabled(enabled);
522 datePanel.setEnabled(enabled);
523 formattedTextField.setEnabled(enabled);
524
525 super.setEnabled(enabled);
526 }
527
528
529
530
531
532
533
534 public void setDateFormat(String datePattern, Date default2DigitYearStart) {
535
536 SimpleDateFormat outputDateFormat = new SimpleDateFormat(datePattern);
537
538 SimpleDateFormat inputDateFormat;
539 if (datePattern.contains("yyyy")) {
540 inputDateFormat = new SimpleDateFormat(datePattern.replace("yyyy", "yy"));
541 } else {
542 inputDateFormat = outputDateFormat;
543 }
544 inputDateFormat.set2DigitYearStart(default2DigitYearStart);
545
546 setFormat(AbstractComponentFormat.Key.OUTPUT_DATE_FIELD, outputDateFormat);
547 setFormat(AbstractComponentFormat.Key.INPUT_DATE_FIELD, inputDateFormat);
548 }
549
550
551
552
553
554
555
556 public void setFormat(AbstractComponentFormat.Key formatKey, DateFormat format) {
557 dateComponentFormatter.setFormat(formatKey, format);
558 }
559
560
561
562
563
564
565
566 public void setColor(AbstractComponentColor.Key colorKey, Color color) {
567 datePanel.getColors().setColor(colorKey, color);
568 }
569
570 private void fireActionPerformed() {
571 for (ActionListener actionListener : actionListeners) {
572 actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, COMMIT_DATE));
573 }
574 }
575
576 }