1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 package net.sourceforge.jdatebutton.swing;
24
25 import java.awt.BorderLayout;
26 import java.awt.Color;
27 import java.awt.Component;
28 import java.awt.Font;
29 import java.awt.GridLayout;
30 import java.awt.Insets;
31 import java.awt.Window;
32 import java.awt.event.ActionEvent;
33 import java.awt.event.ActionListener;
34 import java.awt.event.KeyEvent;
35 import java.awt.event.MouseAdapter;
36 import java.awt.event.MouseEvent;
37 import java.awt.event.WindowAdapter;
38 import java.awt.event.WindowEvent;
39 import java.time.DayOfWeek;
40 import java.time.LocalDate;
41 import java.time.format.TextStyle;
42 import java.util.Calendar;
43 import java.util.Locale;
44 import java.util.ResourceBundle;
45
46 import javax.swing.AbstractAction;
47 import javax.swing.ActionMap;
48 import javax.swing.BorderFactory;
49 import javax.swing.Box;
50 import javax.swing.BoxLayout;
51 import javax.swing.InputMap;
52 import javax.swing.JButton;
53 import javax.swing.JComponent;
54 import javax.swing.JDialog;
55 import javax.swing.JLabel;
56 import javax.swing.JOptionPane;
57 import javax.swing.JPanel;
58 import javax.swing.KeyStroke;
59 import javax.swing.SwingConstants;
60 import javax.swing.border.Border;
61
62
63
64
65
66
67
68 public class JDateDialog
69 extends JDialog {
70
71
72
73 private static final long serialVersionUID = 2518033527621472786L;
74
75
76
77
78 private static final String FONT_NAME = "Courier";
79
80
81
82
83 private static final int FONT_SIZE = 10;
84
85
86
87
88 private static final Font FONT_STANDARD = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
89
90
91
92
93 private static final Font FONT_INACTIVE = new Font(FONT_NAME, Font.ITALIC, FONT_SIZE);
94
95
96
97
98 private static final Font FONT_SELECTED = new Font(FONT_NAME, Font.BOLD, FONT_SIZE);
99
100
101
102
103 private static final ResourceBundle NLS_BUNDLE = ResourceBundle.getBundle(JDateDialog.class.getName());
104
105
106
107
108 public static final String NLS_CURRENTDAY = NLS_BUNDLE.getString("CurrentDay");
109
110
111
112
113 public static final String NLS_SELECTEDDAY = NLS_BUNDLE.getString("SelectedDay");
114
115
116
117
118 public static final String NLS_NEXTMONTH = NLS_BUNDLE.getString("NextMonth");
119
120
121
122
123 public static final String NLS_PREVMONTH = NLS_BUNDLE.getString("PreviousMonth");
124
125
126
127
128 public static final String NLS_NEXTYEAR = NLS_BUNDLE.getString("NextYear");
129
130
131
132
133 public static final String NLS_PREVYEAR = NLS_BUNDLE.getString("PreviousYear");
134
135
136
137
138 public static final String NLS_NULLSELECT = NLS_BUNDLE.getString("NullSelect");
139
140
141
142
143 private static final String ACTION_ESCAPE = "Escape";
144
145
146
147
148 private final PanelMonth theDaysPanel;
149
150
151
152
153 private final PanelNavigation theNavigation;
154
155
156
157
158 private final JButton theNullButton;
159
160
161
162
163 private boolean isNullActive = false;
164
165
166
167
168 private final transient JDateConfig theConfig;
169
170
171
172
173 private boolean doBuildNames = true;
174
175
176
177
178 private final JPanel theContainer;
179
180
181
182
183 private boolean haveSelected = false;
184
185
186
187
188
189
190 protected JDateDialog(final Component pParent,
191 final JDateConfig pConfig) {
192
193 super(JOptionPane.getFrameForComponent(pParent), false);
194
195
196 setUndecorated(true);
197
198
199 theConfig = pConfig;
200
201
202 theDaysPanel = new PanelMonth(this);
203 theNavigation = new PanelNavigation(this);
204
205
206 theNullButton = new JButton(NLS_NULLSELECT);
207 theNullButton.addActionListener(new ButtonHandler());
208
209
210 theContainer = new JPanel(new BorderLayout());
211 theContainer.setBorder(BorderFactory.createLineBorder(Color.black));
212 theContainer.add(theNavigation, BorderLayout.NORTH);
213 theContainer.add(theDaysPanel, BorderLayout.CENTER);
214 setContentPane(theContainer);
215 pack();
216
217
218 initialiseMonth();
219
220
221 handleEscapeKey(theContainer);
222
223
224 addWindowFocusListener(new CalendarFocus());
225
226
227 setLocationRelativeTo(pParent);
228 }
229
230
231
232
233 protected void doBuildNames() {
234 doBuildNames = true;
235 }
236
237
238
239
240
241 public boolean haveSelected() {
242 return haveSelected;
243 }
244
245
246
247
248
249 public JDateConfig getDateConfig() {
250 return theConfig;
251 }
252
253
254
255
256 private void buildMonth() {
257
258 theNavigation.buildMonth();
259 theDaysPanel.buildMonth();
260 }
261
262
263
264
265
266 private void setSelected(final int pDay) {
267
268 theConfig.setSelectedDay(pDay);
269
270
271 haveSelected = true;
272
273
274 setVisible(false);
275 }
276
277
278
279
280 private void reSizeDialog() {
281 pack();
282 }
283
284
285
286
287 private void initialiseMonth() {
288
289 theConfig.initialiseCurrent();
290
291
292 if (doBuildNames) {
293 theDaysPanel.buildDayNames();
294 }
295 doBuildNames = false;
296
297
298 buildMonth();
299
300
301 if (isNullActive != theConfig.allowNullDateSelection()) {
302
303 if (!isNullActive) {
304 theContainer.add(theNullButton, BorderLayout.SOUTH);
305 } else {
306 theContainer.remove(theNullButton);
307 }
308
309
310 isNullActive = theConfig.allowNullDateSelection();
311 reSizeDialog();
312 }
313 }
314
315 @Override
316 public void setVisible(final boolean bVisible) {
317
318 if (bVisible) {
319
320 haveSelected = false;
321
322
323 initialiseMonth();
324
325
326 super.setVisible(true);
327 requestFocus();
328
329
330 } else {
331 super.setVisible(bVisible);
332 }
333 }
334
335
336
337
338
339 private void handleEscapeKey(final JPanel pPane) {
340
341 ActionMap myAction = pPane.getActionMap();
342 InputMap myInput = pPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
343
344
345 myInput.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ACTION_ESCAPE);
346 myAction.put(ACTION_ESCAPE, new CalendarAction());
347 }
348
349
350
351
352 private void closeNonModal() {
353 setVisible(false);
354 }
355
356
357
358
359 private class CalendarAction
360 extends AbstractAction {
361
362
363
364 private static final long serialVersionUID = 5464442251457102478L;
365
366 @Override
367 public void actionPerformed(final ActionEvent e) {
368 closeNonModal();
369 }
370 }
371
372
373
374
375 private class CalendarFocus
376 extends WindowAdapter {
377 @Override
378 public void windowLostFocus(final WindowEvent e) {
379 Window myOppo = e.getOppositeWindow();
380
381
382
383
384
385 if (myOppo != null) {
386 closeNonModal();
387 }
388 }
389 }
390
391
392
393
394 private class ButtonHandler
395 implements ActionListener {
396 @Override
397 public void actionPerformed(final ActionEvent e) {
398
399 Object src = e.getSource();
400
401
402 if (theNullButton.equals(src)) {
403
404 setSelected(-1);
405 }
406 }
407 }
408
409
410
411
412 private static final class PanelNavigation
413 extends JPanel {
414
415
416
417 private static final long serialVersionUID = 4399207012690467687L;
418
419
420
421
422 private final JDateDialog theDialog;
423
424
425
426
427 private final transient JDateConfig theConfig;
428
429
430
431
432 private final JLabel theDateLabel;
433
434
435
436
437 private final JButton thePrevMonthButton;
438
439
440
441
442 private final JButton theNextMonthButton;
443
444
445
446
447 private final JButton thePrevYearButton;
448
449
450
451
452 private final JButton theNextYearButton;
453
454
455
456
457
458 private PanelNavigation(final JDateDialog pDialog) {
459
460 theDialog = pDialog;
461
462
463 theConfig = pDialog.getDateConfig();
464
465
466 theDateLabel = new JLabel();
467
468
469 thePrevMonthButton = new JButton(ArrowIcon.LEFT);
470 theNextMonthButton = new JButton(ArrowIcon.RIGHT);
471 thePrevYearButton = new JButton(ArrowIcon.DOUBLELEFT);
472 theNextYearButton = new JButton(ArrowIcon.DOUBLERIGHT);
473
474
475 theNextMonthButton.setToolTipText(NLS_NEXTMONTH);
476 thePrevMonthButton.setToolTipText(NLS_PREVMONTH);
477 theNextYearButton.setToolTipText(NLS_NEXTYEAR);
478 thePrevYearButton.setToolTipText(NLS_PREVYEAR);
479
480
481 NavigateListener myListener = new NavigateListener();
482 thePrevMonthButton.addActionListener(myListener);
483 theNextMonthButton.addActionListener(myListener);
484 thePrevYearButton.addActionListener(myListener);
485 theNextYearButton.addActionListener(myListener);
486
487
488 thePrevMonthButton.setMargin(new Insets(1, 1, 1, 1));
489 theNextMonthButton.setMargin(new Insets(1, 1, 1, 1));
490 thePrevYearButton.setMargin(new Insets(1, 1, 1, 1));
491 theNextYearButton.setMargin(new Insets(1, 1, 1, 1));
492
493
494 setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
495 add(thePrevYearButton);
496 add(Box.createHorizontalStrut(1));
497 add(thePrevMonthButton);
498 add(Box.createHorizontalGlue());
499 add(theDateLabel);
500 add(Box.createHorizontalGlue());
501 add(theNextMonthButton);
502 add(Box.createHorizontalStrut(1));
503 add(theNextYearButton);
504 }
505
506
507
508
509 private void buildMonth() {
510
511 LocalDate myBase = theConfig.getCurrentMonth();
512 Locale myLocale = theConfig.getLocale();
513
514
515 String myMonth = myBase.getMonth().getDisplayName(TextStyle.FULL, myLocale);
516 String myYear = Integer.toString(myBase.getYear());
517
518
519 if (theConfig.doPretty()) {
520
521 myMonth = theConfig.capitaliseString(myMonth);
522 }
523
524
525 theDateLabel.setText(myMonth
526 + ", "
527 + myYear);
528
529
530 LocalDate myEarliest = theConfig.getEarliestDate();
531 LocalDate myLatest = theConfig.getLatestDate();
532
533
534 thePrevMonthButton.setEnabled(!JDateConfig.isSameMonth(myEarliest, myBase));
535 thePrevYearButton.setEnabled(!JDateConfig.isSameYear(myEarliest, myBase));
536 theNextMonthButton.setEnabled(!JDateConfig.isSameMonth(myLatest, myBase));
537 theNextYearButton.setEnabled(!JDateConfig.isSameYear(myLatest, myBase));
538 }
539
540
541
542
543 private class NavigateListener
544 implements ActionListener {
545 @Override
546 public void actionPerformed(final ActionEvent e) {
547
548 Object src = e.getSource();
549
550
551 if (thePrevMonthButton.equals(src)) {
552
553 theConfig.previousMonth();
554 theDialog.buildMonth();
555 } else if (theNextMonthButton.equals(src)) {
556
557 theConfig.nextMonth();
558 theDialog.buildMonth();
559 } else if (thePrevYearButton.equals(src)) {
560
561 theConfig.previousYear();
562 theDialog.buildMonth();
563 } else if (theNextYearButton.equals(src)) {
564
565 theConfig.nextYear();
566 theDialog.buildMonth();
567 }
568 }
569 }
570 }
571
572
573
574
575 private static final class PanelMonth
576 extends JPanel {
577
578
579
580 private static final long serialVersionUID = 4003892701953050327L;
581
582
583
584
585 private static final int DAYS_IN_WEEK = 7;
586
587
588
589
590 private static final int MAX_WEEKS_IN_MONTH = 6;
591
592
593
594
595 private final DayOfWeek[] theDaysOfWk = new DayOfWeek[DAYS_IN_WEEK];
596
597
598
599
600 private final JLabel[] theHdrs = new JLabel[DAYS_IN_WEEK];
601
602
603
604
605 private final PanelDay[][] theDays = new PanelDay[MAX_WEEKS_IN_MONTH][DAYS_IN_WEEK];
606
607
608
609
610 private final JDateDialog theDialog;
611
612
613
614
615 private final transient JDateConfig theConfig;
616
617
618
619
620 private int theNumRows = MAX_WEEKS_IN_MONTH;
621
622
623
624
625
626 private PanelMonth(final JDateDialog pDialog) {
627
628 theDialog = pDialog;
629
630
631 theConfig = pDialog.getDateConfig();
632
633
634 GridLayout myLayout = new GridLayout();
635 myLayout.setColumns(DAYS_IN_WEEK);
636 myLayout.setRows(0);
637 setLayout(myLayout);
638
639
640 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
641
642 JLabel myLabel = new JLabel();
643 theHdrs[iCol] = myLabel;
644
645
646 myLabel.setHorizontalAlignment(SwingConstants.CENTER);
647 myLabel.setBackground(Color.lightGray);
648 myLabel.setOpaque(true);
649
650
651 add(myLabel);
652 }
653
654
655 for (int iRow = 0; iRow < MAX_WEEKS_IN_MONTH; iRow++) {
656 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
657 PanelDay myDay = new PanelDay(pDialog);
658 theDays[iRow][iCol] = myDay;
659 add(myDay);
660 }
661 }
662 }
663
664
665
666
667
668 private void reSizeRows(final int iNumRows) {
669
670 while (iNumRows < theNumRows) {
671
672 theNumRows--;
673
674
675 for (PanelDay day : theDays[theNumRows]) {
676
677 remove(day);
678 }
679 }
680
681
682 while (iNumRows > theNumRows) {
683
684 for (PanelDay day : theDays[theNumRows]) {
685
686 add(day);
687 }
688
689
690 theNumRows++;
691 }
692
693
694 theDialog.reSizeDialog();
695 }
696
697
698
699
700
701
702 private static boolean isWeekend(final DayOfWeek pDoW) {
703 switch (pDoW) {
704 case SATURDAY:
705 case SUNDAY:
706 return true;
707 default:
708 return false;
709 }
710 }
711
712
713
714
715
716
717 private int getDayColumn(final DayOfWeek pDoW) {
718 for (int i = 0; i < DAYS_IN_WEEK; i++) {
719 if (theDaysOfWk[i] == pDoW) {
720 return i;
721 }
722 }
723 return -1;
724 }
725
726
727
728
729 private void buildMonth() {
730 int iRow = 0;
731 int iCol = 0;
732
733
734 LocalDate myCurr = theConfig.getCurrentMonth();
735 int iMonth = myCurr.getMonthValue();
736
737
738 DayOfWeek myWeekDay = myCurr.getDayOfWeek();
739 int iFirstCol = getDayColumn(myWeekDay);
740
741
742 int iCurrent = theConfig.getCurrentDay();
743 int iSelected = theConfig.getSelectedDay();
744 int iEarliest = theConfig.getEarliestDay();
745 int iLatest = theConfig.getLatestDay();
746
747
748 if (iFirstCol > 0) {
749 myCurr = myCurr.minusDays(iFirstCol);
750 }
751
752
753 for (int iDay = myCurr.getDayOfMonth(); iCol < iFirstCol; iCol++, iDay++, myCurr = myCurr.plusDays(1)) {
754
755 PanelDay myLabel = theDays[0][iCol];
756
757
758 myLabel.resetDay(false);
759 myLabel.setDay(iDay, false);
760 }
761
762
763 for (int iDay = 1; iMonth == myCurr.getMonthValue(); iCol++, iDay++, myCurr = myCurr.plusDays(1)) {
764
765 if (iCol > MAX_WEEKS_IN_MONTH) {
766 iRow++;
767 iCol = 0;
768 }
769
770
771 PanelDay myLabel = theDays[iRow][iCol];
772
773
774 myLabel.resetDay(true);
775 if (isWeekend(myCurr.getDayOfWeek())) {
776 myLabel.setWeekend();
777 }
778 if (iCurrent == iDay) {
779 myLabel.setCurrent();
780 }
781 if (iSelected == iDay) {
782 myLabel.setSelected();
783 }
784
785
786 boolean isSelectable = true;
787 if ((iEarliest > 0)
788 && (iDay < iEarliest)) {
789 isSelectable = false;
790 } else if ((iLatest > 0)
791 && (iDay > iLatest)) {
792 isSelectable = false;
793 }
794
795
796 myLabel.setDay(iDay, isSelectable);
797 }
798
799
800 for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
801
802 PanelDay myLabel = theDays[iRow][iCol];
803
804
805 myLabel.resetDay(false);
806 myLabel.setDay(iDay, false);
807 }
808
809
810 reSizeRows(iRow + 1);
811 }
812
813
814
815
816 private void buildDayNames() {
817
818 Locale myLocale = theConfig.getLocale();
819 Calendar myDate = Calendar.getInstance(myLocale);
820 int myStart = myDate.getFirstDayOfWeek();
821 if (myStart == Calendar.SUNDAY) {
822 myStart += DAYS_IN_WEEK;
823 }
824
825
826 DayOfWeek myDoW = DayOfWeek.of(myStart - 1);
827 for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
828
829 theDaysOfWk[iDay] = myDoW;
830 }
831
832
833 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
834
835 JLabel myLabel = theHdrs[iCol];
836
837
838 myDoW = theDaysOfWk[iCol];
839 String myName = myDoW.getDisplayName(TextStyle.SHORT, myLocale);
840
841
842 int iMaxDayLen = theConfig.getMaxDayLen();
843 if (myName.length() > iMaxDayLen) {
844
845 myName = theConfig.doShrinkFromRight()
846 ? myName.substring(0, iMaxDayLen)
847 : myName.substring(myName.length()
848 - iMaxDayLen);
849 }
850
851
852 if (theConfig.doPretty()) {
853
854 myName = theConfig.capitaliseString(myName);
855 }
856
857
858 myLabel.setText(myName);
859
860
861 myLabel.setForeground(isWeekend(myDoW)
862 ? Color.red
863 : Color.black);
864 }
865 }
866 }
867
868
869
870
871 private static final class PanelDay
872 extends JLabel {
873
874
875
876 private static final long serialVersionUID = -5636278095729007866L;
877
878
879
880
881 private final JDateDialog theDialog;
882
883
884
885
886 private static final Border BORDER_STD = BorderFactory.createEmptyBorder();
887
888
889
890
891 private static final Border BORDER_SEL = BorderFactory.createLineBorder(Color.green.darker());
892
893
894
895
896 private static final Border BORDER_HLT = BorderFactory.createLineBorder(Color.orange);
897
898
899
900
901 private int theDay = -1;
902
903
904
905
906 private boolean isSelectable = false;
907
908
909
910
911 private Font theFont = null;
912
913
914
915
916 private Color theForeGround = null;
917
918
919
920
921 private Color theBackGround = null;
922
923
924
925
926 private Border theBorder = null;
927
928
929
930
931 private String theToolTip = null;
932
933
934
935
936
937 private PanelDay(final JDateDialog pDialog) {
938
939 theDialog = pDialog;
940
941
942 setHorizontalAlignment(SwingConstants.CENTER);
943 setOpaque(true);
944 addMouseListener(new CalendarMouse(this));
945 }
946
947
948
949
950
951
952 private void setDay(final int pDay,
953 final boolean pSelectable) {
954
955 theDay = pDay;
956 isSelectable = pSelectable;
957
958
959 if (pDay > 0) {
960 setText(Integer.toString(theDay));
961 } else {
962 setText("");
963 }
964
965
966 setFont(theFont);
967 setForeground(theForeGround);
968 setBackground(theBackGround);
969 setBorder(theBorder);
970 setToolTipText(theToolTip);
971
972
973 setEnabled(isSelectable);
974 }
975
976
977
978
979
980 private void resetDay(final boolean isActive) {
981
982 theFont = isActive
983 ? FONT_STANDARD
984 : FONT_INACTIVE;
985 theForeGround = Color.black;
986 theBackGround = Color.white;
987 theBorder = BORDER_STD;
988 theToolTip = null;
989 isSelectable = isActive;
990 }
991
992
993
994
995 private void setWeekend() {
996
997 theForeGround = Color.red;
998 }
999
1000
1001
1002
1003 private void setCurrent() {
1004
1005 theForeGround = Color.blue;
1006 theBackGround = Color.gray;
1007 theToolTip = NLS_CURRENTDAY;
1008 }
1009
1010
1011
1012
1013 private void setSelected() {
1014
1015 theFont = FONT_SELECTED;
1016 theForeGround = Color.green.darker();
1017 theBackGround = Color.green.brighter();
1018 theBorder = BORDER_SEL;
1019 theToolTip = NLS_SELECTEDDAY;
1020 }
1021
1022
1023
1024
1025 private final class CalendarMouse
1026 extends MouseAdapter {
1027
1028
1029
1030 private final PanelDay theOwner;
1031
1032
1033
1034
1035
1036 private CalendarMouse(final PanelDay pOwner) {
1037
1038 theOwner = pOwner;
1039 }
1040
1041 @Override
1042 public void mouseClicked(final MouseEvent e) {
1043
1044 if (isSelectable) {
1045 theDialog.setSelected(theDay);
1046 }
1047 }
1048
1049 @Override
1050 public void mouseEntered(final MouseEvent e) {
1051
1052 if (isSelectable) {
1053 theOwner.setBorder(BORDER_HLT);
1054 }
1055 }
1056
1057 @Override
1058 public void mouseExited(final MouseEvent e) {
1059
1060 if (isSelectable) {
1061 theOwner.setBorder(theBorder);
1062 }
1063 }
1064 }
1065 }
1066 }