View Javadoc
1   /*
2    * Tethys: GUI Utilities
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.tethys.javafx.button;
18  
19  import io.github.tonywasher.joceanus.oceanus.date.OceanusDate;
20  import io.github.tonywasher.joceanus.oceanus.date.OceanusDateConfig;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
23  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
24  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
25  import io.github.tonywasher.joceanus.tethys.core.base.TethysUIResource;
26  import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXArrowIcon;
27  import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXUtils;
28  import javafx.collections.ObservableList;
29  import javafx.event.ActionEvent;
30  import javafx.geometry.Bounds;
31  import javafx.scene.Node;
32  import javafx.scene.Scene;
33  import javafx.scene.control.Button;
34  import javafx.scene.control.Label;
35  import javafx.scene.control.Tooltip;
36  import javafx.scene.input.KeyCode;
37  import javafx.scene.input.MouseEvent;
38  import javafx.scene.layout.BorderPane;
39  import javafx.scene.layout.GridPane;
40  import javafx.scene.layout.HBox;
41  import javafx.scene.layout.Priority;
42  import javafx.scene.layout.Region;
43  import javafx.stage.Modality;
44  import javafx.stage.Stage;
45  import javafx.stage.StageStyle;
46  
47  import java.time.DayOfWeek;
48  import java.time.format.TextStyle;
49  import java.time.temporal.WeekFields;
50  import java.util.Locale;
51  
52  /**
53   * FX Date Dialog.
54   */
55  public final class TethysUIFXDateDialog
56          implements OceanusEventProvider<TethysUIEvent> {
57      /**
58       * StyleSheet Name.
59       */
60      private static final String CSS_STYLE_NAME = "jtethys-javafx-datedialog.css";
61  
62      /**
63       * StyleSheet.
64       */
65      private static final String CSS_STYLE = TethysUIFXDateDialog.class.getResource(CSS_STYLE_NAME).toExternalForm();
66  
67      /**
68       * The dialog style.
69       */
70      static final String STYLE_DIALOG = TethysUIFXUtils.CSS_STYLE_BASE + "-datedialog";
71  
72      /**
73       * Null Date selection text.
74       */
75      private static final String NLS_NULLSELECT = TethysUIResource.DIALOG_NULL.getValue();
76  
77      /**
78       * The stage.
79       */
80      private final Stage theStage;
81  
82      /**
83       * The event manager.
84       */
85      private final OceanusEventManager<TethysUIEvent> theEventManager;
86  
87      /**
88       * The month array.
89       */
90      private final PanelMonth theDaysPanel;
91  
92      /**
93       * The navigation.
94       */
95      private final PanelNavigation theNavigation;
96  
97      /**
98       * The Null Select.
99       */
100     private final Button theNullButton;
101 
102     /**
103      * Is the Null button active.
104      */
105     private boolean isNullActive;
106 
107     /**
108      * The Date Configuration.
109      */
110     private final OceanusDateConfig theConfig;
111 
112     /**
113      * Should we build names?
114      */
115     private boolean doBuildNames = true;
116 
117     /**
118      * The container box.
119      */
120     private final BorderPane theContainer;
121 
122     /**
123      * Have we selected a date?
124      */
125     private boolean haveSelected;
126 
127     /**
128      * Constructor.
129      *
130      * @param pConfig the configuration for the dialog
131      */
132     public TethysUIFXDateDialog(final OceanusDateConfig pConfig) {
133         /* Create Non-Modal and undecorated stage */
134         theStage = new Stage(StageStyle.UNDECORATED);
135         theStage.initModality(Modality.NONE);
136 
137         /* Store the DateConfig */
138         theConfig = pConfig;
139 
140         /* Create the event manager */
141         theEventManager = new OceanusEventManager<>();
142 
143         /* Build the panels */
144         theDaysPanel = new PanelMonth(this);
145         theNavigation = new PanelNavigation(this);
146 
147         /* Build the Null Select */
148         theNullButton = new Button(NLS_NULLSELECT);
149         theNullButton.setMaxWidth(Double.MAX_VALUE);
150         theNullButton.addEventHandler(ActionEvent.ACTION, e -> setSelected(-1));
151 
152         /* Add listener to shut dialog on loss of focus */
153         theStage.focusedProperty().addListener((v, o, n) -> {
154             if (Boolean.FALSE.equals(n)) {
155                 closeNonModal();
156             }
157         });
158 
159         /* Create the scene */
160         theContainer = new BorderPane();
161         theContainer.setTop(theNavigation.getHBox());
162         theContainer.setCenter(theDaysPanel.getGridPane());
163         theContainer.getStyleClass().add(STYLE_DIALOG);
164         final Scene myScene = new Scene(theContainer);
165         final ObservableList<String> mySheets = myScene.getStylesheets();
166         mySheets.add(CSS_STYLE);
167         theStage.setScene(myScene);
168 
169         /* Initialise the month */
170         initialiseMonth();
171 
172         /* Add listener to shut dialog on escape key */
173         theContainer.setOnKeyPressed(e -> {
174             if (e.getCode() == KeyCode.ESCAPE) {
175                 closeNonModal();
176             }
177         });
178 
179         /* Add listener to Configuration to reBuild Names */
180         theConfig.getEventRegistrar().addEventListener(e -> doBuildNames());
181     }
182 
183     @Override
184     public OceanusEventRegistrar<TethysUIEvent> getEventRegistrar() {
185         return theEventManager.getEventRegistrar();
186     }
187 
188     /**
189      * Request a rebuild of panel names.
190      */
191     private void doBuildNames() {
192         doBuildNames = true;
193     }
194 
195     /**
196      * Have we selected a date?
197      *
198      * @return true/false
199      */
200     public boolean haveSelected() {
201         return haveSelected;
202     }
203 
204     /**
205      * Obtain Date Configuration.
206      *
207      * @return the date configuration
208      */
209     public OceanusDateConfig getDateConfig() {
210         return theConfig;
211     }
212 
213     /**
214      * Build the month.
215      */
216     void buildMonth() {
217         /* Build the month */
218         theNavigation.buildMonth();
219         theDaysPanel.buildMonth();
220     }
221 
222     /**
223      * Set Selected Date.
224      *
225      * @param pDay the Selected day
226      */
227     void setSelected(final int pDay) {
228         /* Set the selected day */
229         theConfig.setSelectedDay(pDay);
230 
231         /* Note that we have selected */
232         haveSelected = true;
233 
234         /* Close the dialog */
235         theStage.close();
236 
237         /* Note that selection has been made */
238         theEventManager.fireEvent(TethysUIEvent.NEWVALUE, theConfig.getSelectedDate());
239     }
240 
241     /**
242      * Resize the dialog.
243      */
244     void reSizeDialog() {
245         theStage.sizeToScene();
246     }
247 
248     /**
249      * InitialiseMonth.
250      */
251     private void initialiseMonth() {
252         /* Initialise the current month */
253         theConfig.initialiseCurrent();
254 
255         /* Build the day names if required */
256         if (doBuildNames) {
257             theDaysPanel.buildDayNames();
258         }
259         doBuildNames = false;
260 
261         /* Build detail */
262         buildMonth();
263 
264         /* If we need to change the visibility of the null button */
265         if (isNullActive != theConfig.allowNullDateSelection()) {
266             /* Add/Remove the button */
267             if (!isNullActive) {
268                 theContainer.setBottom(theNullButton);
269             } else {
270                 theContainer.setBottom(null);
271             }
272 
273             /* Record status and resize the dialog */
274             isNullActive = theConfig.allowNullDateSelection();
275             reSizeDialog();
276         }
277     }
278 
279     /**
280      * Show the dialog.
281      *
282      * @param pNode the node under which to show the dialog
283      */
284     public void showDialogUnderNode(final Node pNode) {
285         /* Allow configuration to be updated */
286         theEventManager.fireEvent(TethysUIEvent.PREPAREDIALOG, theConfig);
287 
288         /* Determine the relevant bounds */
289         final Bounds myBounds = pNode.localToScreen(pNode.getLayoutBounds());
290 
291         /* Position the dialog just below the node */
292         theStage.setX(myBounds.getMinX());
293         theStage.setY(myBounds.getMaxY());
294 
295         /* Note that we have not selected */
296         haveSelected = false;
297 
298         /* Initialise the current month and show the dialog */
299         initialiseMonth();
300         theStage.show();
301     }
302 
303     /**
304      * Close non-Modal.
305      */
306     private void closeNonModal() {
307         /* Close the window */
308         theStage.close();
309 
310         /* Note that no selection has been made */
311         theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
312     }
313 
314     /**
315      * Panel Navigation class allowing navigation between months.
316      */
317     private static final class PanelNavigation {
318         /**
319          * ToolTip for Next Month.
320          */
321         private static final String NLS_NEXTMONTH = TethysUIResource.DIALOG_NEXTMONTH.getValue();
322 
323         /**
324          * ToolTip for Previous Month.
325          */
326         private static final String NLS_PREVMONTH = TethysUIResource.DIALOG_PREVMONTH.getValue();
327 
328         /**
329          * ToolTip for Next Year.
330          */
331         private static final String NLS_NEXTYEAR = TethysUIResource.DIALOG_NEXTYEAR.getValue();
332 
333         /**
334          * ToolTip for Previous Year.
335          */
336         private static final String NLS_PREVYEAR = TethysUIResource.DIALOG_PREVYEAR.getValue();
337 
338         /**
339          * The button style.
340          */
341         private static final String STYLE_BUTTON = STYLE_DIALOG + "-button";
342 
343         /**
344          * The title style.
345          */
346         private static final String STYLE_TITLE = STYLE_DIALOG + "-title";
347 
348         /**
349          * The owning dialog.
350          */
351         private final TethysUIFXDateDialog theDialog;
352 
353         /**
354          * The Date Configuration.
355          */
356         private final OceanusDateConfig theConfig;
357 
358         /**
359          * The HBox.
360          */
361         private final HBox theHBox;
362 
363         /**
364          * The Date Label.
365          */
366         private final Label theDateLabel;
367 
368         /**
369          * The Previous Month Button.
370          */
371         private final Button thePrevMonthButton;
372 
373         /**
374          * The Next Month Button.
375          */
376         private final Button theNextMonthButton;
377 
378         /**
379          * The Previous Year Button.
380          */
381         private final Button thePrevYearButton;
382 
383         /**
384          * The Next Year Button.
385          */
386         private final Button theNextYearButton;
387 
388         /**
389          * Constructor.
390          *
391          * @param pDialog the owning dialog
392          */
393         PanelNavigation(final TethysUIFXDateDialog pDialog) {
394             /* Create the hBox with single space */
395             theHBox = new HBox(1.0);
396 
397             /* Record the dialog */
398             theDialog = pDialog;
399 
400             /* Store the Date Configuration */
401             theConfig = pDialog.getDateConfig();
402 
403             /* Create the label */
404             theDateLabel = new Label();
405             theDateLabel.getStyleClass().add(STYLE_TITLE);
406 
407             /* Create the buttons */
408             thePrevMonthButton = new Button();
409             theNextMonthButton = new Button();
410             thePrevYearButton = new Button();
411             theNextYearButton = new Button();
412 
413             /* Set the icons */
414             thePrevMonthButton.setGraphic(TethysUIFXArrowIcon.LEFT.getArrow());
415             theNextMonthButton.setGraphic(TethysUIFXArrowIcon.RIGHT.getArrow());
416             thePrevYearButton.setGraphic(TethysUIFXArrowIcon.DOUBLELEFT.getArrow());
417             theNextYearButton.setGraphic(TethysUIFXArrowIcon.DOUBLERIGHT.getArrow());
418 
419             /* Add ToopTips */
420             theNextMonthButton.setTooltip(new Tooltip(NLS_NEXTMONTH));
421             thePrevMonthButton.setTooltip(new Tooltip(NLS_PREVMONTH));
422             theNextYearButton.setTooltip(new Tooltip(NLS_NEXTYEAR));
423             thePrevYearButton.setTooltip(new Tooltip(NLS_PREVYEAR));
424 
425             /* Listen for button events */
426             thePrevMonthButton.addEventHandler(ActionEvent.ACTION, e -> {
427                 theConfig.previousMonth();
428                 theDialog.buildMonth();
429             });
430             theNextMonthButton.addEventHandler(ActionEvent.ACTION, e -> {
431                 theConfig.nextMonth();
432                 theDialog.buildMonth();
433             });
434             thePrevYearButton.addEventHandler(ActionEvent.ACTION, e -> {
435                 theConfig.previousYear();
436                 theDialog.buildMonth();
437             });
438             theNextYearButton.addEventFilter(ActionEvent.ACTION, e -> {
439                 theConfig.nextYear();
440                 theDialog.buildMonth();
441             });
442 
443             /* Restrict the margins */
444             thePrevMonthButton.getStyleClass().add(STYLE_BUTTON);
445             theNextMonthButton.getStyleClass().add(STYLE_BUTTON);
446             thePrevYearButton.getStyleClass().add(STYLE_BUTTON);
447             theNextYearButton.getStyleClass().add(STYLE_BUTTON);
448 
449             /* Create the struts */
450             final Region myStrut1 = new Region();
451             final Region myStrut2 = new Region();
452             HBox.setHgrow(myStrut1, Priority.ALWAYS);
453             HBox.setHgrow(myStrut2, Priority.ALWAYS);
454 
455             /* Add these elements into the HBox */
456             final ObservableList<Node> myChildren = theHBox.getChildren();
457             myChildren.add(thePrevYearButton);
458             myChildren.add(thePrevMonthButton);
459             myChildren.add(myStrut1);
460             myChildren.add(theDateLabel);
461             myChildren.add(myStrut2);
462             myChildren.add(theNextMonthButton);
463             myChildren.add(theNextYearButton);
464         }
465 
466         /**
467          * Obtain the hBox.
468          *
469          * @return the hBox
470          */
471         HBox getHBox() {
472             return theHBox;
473         }
474 
475         /**
476          * Build month details.
477          */
478         private void buildMonth() {
479             /* Obtain the active month */
480             final OceanusDate myBase = theConfig.getCurrentMonth();
481             final Locale myLocale = theConfig.getLocale();
482 
483             /* Determine the display for the label */
484             final String myMonth = myBase.getMonthValue().getDisplayName(TextStyle.FULL, myLocale);
485             final String myYear = Integer.toString(myBase.getYear());
486 
487             /* Set the label */
488             theDateLabel.setText(myMonth
489                     + ", "
490                     + myYear);
491 
492             /* Access boundary dates */
493             final OceanusDate myEarliest = theConfig.getEarliestDate();
494             final OceanusDate myLatest = theConfig.getLatestDate();
495 
496             /* Enable/Disable buttons as required */
497             thePrevMonthButton.setDisable(OceanusDateConfig.isSameMonth(myEarliest, myBase));
498             thePrevYearButton.setDisable(OceanusDateConfig.isSameYear(myEarliest, myBase));
499             theNextMonthButton.setDisable(OceanusDateConfig.isSameMonth(myLatest, myBase));
500             theNextYearButton.setDisable(OceanusDateConfig.isSameYear(myLatest, myBase));
501         }
502     }
503 
504     /**
505      * Month Panel.
506      */
507     private static final class PanelMonth {
508         /**
509          * The panel style.
510          */
511         private static final String STYLE_PANEL = STYLE_DIALOG + "-month";
512 
513         /**
514          * The header style.
515          */
516         private static final String STYLE_HEADER = STYLE_DIALOG + "-hdr";
517 
518         /**
519          * Number of days in week.
520          */
521         private static final int DAYS_IN_WEEK = 7;
522 
523         /**
524          * Maximum # of weeks in month.
525          */
526         private static final int MAX_WEEKS_IN_MONTH = 6;
527 
528         /**
529          * Tile Width.
530          */
531         private static final int WIDTH_TILE = 22;
532 
533         /**
534          * The Dialog.
535          */
536         private final TethysUIFXDateDialog theDialog;
537 
538         /**
539          * The Date Configuration.
540          */
541         private final OceanusDateConfig theConfig;
542 
543         /**
544          * The GridPane.
545          */
546         private final GridPane theGridPane;
547 
548         /**
549          * The Array of Days.
550          */
551         private final DayOfWeek[] theDaysOfWk = new DayOfWeek[DAYS_IN_WEEK];
552 
553         /**
554          * The Array of Day Names.
555          */
556         private final Label[] theHdrs = new Label[DAYS_IN_WEEK];
557 
558         /**
559          * The Array of Day Labels.
560          */
561         private final PanelDay[][] theDays = new PanelDay[MAX_WEEKS_IN_MONTH][DAYS_IN_WEEK];
562 
563         /**
564          * The Active number of rows.
565          */
566         private int theNumRows = MAX_WEEKS_IN_MONTH;
567 
568         /**
569          * Constructor.
570          *
571          * @param pDialog the owning dialog
572          */
573         PanelMonth(final TethysUIFXDateDialog pDialog) {
574             /* Store parameters */
575             theDialog = pDialog;
576 
577             /* Create the gridPane */
578             theGridPane = new GridPane();
579 
580             /* Store the Date Configuration */
581             theConfig = pDialog.getDateConfig();
582 
583             /* Define style of GridPane */
584             theGridPane.getStyleClass().add(STYLE_PANEL);
585 
586             /* Add the Names to the layout */
587             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
588                 final Label myDay = new Label();
589                 myDay.setMaxWidth(Double.MAX_VALUE);
590                 theHdrs[iCol] = myDay;
591                 theGridPane.add(myDay, iCol, 0);
592             }
593 
594             /* Add the Days to the layout */
595             for (int iRow = 0; iRow < MAX_WEEKS_IN_MONTH; iRow++) {
596                 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
597                     final PanelDay myDay = new PanelDay(theDialog);
598                     theDays[iRow][iCol] = myDay;
599                     theGridPane.add(myDay.getLabel(), iCol, iRow + 1);
600                 }
601             }
602 
603             /* Build the Day Names */
604             buildDayNames();
605         }
606 
607         /**
608          * Obtain the gridPane.
609          *
610          * @return the gridPane
611          */
612         GridPane getGridPane() {
613             return theGridPane;
614         }
615 
616         /**
617          * Is the DayOfWeek a Weekend day.
618          *
619          * @param pDoW the day of the week
620          * @return true/false
621          */
622         private static boolean isWeekend(final DayOfWeek pDoW) {
623             return switch (pDoW) {
624                 case SATURDAY, SUNDAY -> true;
625                 default -> false;
626             };
627         }
628 
629         /**
630          * obtain column number for DayOfWeek.
631          *
632          * @param pDoW the day of the week
633          * @return the column number
634          */
635         private int getDayColumn(final DayOfWeek pDoW) {
636             for (int i = 0; i < DAYS_IN_WEEK; i++) {
637                 if (theDaysOfWk[i].equals(pDoW)) {
638                     return i;
639                 }
640             }
641             return -1;
642         }
643 
644         /**
645          * Build the month.
646          */
647         void buildMonth() {
648             /* Obtain the active month */
649             final OceanusDate myCurr = new OceanusDate(theConfig.getCurrentMonth());
650 
651             /* Access the interesting days of the month */
652             final int iCurrent = theConfig.getCurrentDay();
653             final int iSelected = theConfig.getSelectedDay();
654             final int iEarliest = theConfig.getEarliestDay();
655             final int iLatest = theConfig.getLatestDay();
656 
657             /* Move to the start of the week */
658             final DayOfWeek myWeekDay = myCurr.getDayOfWeek();
659             final int iStart = getDayColumn(myWeekDay);
660             if (iStart > 0) {
661                 myCurr.adjustDay(-iStart);
662             }
663 
664             /* Loop through initial columns */
665             int iCol = 0;
666             for (int iDay = myCurr.getDay(); iCol < iStart; iCol++, iDay++, myCurr.adjustDay(1)) {
667                 /* Access the label */
668                 final PanelDay myLabel = theDays[0][iCol];
669 
670                 /* Reset the day and set no day */
671                 myLabel.resetDay(false);
672                 myLabel.setDay(iDay, false);
673             }
674 
675             /* Loop through the days of the month */
676             int iRow = 0;
677             final int iMonth = myCurr.getMonth();
678             for (int iDay = 1; iMonth == myCurr.getMonth(); iCol++, iDay++, myCurr.adjustDay(1)) {
679                 /* Reset column if necessary */
680                 if (iCol >= DAYS_IN_WEEK) {
681                     iRow++;
682                     iCol = 0;
683                 }
684 
685                 /* Access the label */
686                 final PanelDay myLabel = theDays[iRow][iCol];
687 
688                 /* Reset the day */
689                 myLabel.resetDay(true);
690 
691                 if (iSelected == iDay) {
692                     myLabel.setSelected();
693                 } else if (iCurrent == iDay) {
694                     myLabel.setCurrent();
695                 } else if (isWeekend(myCurr.getDayOfWeek())) {
696                     myLabel.setWeekend();
697                 }
698 
699                 /* Determine whether the day is select-able */
700                 boolean isSelectable = true;
701                 if (iEarliest > 0) {
702                     isSelectable &= iDay >= iEarliest;
703                 }
704                 if (iLatest > 0) {
705                     isSelectable &= iDay <= iLatest;
706                 }
707 
708                 /* Check for allowed date */
709                 isSelectable &= theConfig.isAllowed(iDay);
710 
711                 /* Set text */
712                 myLabel.setDay(iDay, isSelectable);
713             }
714 
715             /* Loop through remaining columns in row */
716             for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
717                 /* Access the label */
718                 final PanelDay myLabel = theDays[iRow][iCol];
719 
720                 /* Reset the day and set no day */
721                 myLabel.resetDay(false);
722                 myLabel.setDay(iDay, false);
723             }
724 
725             /* Resize to the number of rows */
726             reSizeRows(iRow + 1);
727         }
728 
729         /**
730          * build Day names.
731          */
732         void buildDayNames() {
733             /* Get todays date */
734             final Locale myLocale = theConfig.getLocale();
735             final WeekFields myWeekFields = WeekFields.of(myLocale);
736 
737             /* Build the array of the days of the week */
738             DayOfWeek myDoW = myWeekFields.getFirstDayOfWeek();
739             for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
740                 /* Store the day into the array */
741                 theDaysOfWk[iDay] = myDoW;
742             }
743 
744             /* Loop through the labels */
745             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
746                 /* Access the label */
747                 final Label myLabel = theHdrs[iCol];
748 
749                 /* Reset classes */
750                 final ObservableList<String> myStyles = myLabel.getStyleClass();
751                 myStyles.clear();
752                 myStyles.add(STYLE_HEADER);
753 
754                 /* Access the required name */
755                 myDoW = theDaysOfWk[iCol];
756                 final TextStyle myStyle = theConfig.showNarrowDays()
757                         ? TextStyle.NARROW
758                         : TextStyle.SHORT;
759                 final String myName = myDoW.getDisplayName(myStyle, myLocale);
760 
761                 /* Set the name */
762                 myLabel.setText(myName);
763 
764                 /* Set weekend if required */
765                 if (isWeekend(myDoW)) {
766                     myStyles.add(PanelDay.STYLE_WEEKEND);
767                 }
768             }
769         }
770 
771         /**
772          * ReSize the number of visible rows.
773          *
774          * @param iNumRows number of visible rows
775          */
776         private void reSizeRows(final int iNumRows) {
777             /* Access the children */
778             final ObservableList<Node> myNodes = theGridPane.getChildren();
779 
780             /* Hide any visible rows that should now be hidden */
781             while (iNumRows < theNumRows) {
782                 /* Decrement number of rows */
783                 theNumRows--;
784 
785                 /* Loop through remaining rows */
786                 for (final PanelDay myDay : theDays[theNumRows]) {
787                     /* Remove from panel */
788                     myNodes.remove(myDay.getLabel());
789                 }
790             }
791 
792             /* Show any hidden rows that should now be visible */
793             while (iNumRows > theNumRows) {
794                 /* Increment number of rows */
795                 theNumRows++;
796 
797                 /* Loop through remaining rows */
798                 int iCol = 0;
799                 for (final PanelDay myDay : theDays[theNumRows - 1]) {
800                     /* Add to panel */
801                     theGridPane.add(myDay.getLabel(), iCol++, theNumRows);
802                 }
803             }
804 
805             /* RePack the Dialog */
806             theDialog.reSizeDialog();
807         }
808     }
809 
810     /**
811      * Panel class representing a single day in the panel.
812      */
813     private static final class PanelDay {
814         /**
815          * ToolTip for Current Day.
816          */
817         private static final String NLS_CURRENTDAY = TethysUIResource.DIALOG_CURRENT.getValue();
818 
819         /**
820          * ToolTip for Selected Day.
821          */
822         private static final String NLS_SELECTEDDAY = TethysUIResource.DIALOG_SELECTED.getValue();
823 
824         /**
825          * The panel style.
826          */
827         private static final String STYLE_PANEL = STYLE_DIALOG + "-day";
828 
829         /**
830          * The inactive style.
831          */
832         private static final String STYLE_INACTIVE = STYLE_DIALOG + "-inactive";
833 
834         /**
835          * The selected style.
836          */
837         private static final String STYLE_SELECTED = STYLE_DIALOG + "-selected";
838 
839         /**
840          * The current style.
841          */
842         private static final String STYLE_CURRENT = STYLE_DIALOG + "-current";
843 
844         /**
845          * The weekend style.
846          */
847         private static final String STYLE_WEEKEND = STYLE_DIALOG + "-weekend";
848 
849         /**
850          * The Dialog.
851          */
852         private final TethysUIFXDateDialog theDialog;
853 
854         /**
855          * The Label.
856          */
857         private final Label theLabel;
858 
859         /**
860          * The day of the month.
861          */
862         private int theDay = -1;
863 
864         /**
865          * Constructor.
866          *
867          * @param pDialog the owning dialog
868          */
869         PanelDay(final TethysUIFXDateDialog pDialog) {
870             /* Store parameters */
871             theDialog = pDialog;
872 
873             /* Create the label */
874             theLabel = new Label();
875 
876             /* Set width */
877             theLabel.setPrefWidth(PanelMonth.WIDTH_TILE);
878             theLabel.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> theDialog.setSelected(theDay));
879         }
880 
881         /**
882          * Obtain the label.
883          *
884          * @return the label
885          */
886         Label getLabel() {
887             return theLabel;
888         }
889 
890         /**
891          * Reset a Day Label.
892          *
893          * @param isActive true/false
894          */
895         void resetDay(final boolean isActive) {
896             theLabel.setTooltip(null);
897             theLabel.getStyleClass().clear();
898             theLabel.getStyleClass().add(STYLE_PANEL);
899             if (!isActive) {
900                 theLabel.getStyleClass().add(STYLE_INACTIVE);
901             }
902         }
903 
904         /**
905          * Set day for label.
906          *
907          * @param pDay        the Day number
908          * @param pSelectable is the day select-able
909          */
910         void setDay(final int pDay,
911                     final boolean pSelectable) {
912             /* Record the day */
913             theDay = pDay;
914 
915             /* Set the text for the item */
916             if (pDay > 0) {
917                 theLabel.setText(Integer.toString(theDay));
918             } else {
919                 theLabel.setText("");
920             }
921 
922             /* Enable/Disable the label */
923             theLabel.setDisable(!pSelectable);
924         }
925 
926         /**
927          * Set weekend.
928          */
929         void setWeekend() {
930             theLabel.getStyleClass().add(STYLE_WEEKEND);
931         }
932 
933         /**
934          * Set selected day.
935          */
936         void setSelected() {
937             theLabel.getStyleClass().add(STYLE_SELECTED);
938             theLabel.setTooltip(new Tooltip(NLS_SELECTEDDAY));
939         }
940 
941         /**
942          * Set current day.
943          */
944         void setCurrent() {
945             theLabel.getStyleClass().add(STYLE_CURRENT);
946             theLabel.setTooltip(new Tooltip(NLS_CURRENTDAY));
947         }
948     }
949 }