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 javafx.collections.ObservableList;
25  import javafx.event.ActionEvent;
26  import javafx.geometry.Bounds;
27  import javafx.scene.Node;
28  import javafx.scene.Scene;
29  import javafx.scene.control.Button;
30  import javafx.scene.control.Label;
31  import javafx.scene.control.Tooltip;
32  import javafx.scene.input.KeyCode;
33  import javafx.scene.input.MouseEvent;
34  import javafx.scene.layout.BorderPane;
35  import javafx.scene.layout.GridPane;
36  import javafx.scene.layout.HBox;
37  import javafx.scene.layout.Priority;
38  import javafx.scene.layout.Region;
39  import javafx.stage.Modality;
40  import javafx.stage.Stage;
41  import javafx.stage.StageStyle;
42  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
43  import io.github.tonywasher.joceanus.tethys.core.base.TethysUIResource;
44  import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXArrowIcon;
45  import io.github.tonywasher.joceanus.tethys.javafx.base.TethysUIFXUtils;
46  
47  import java.time.DayOfWeek;
48  import java.time.format.TextStyle;
49  import java.util.Calendar;
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             switch (pDoW) {
624                 case SATURDAY:
625                 case SUNDAY:
626                     return true;
627                 default:
628                     return false;
629             }
630         }
631 
632         /**
633          * obtain column number for DayOfWeek.
634          *
635          * @param pDoW the day of the week
636          * @return the column number
637          */
638         private int getDayColumn(final DayOfWeek pDoW) {
639             for (int i = 0; i < DAYS_IN_WEEK; i++) {
640                 if (theDaysOfWk[i] == pDoW) {
641                     return i;
642                 }
643             }
644             return -1;
645         }
646 
647         /**
648          * Build the month.
649          */
650         void buildMonth() {
651             /* Obtain the active month */
652             final OceanusDate myCurr = new OceanusDate(theConfig.getCurrentMonth());
653 
654             /* Access the interesting days of the month */
655             final int iCurrent = theConfig.getCurrentDay();
656             final int iSelected = theConfig.getSelectedDay();
657             final int iEarliest = theConfig.getEarliestDay();
658             final int iLatest = theConfig.getLatestDay();
659 
660             /* Move to the start of the week */
661             final DayOfWeek myWeekDay = myCurr.getDayOfWeek();
662             final int iStart = getDayColumn(myWeekDay);
663             if (iStart > 0) {
664                 myCurr.adjustDay(-iStart);
665             }
666 
667             /* Loop through initial columns */
668             int iCol = 0;
669             for (int iDay = myCurr.getDay(); iCol < iStart; iCol++, iDay++, myCurr.adjustDay(1)) {
670                 /* Access the label */
671                 final PanelDay myLabel = theDays[0][iCol];
672 
673                 /* Reset the day and set no day */
674                 myLabel.resetDay(false);
675                 myLabel.setDay(iDay, false);
676             }
677 
678             /* Loop through the days of the month */
679             int iRow = 0;
680             final int iMonth = myCurr.getMonth();
681             for (int iDay = 1; iMonth == myCurr.getMonth(); iCol++, iDay++, myCurr.adjustDay(1)) {
682                 /* Reset column if necessary */
683                 if (iCol >= DAYS_IN_WEEK) {
684                     iRow++;
685                     iCol = 0;
686                 }
687 
688                 /* Access the label */
689                 final PanelDay myLabel = theDays[iRow][iCol];
690 
691                 /* Reset the day */
692                 myLabel.resetDay(true);
693 
694                 if (iSelected == iDay) {
695                     myLabel.setSelected();
696                 } else if (iCurrent == iDay) {
697                     myLabel.setCurrent();
698                 } else if (isWeekend(myCurr.getDayOfWeek())) {
699                     myLabel.setWeekend();
700                 }
701 
702                 /* Determine whether the day is select-able */
703                 boolean isSelectable = true;
704                 if (iEarliest > 0) {
705                     isSelectable &= iDay >= iEarliest;
706                 }
707                 if (iLatest > 0) {
708                     isSelectable &= iDay <= iLatest;
709                 }
710 
711                 /* Check for allowed date */
712                 isSelectable &= theConfig.isAllowed(iDay);
713 
714                 /* Set text */
715                 myLabel.setDay(iDay, isSelectable);
716             }
717 
718             /* Loop through remaining columns in row */
719             for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
720                 /* Access the label */
721                 final PanelDay myLabel = theDays[iRow][iCol];
722 
723                 /* Reset the day and set no day */
724                 myLabel.resetDay(false);
725                 myLabel.setDay(iDay, false);
726             }
727 
728             /* Resize to the number of rows */
729             reSizeRows(iRow + 1);
730         }
731 
732         /**
733          * build Day names.
734          */
735         void buildDayNames() {
736             /* Get todays date */
737             final Locale myLocale = theConfig.getLocale();
738             final Calendar myDate = Calendar.getInstance(myLocale);
739             int myStart = myDate.getFirstDayOfWeek();
740             if (myStart == Calendar.SUNDAY) {
741                 myStart += DAYS_IN_WEEK;
742             }
743 
744             /* Build the array of the days of the week */
745             DayOfWeek myDoW = DayOfWeek.of(myStart - 1);
746             for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
747                 /* Store the day into the array */
748                 theDaysOfWk[iDay] = myDoW;
749             }
750 
751             /* Loop through the labels */
752             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
753                 /* Access the label */
754                 final Label myLabel = theHdrs[iCol];
755 
756                 /* Reset classes */
757                 final ObservableList<String> myStyles = myLabel.getStyleClass();
758                 myStyles.clear();
759                 myStyles.add(STYLE_HEADER);
760 
761                 /* Access the required name */
762                 myDoW = theDaysOfWk[iCol];
763                 final TextStyle myStyle = theConfig.showNarrowDays()
764                         ? TextStyle.NARROW
765                         : TextStyle.SHORT;
766                 final String myName = myDoW.getDisplayName(myStyle, myLocale);
767 
768                 /* Set the name */
769                 myLabel.setText(myName);
770 
771                 /* Set weekend if required */
772                 if (isWeekend(myDoW)) {
773                     myStyles.add(PanelDay.STYLE_WEEKEND);
774                 }
775             }
776         }
777 
778         /**
779          * ReSize the number of visible rows.
780          *
781          * @param iNumRows number of visible rows
782          */
783         private void reSizeRows(final int iNumRows) {
784             /* Access the children */
785             final ObservableList<Node> myNodes = theGridPane.getChildren();
786 
787             /* Hide any visible rows that should now be hidden */
788             while (iNumRows < theNumRows) {
789                 /* Decrement number of rows */
790                 theNumRows--;
791 
792                 /* Loop through remaining rows */
793                 for (final PanelDay myDay : theDays[theNumRows]) {
794                     /* Remove from panel */
795                     myNodes.remove(myDay.getLabel());
796                 }
797             }
798 
799             /* Show any hidden rows that should now be visible */
800             while (iNumRows > theNumRows) {
801                 /* Increment number of rows */
802                 theNumRows++;
803 
804                 /* Loop through remaining rows */
805                 int iCol = 0;
806                 for (final PanelDay myDay : theDays[theNumRows - 1]) {
807                     /* Add to panel */
808                     theGridPane.add(myDay.getLabel(), iCol++, theNumRows);
809                 }
810             }
811 
812             /* RePack the Dialog */
813             theDialog.reSizeDialog();
814         }
815     }
816 
817     /**
818      * Panel class representing a single day in the panel.
819      */
820     private static final class PanelDay {
821         /**
822          * ToolTip for Current Day.
823          */
824         private static final String NLS_CURRENTDAY = TethysUIResource.DIALOG_CURRENT.getValue();
825 
826         /**
827          * ToolTip for Selected Day.
828          */
829         private static final String NLS_SELECTEDDAY = TethysUIResource.DIALOG_SELECTED.getValue();
830 
831         /**
832          * The panel style.
833          */
834         private static final String STYLE_PANEL = STYLE_DIALOG + "-day";
835 
836         /**
837          * The inactive style.
838          */
839         private static final String STYLE_INACTIVE = STYLE_DIALOG + "-inactive";
840 
841         /**
842          * The selected style.
843          */
844         private static final String STYLE_SELECTED = STYLE_DIALOG + "-selected";
845 
846         /**
847          * The current style.
848          */
849         private static final String STYLE_CURRENT = STYLE_DIALOG + "-current";
850 
851         /**
852          * The weekend style.
853          */
854         private static final String STYLE_WEEKEND = STYLE_DIALOG + "-weekend";
855 
856         /**
857          * The Dialog.
858          */
859         private final TethysUIFXDateDialog theDialog;
860 
861         /**
862          * The Label.
863          */
864         private final Label theLabel;
865 
866         /**
867          * The day of the month.
868          */
869         private int theDay = -1;
870 
871         /**
872          * Constructor.
873          *
874          * @param pDialog the owning dialog
875          */
876         PanelDay(final TethysUIFXDateDialog pDialog) {
877             /* Store parameters */
878             theDialog = pDialog;
879 
880             /* Create the label */
881             theLabel = new Label();
882 
883             /* Set width */
884             theLabel.setPrefWidth(PanelMonth.WIDTH_TILE);
885             theLabel.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> theDialog.setSelected(theDay));
886         }
887 
888         /**
889          * Obtain the label.
890          *
891          * @return the label
892          */
893         Label getLabel() {
894             return theLabel;
895         }
896 
897         /**
898          * Reset a Day Label.
899          *
900          * @param isActive true/false
901          */
902         void resetDay(final boolean isActive) {
903             theLabel.setTooltip(null);
904             theLabel.getStyleClass().clear();
905             theLabel.getStyleClass().add(STYLE_PANEL);
906             if (!isActive) {
907                 theLabel.getStyleClass().add(STYLE_INACTIVE);
908             }
909         }
910 
911         /**
912          * Set day for label.
913          *
914          * @param pDay        the Day number
915          * @param pSelectable is the day select-able
916          */
917         void setDay(final int pDay,
918                     final boolean pSelectable) {
919             /* Record the day */
920             theDay = pDay;
921 
922             /* Set the text for the item */
923             if (pDay > 0) {
924                 theLabel.setText(Integer.toString(theDay));
925             } else {
926                 theLabel.setText("");
927             }
928 
929             /* Enable/Disable the label */
930             theLabel.setDisable(!pSelectable);
931         }
932 
933         /**
934          * Set weekend.
935          */
936         void setWeekend() {
937             theLabel.getStyleClass().add(STYLE_WEEKEND);
938         }
939 
940         /**
941          * Set selected day.
942          */
943         void setSelected() {
944             theLabel.getStyleClass().add(STYLE_SELECTED);
945             theLabel.setTooltip(new Tooltip(NLS_SELECTEDDAY));
946         }
947 
948         /**
949          * Set current day.
950          */
951         void setCurrent() {
952             theLabel.getStyleClass().add(STYLE_CURRENT);
953             theLabel.setTooltip(new Tooltip(NLS_CURRENTDAY));
954         }
955     }
956 }