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.javafx;
24
25 import java.time.DayOfWeek;
26 import java.time.LocalDate;
27 import java.time.format.TextStyle;
28 import java.util.Calendar;
29 import java.util.Locale;
30 import java.util.ResourceBundle;
31
32 import javafx.beans.value.ChangeListener;
33 import javafx.beans.value.ObservableValue;
34 import javafx.collections.ObservableList;
35 import javafx.event.ActionEvent;
36 import javafx.event.EventHandler;
37 import javafx.scene.Node;
38 import javafx.scene.Scene;
39 import javafx.scene.control.Button;
40 import javafx.scene.control.Label;
41 import javafx.scene.control.Tooltip;
42 import javafx.scene.input.KeyCode;
43 import javafx.scene.input.KeyEvent;
44 import javafx.scene.input.MouseEvent;
45 import javafx.scene.layout.BorderPane;
46 import javafx.scene.layout.GridPane;
47 import javafx.scene.layout.HBox;
48 import javafx.scene.layout.Priority;
49 import javafx.scene.layout.Region;
50 import javafx.stage.Modality;
51 import javafx.stage.Stage;
52 import javafx.stage.StageStyle;
53
54
55
56
57
58
59
60 public class JDateDialog
61 extends Stage {
62
63
64
65 private static final String CSS_STYLE = JDateDialog.class.getResource("JDateDialog.css").toExternalForm();
66
67
68
69
70 private static final ResourceBundle NLS_BUNDLE = ResourceBundle.getBundle(JDateDialog.class.getName());
71
72
73
74
75 public static final String NLS_CURRENTDAY = NLS_BUNDLE.getString("CurrentDay");
76
77
78
79
80 public static final String NLS_SELECTEDDAY = NLS_BUNDLE.getString("SelectedDay");
81
82
83
84
85 public static final String NLS_NEXTMONTH = NLS_BUNDLE.getString("NextMonth");
86
87
88
89
90 public static final String NLS_PREVMONTH = NLS_BUNDLE.getString("PreviousMonth");
91
92
93
94
95 public static final String NLS_NEXTYEAR = NLS_BUNDLE.getString("NextYear");
96
97
98
99
100 public static final String NLS_PREVYEAR = NLS_BUNDLE.getString("PreviousYear");
101
102
103
104
105 public static final String NLS_NULLSELECT = NLS_BUNDLE.getString("NullSelect");
106
107
108
109
110 private final PanelMonth theDaysPanel;
111
112
113
114
115 private final PanelNavigation theNavigation;
116
117
118
119
120 private final Button theNullButton;
121
122
123
124
125 private boolean isNullActive = false;
126
127
128
129
130 private final JDateConfig theConfig;
131
132
133
134
135 private boolean doBuildNames = true;
136
137
138
139
140 private final BorderPane theContainer;
141
142
143
144
145 private boolean haveSelected = false;
146
147
148
149
150
151 public JDateDialog(final JDateConfig pConfig) {
152
153 super(StageStyle.UNDECORATED);
154 initModality(Modality.NONE);
155
156
157 theConfig = pConfig;
158
159
160 theDaysPanel = new PanelMonth(this);
161 theNavigation = new PanelNavigation(this);
162
163
164 theNullButton = new Button(NLS_NULLSELECT);
165 theNullButton.setMaxWidth(Double.MAX_VALUE);
166 theNullButton.addEventFilter(ActionEvent.ACTION, new ButtonHandler());
167
168
169 focusedProperty().addListener(new ChangeListener<Boolean>() {
170 @Override
171 public void changed(final ObservableValue<? extends Boolean> observable,
172 final Boolean oldValue,
173 final Boolean newValue) {
174 if (!newValue) {
175 closeNonModal();
176 }
177 }
178 });
179
180
181 theContainer = new BorderPane();
182 theContainer.setTop(theNavigation);
183 theContainer.setCenter(theDaysPanel);
184 Scene myScene = new Scene(theContainer);
185 ObservableList<String> mySheets = myScene.getStylesheets();
186 mySheets.add(CSS_STYLE);
187 setScene(myScene);
188
189
190 initialiseMonth();
191
192 theContainer.setOnKeyPressed(new EventHandler<KeyEvent>() {
193 @Override
194 public void handle(final KeyEvent t) {
195 if (t.getCode() == KeyCode.ESCAPE) {
196 closeNonModal();
197 }
198 }
199 });
200 }
201
202
203
204
205 protected void doBuildNames() {
206 doBuildNames = true;
207 }
208
209
210
211
212
213 public boolean haveSelected() {
214 return haveSelected;
215 }
216
217
218
219
220
221 public JDateConfig getDateConfig() {
222 return theConfig;
223 }
224
225
226
227
228 private void buildMonth() {
229
230 theNavigation.buildMonth();
231 theDaysPanel.buildMonth();
232 }
233
234
235
236
237
238 private void setSelected(final int pDay) {
239
240 theConfig.setSelectedDay(pDay);
241
242
243 haveSelected = true;
244
245
246 close();
247 }
248
249
250
251
252 private void reSizeDialog() {
253 sizeToScene();
254 }
255
256
257
258
259 private void initialiseMonth() {
260
261 theConfig.initialiseCurrent();
262
263
264 if (doBuildNames) {
265 theDaysPanel.buildDayNames();
266 }
267 doBuildNames = false;
268
269
270 buildMonth();
271
272
273 if (isNullActive != theConfig.allowNullDateSelection()) {
274
275 if (!isNullActive) {
276 theContainer.setBottom(theNullButton);
277 } else {
278 theContainer.setBottom(null);
279 }
280
281
282 isNullActive = theConfig.allowNullDateSelection();
283 reSizeDialog();
284 }
285 }
286
287
288
289
290 public void showDialog() {
291
292 haveSelected = false;
293
294
295 initialiseMonth();
296 show();
297 }
298
299
300
301
302 private void closeNonModal() {
303 close();
304 }
305
306
307
308
309 private class ButtonHandler
310 implements EventHandler<ActionEvent> {
311 @Override
312 public void handle(final ActionEvent e) {
313
314 Object src = e.getSource();
315
316
317 if (theNullButton.equals(src)) {
318
319 setSelected(-1);
320 }
321 }
322 }
323
324
325
326
327 private static final class PanelNavigation
328 extends HBox {
329
330
331
332 private static final String STYLE_BUTTON = "-jdd-button";
333
334
335
336
337 private static final String STYLE_TITLE = "-jdd-title";
338
339
340
341
342 private final JDateDialog theDialog;
343
344
345
346
347 private final JDateConfig theConfig;
348
349
350
351
352 private final Label theDateLabel;
353
354
355
356
357 private final Button thePrevMonthButton;
358
359
360
361
362 private final Button theNextMonthButton;
363
364
365
366
367 private final Button thePrevYearButton;
368
369
370
371
372 private final Button theNextYearButton;
373
374
375
376
377
378 private PanelNavigation(final JDateDialog pDialog) {
379
380 super(1.0);
381
382
383 theDialog = pDialog;
384
385
386 theConfig = pDialog.getDateConfig();
387
388
389 theDateLabel = new Label();
390 theDateLabel.getStyleClass().add(STYLE_TITLE);
391
392
393 thePrevMonthButton = new Button();
394 theNextMonthButton = new Button();
395 thePrevYearButton = new Button();
396 theNextYearButton = new Button();
397
398
399 thePrevMonthButton.setGraphic(ArrowIcon.LEFT.getArrow());
400 theNextMonthButton.setGraphic(ArrowIcon.RIGHT.getArrow());
401 thePrevYearButton.setGraphic(ArrowIcon.DOUBLELEFT.getArrow());
402 theNextYearButton.setGraphic(ArrowIcon.DOUBLERIGHT.getArrow());
403
404
405 theNextMonthButton.setTooltip(new Tooltip(NLS_NEXTMONTH));
406 thePrevMonthButton.setTooltip(new Tooltip(NLS_PREVMONTH));
407 theNextYearButton.setTooltip(new Tooltip(NLS_NEXTYEAR));
408 thePrevYearButton.setTooltip(new Tooltip(NLS_PREVYEAR));
409
410
411 NavigateListener myListener = new NavigateListener();
412 thePrevMonthButton.addEventFilter(ActionEvent.ACTION, myListener);
413 theNextMonthButton.addEventFilter(ActionEvent.ACTION, myListener);
414 thePrevYearButton.addEventFilter(ActionEvent.ACTION, myListener);
415 theNextYearButton.addEventFilter(ActionEvent.ACTION, myListener);
416
417
418 thePrevMonthButton.getStyleClass().add(STYLE_BUTTON);
419 theNextMonthButton.getStyleClass().add(STYLE_BUTTON);
420 thePrevYearButton.getStyleClass().add(STYLE_BUTTON);
421 theNextYearButton.getStyleClass().add(STYLE_BUTTON);
422
423
424 Region myStrut1 = new Region();
425 Region myStrut2 = new Region();
426 setHgrow(myStrut1, Priority.ALWAYS);
427 setHgrow(myStrut2, Priority.ALWAYS);
428
429
430 ObservableList<Node> myChildren = getChildren();
431 myChildren.add(thePrevYearButton);
432 myChildren.add(thePrevMonthButton);
433 myChildren.add(myStrut1);
434 myChildren.add(theDateLabel);
435 myChildren.add(myStrut2);
436 myChildren.add(theNextMonthButton);
437 myChildren.add(theNextYearButton);
438 }
439
440
441
442
443 private void buildMonth() {
444
445 LocalDate myBase = theConfig.getCurrentMonth();
446 Locale myLocale = theConfig.getLocale();
447
448
449 String myMonth = myBase.getMonth().getDisplayName(TextStyle.FULL, myLocale);
450 String myYear = Integer.toString(myBase.getYear());
451
452
453 theDateLabel.setText(myMonth
454 + ", "
455 + myYear);
456
457
458 LocalDate myEarliest = theConfig.getEarliestDate();
459 LocalDate myLatest = theConfig.getLatestDate();
460
461
462 thePrevMonthButton.setDisable(JDateConfig.isSameMonth(myEarliest, myBase));
463 thePrevYearButton.setDisable(JDateConfig.isSameYear(myEarliest, myBase));
464 theNextMonthButton.setDisable(JDateConfig.isSameMonth(myLatest, myBase));
465 theNextYearButton.setDisable(JDateConfig.isSameYear(myLatest, myBase));
466 }
467
468
469
470
471 private class NavigateListener
472 implements EventHandler<ActionEvent> {
473 @Override
474 public void handle(final ActionEvent e) {
475
476 Object src = e.getSource();
477
478
479 if (thePrevMonthButton.equals(src)) {
480
481 theConfig.previousMonth();
482 theDialog.buildMonth();
483 } else if (theNextMonthButton.equals(src)) {
484
485 theConfig.nextMonth();
486 theDialog.buildMonth();
487 } else if (thePrevYearButton.equals(src)) {
488
489 theConfig.previousYear();
490 theDialog.buildMonth();
491 } else if (theNextYearButton.equals(src)) {
492
493 theConfig.nextYear();
494 theDialog.buildMonth();
495 }
496 }
497 }
498 }
499
500
501
502
503 private static final class PanelMonth
504 extends GridPane {
505
506
507
508 private static final String STYLE_PANEL = "-jdd-month";
509
510
511
512
513 private static final String STYLE_HEADER = "-jdd-hdr";
514
515
516
517
518 private static final int DAYS_IN_WEEK = 7;
519
520
521
522
523 private static final int MAX_WEEKS_IN_MONTH = 6;
524
525
526
527
528 private static final int WIDTH_TILE = 22;
529
530
531
532
533 private final JDateDialog theDialog;
534
535
536
537
538 private final JDateConfig theConfig;
539
540
541
542
543 private final DayOfWeek[] theDaysOfWk = new DayOfWeek[DAYS_IN_WEEK];
544
545
546
547
548 private final Label[] theHdrs = new Label[DAYS_IN_WEEK];
549
550
551
552
553 private final PanelDay[][] theDays = new PanelDay[MAX_WEEKS_IN_MONTH][DAYS_IN_WEEK];
554
555
556
557
558 private int theNumRows = MAX_WEEKS_IN_MONTH;
559
560
561
562
563
564 private PanelMonth(final JDateDialog pDialog) {
565
566 theDialog = pDialog;
567
568
569 theConfig = pDialog.getDateConfig();
570
571
572 getStyleClass().add(STYLE_PANEL);
573
574
575 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
576 Label myDay = new Label();
577 myDay.setPrefWidth(WIDTH_TILE);
578 theHdrs[iCol] = myDay;
579 add(myDay, iCol, 0);
580 }
581
582
583 for (int iRow = 0; iRow < MAX_WEEKS_IN_MONTH; iRow++) {
584 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
585 PanelDay myDay = new PanelDay(theDialog);
586 theDays[iRow][iCol] = myDay;
587 add(myDay, iCol, iRow + 1);
588 }
589 }
590
591
592 buildDayNames();
593 }
594
595
596
597
598
599
600 private static boolean isWeekend(final DayOfWeek pDoW) {
601 switch (pDoW) {
602 case SATURDAY:
603 case SUNDAY:
604 return true;
605 default:
606 return false;
607 }
608 }
609
610
611
612
613
614
615 private int getDayColumn(final DayOfWeek pDoW) {
616 for (int i = 0; i < DAYS_IN_WEEK; i++) {
617 if (theDaysOfWk[i] == pDoW) {
618 return i;
619 }
620 }
621 return -1;
622 }
623
624
625
626
627 private void buildMonth() {
628
629 LocalDate myCurr = theConfig.getCurrentMonth();
630
631
632 int iCurrent = theConfig.getCurrentDay();
633 int iSelected = theConfig.getSelectedDay();
634 int iEarliest = theConfig.getEarliestDay();
635 int iLatest = theConfig.getLatestDay();
636
637
638 DayOfWeek myWeekDay = myCurr.getDayOfWeek();
639 int iStart = getDayColumn(myWeekDay);
640 if (iStart > 0) {
641 myCurr = myCurr.minusDays(iStart);
642 }
643
644
645 int iCol = 0;
646 for (int iDay = myCurr.getDayOfMonth(); iCol < iStart; iCol++, iDay++, myCurr = myCurr.plusDays(1)) {
647
648 PanelDay myLabel = theDays[0][iCol];
649
650
651 myLabel.resetDay(false);
652 myLabel.setDay(iDay, false);
653 }
654
655
656 int iRow = 0;
657 int iMonth = myCurr.getMonthValue();
658 for (int iDay = 1; iMonth == myCurr.getMonthValue(); iCol++, iDay++, myCurr = myCurr.plusDays(1)) {
659
660 if (iCol >= DAYS_IN_WEEK) {
661 iRow++;
662 iCol = 0;
663 }
664
665
666 PanelDay myLabel = theDays[iRow][iCol];
667
668
669 myLabel.resetDay(true);
670
671 if (iSelected == iDay) {
672 myLabel.setSelected();
673 } else if (iCurrent == iDay) {
674 myLabel.setCurrent();
675 } else if (isWeekend(myCurr.getDayOfWeek())) {
676 myLabel.setWeekend();
677 }
678
679
680 boolean isSelectable = true;
681 if ((iEarliest > 0)
682 && (iDay < iEarliest)) {
683 isSelectable = false;
684 } else if ((iLatest > 0)
685 && (iDay > iLatest)) {
686 isSelectable = false;
687 }
688
689
690 myLabel.setDay(iDay, isSelectable);
691 }
692
693
694 for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
695
696 PanelDay myLabel = theDays[iRow][iCol];
697
698
699 myLabel.resetDay(false);
700 myLabel.setDay(iDay, false);
701 }
702
703
704 reSizeRows(iRow + 1);
705 }
706
707
708
709
710 private void buildDayNames() {
711
712 Locale myLocale = theConfig.getLocale();
713 Calendar myDate = Calendar.getInstance(myLocale);
714 int myStart = myDate.getFirstDayOfWeek();
715 if (myStart == Calendar.SUNDAY) {
716 myStart += DAYS_IN_WEEK;
717 }
718
719
720 DayOfWeek myDoW = DayOfWeek.of(myStart - 1);
721 for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
722
723 theDaysOfWk[iDay] = myDoW;
724 }
725
726
727 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
728
729 Label myLabel = theHdrs[iCol];
730
731
732 ObservableList<String> myStyles = myLabel.getStyleClass();
733 myStyles.clear();
734 myStyles.add(STYLE_HEADER);
735
736
737 myDoW = theDaysOfWk[iCol];
738 TextStyle myStyle = theConfig.showNarrowDays()
739 ? TextStyle.NARROW
740 : TextStyle.SHORT;
741 String myName = myDoW.getDisplayName(myStyle, myLocale);
742
743
744 myLabel.setText(myName);
745
746
747 if (isWeekend(myDoW)) {
748 myStyles.add(PanelDay.STYLE_WEEKEND);
749 }
750 }
751 }
752
753
754
755
756
757 private void reSizeRows(final int iNumRows) {
758
759 ObservableList<Node> myNodes = getChildren();
760
761
762 while (iNumRows < theNumRows) {
763
764 theNumRows--;
765
766
767 for (PanelDay myDay : theDays[theNumRows]) {
768
769 myNodes.remove(myDay);
770 }
771 }
772
773
774 while (iNumRows > theNumRows) {
775
776 theNumRows++;
777
778
779 int iCol = 0;
780 for (PanelDay myDay : theDays[theNumRows - 1]) {
781
782 add(myDay, iCol++, theNumRows);
783 }
784 }
785
786
787 theDialog.sizeToScene();
788 }
789 }
790
791
792
793
794 private static final class PanelDay
795 extends Label {
796
797
798
799 private static final String STYLE_PANEL = "-jdd-day";
800
801
802
803
804 private static final String STYLE_INACTIVE = "-jdd-inactive";
805
806
807
808
809 private static final String STYLE_SELECTED = "-jdd-selected";
810
811
812
813
814 private static final String STYLE_CURRENT = "-jdd-current";
815
816
817
818
819 private static final String STYLE_WEEKEND = "-jdd-weekend";
820
821
822
823
824 private final JDateDialog theDialog;
825
826
827
828
829 private int theDay = -1;
830
831
832
833
834 private boolean isSelectable = false;
835
836
837
838
839
840 protected PanelDay(final JDateDialog pDialog) {
841
842 theDialog = pDialog;
843
844
845 setPrefWidth(PanelMonth.WIDTH_TILE);
846 addEventFilter(MouseEvent.MOUSE_CLICKED, new MouseEventHandler());
847 }
848
849
850
851
852
853 private void resetDay(final boolean isActive) {
854 setTooltip(null);
855 getStyleClass().clear();
856 getStyleClass().add(STYLE_PANEL);
857 if (!isActive) {
858 getStyleClass().add(STYLE_INACTIVE);
859 }
860 }
861
862
863
864
865
866
867 private void setDay(final int pDay,
868 final boolean pSelectable) {
869
870 theDay = pDay;
871 isSelectable = pSelectable;
872
873
874 if (pDay > 0) {
875 setText(Integer.toString(theDay));
876 } else {
877 setText("");
878 }
879
880
881 setDisable(!isSelectable);
882 }
883
884
885
886
887 private void setWeekend() {
888 getStyleClass().add(STYLE_WEEKEND);
889 }
890
891
892
893
894 private void setSelected() {
895 getStyleClass().add(STYLE_SELECTED);
896 setTooltip(new Tooltip(NLS_SELECTEDDAY));
897 }
898
899
900
901
902 private void setCurrent() {
903 getStyleClass().add(STYLE_CURRENT);
904 setTooltip(new Tooltip(NLS_CURRENTDAY));
905 }
906
907
908
909
910 private class MouseEventHandler
911 implements EventHandler<MouseEvent> {
912 @Override
913 public void handle(final MouseEvent event) {
914 theDialog.setSelected(theDay);
915 }
916 }
917 }
918 }