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.swing.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.swing.base.TethysUISwingArrowIcon;
27  
28  import javax.swing.AbstractAction;
29  import javax.swing.ActionMap;
30  import javax.swing.BorderFactory;
31  import javax.swing.Box;
32  import javax.swing.BoxLayout;
33  import javax.swing.InputMap;
34  import javax.swing.JButton;
35  import javax.swing.JComponent;
36  import javax.swing.JDialog;
37  import javax.swing.JLabel;
38  import javax.swing.JPanel;
39  import javax.swing.KeyStroke;
40  import javax.swing.SwingConstants;
41  import javax.swing.border.Border;
42  import java.awt.BorderLayout;
43  import java.awt.Color;
44  import java.awt.Font;
45  import java.awt.GridLayout;
46  import java.awt.Insets;
47  import java.awt.Point;
48  import java.awt.Rectangle;
49  import java.awt.Window;
50  import java.awt.event.ActionEvent;
51  import java.awt.event.KeyEvent;
52  import java.awt.event.MouseAdapter;
53  import java.awt.event.MouseEvent;
54  import java.awt.event.WindowAdapter;
55  import java.awt.event.WindowEvent;
56  import java.io.Serial;
57  import java.time.DayOfWeek;
58  import java.time.format.TextStyle;
59  import java.time.temporal.WeekFields;
60  import java.util.Locale;
61  
62  /**
63   * Swing Date Dialog.
64   */
65  public final class TethysUISwingDateDialog
66          implements OceanusEventProvider<TethysUIEvent> {
67      /**
68       * Null Date selection text.
69       */
70      private static final String NLS_NULLSELECT = TethysUIResource.DIALOG_NULL.getValue();
71  
72      /**
73       * Escape action text.
74       */
75      private static final String ACTION_ESCAPE = "Escape";
76  
77      /**
78       * The dialog.
79       */
80      private final JDialog theDialog;
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 JButton 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 panel.
119      */
120     private final JPanel 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     TethysUISwingDateDialog(final OceanusDateConfig pConfig) {
133         /* Initialise the dialog */
134         theDialog = new JDialog();
135 
136         /* Set as undecorated */
137         theDialog.setUndecorated(true);
138 
139         /* Store the DateConfig */
140         theConfig = pConfig;
141 
142         /* Create the event manager */
143         theEventManager = new OceanusEventManager<>();
144 
145         /* Build the panels */
146         theDaysPanel = new PanelMonth(this);
147         theNavigation = new PanelNavigation(this);
148 
149         /* Build the Null Select */
150         theNullButton = new JButton(NLS_NULLSELECT);
151         theNullButton.addActionListener(e -> setSelected(-1));
152 
153         /* Set this to be the main panel */
154         theContainer = new JPanel(new BorderLayout());
155         theContainer.setBorder(BorderFactory.createLineBorder(Color.black));
156         theContainer.add(theNavigation.getPanel(), BorderLayout.NORTH);
157         theContainer.add(theDaysPanel.getPanel(), BorderLayout.CENTER);
158         theDialog.setContentPane(theContainer);
159         theDialog.pack();
160 
161         /* Initialise the month */
162         initialiseMonth();
163 
164         /* Handle Escape Key */
165         handleEscapeKey(theContainer);
166 
167         /* Create focus listener */
168         theDialog.addWindowFocusListener(new CalendarFocus());
169 
170         /* Add listener to Configuration to reBuild Names */
171         theConfig.getEventRegistrar().addEventListener(e -> doBuildNames());
172     }
173 
174     @Override
175     public OceanusEventRegistrar<TethysUIEvent> getEventRegistrar() {
176         return theEventManager.getEventRegistrar();
177     }
178 
179     /**
180      * Request a rebuild of panel names.
181      */
182     private void doBuildNames() {
183         doBuildNames = true;
184     }
185 
186     /**
187      * Have we selected a date?
188      *
189      * @return true/false
190      */
191     public boolean haveSelected() {
192         return haveSelected;
193     }
194 
195     /**
196      * Obtain Date Configuration.
197      *
198      * @return the date configuration
199      */
200     public OceanusDateConfig getConfig() {
201         return theConfig;
202     }
203 
204     /**
205      * Build the month.
206      */
207     void buildMonth() {
208         /* Build the month */
209         theNavigation.buildMonth();
210         theDaysPanel.buildMonth();
211     }
212 
213     /**
214      * Set Selected Date.
215      *
216      * @param pDay the Selected day
217      */
218     void setSelected(final int pDay) {
219         /* Set the selected day */
220         theConfig.setSelectedDay(pDay);
221 
222         /* Note that we have selected */
223         haveSelected = true;
224 
225         /* Close the dialog */
226         theDialog.setVisible(false);
227 
228         /* Note that selection has been made */
229         theEventManager.fireEvent(TethysUIEvent.NEWVALUE, theConfig.getSelectedDate());
230     }
231 
232     /**
233      * Resize the dialog.
234      */
235     void reSizeDialog() {
236         theDialog.pack();
237     }
238 
239     /**
240      * InitialiseMonth.
241      */
242     private void initialiseMonth() {
243         /* Initialise the current month */
244         theConfig.initialiseCurrent();
245 
246         /* Build the day names if required */
247         if (doBuildNames) {
248             theDaysPanel.buildDayNames();
249         }
250         doBuildNames = false;
251 
252         /* Build detail */
253         buildMonth();
254 
255         /* If we need to change the visibility of the null button */
256         if (isNullActive != theConfig.allowNullDateSelection()) {
257             /* Add/Remove the button */
258             if (!isNullActive) {
259                 theContainer.add(theNullButton, BorderLayout.SOUTH);
260             } else {
261                 theContainer.remove(theNullButton);
262             }
263 
264             /* Record status and resize the dialog */
265             isNullActive = theConfig.allowNullDateSelection();
266             reSizeDialog();
267         }
268     }
269 
270     /**
271      * Show the dialog.
272      *
273      * @param pNode the node under which to show the dialog
274      */
275     public void showDialogUnderNode(final JComponent pNode) {
276         /* Position the dialog just below the node */
277         final Point myLoc = pNode.getLocationOnScreen();
278         theDialog.setLocation(myLoc.x, myLoc.y
279                 + pNode.getHeight());
280 
281         /* Show the dialog */
282         showDialog();
283     }
284 
285     /**
286      * Show the dialog.
287      *
288      * @param pRect the rectangle under which to show the dialog
289      */
290     public void showDialogUnderRectangle(final Rectangle pRect) {
291         /* Position the dialog just below the rectangle */
292         theDialog.setLocation(pRect.x, pRect.y + pRect.height);
293 
294         /* Show the dialog */
295         showDialog();
296     }
297 
298     /**
299      * Show the dialog.
300      */
301     private void showDialog() {
302         /* Allow configuration to be updated */
303         theEventManager.fireEvent(TethysUIEvent.PREPAREDIALOG, theConfig);
304 
305         /* Note that we have not selected */
306         haveSelected = false;
307 
308         /* Initialise the current month and show the dialog */
309         initialiseMonth();
310         theDialog.setVisible(true);
311         theDialog.requestFocus();
312     }
313 
314     /**
315      * Handle Escape to close dialog.
316      *
317      * @param pPane the panel to handle keys for
318      */
319     private void handleEscapeKey(final JPanel pPane) {
320         /* Access Maps */
321         final ActionMap myAction = pPane.getActionMap();
322         final InputMap myInput = pPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
323 
324         /* Build the maps */
325         myInput.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), ACTION_ESCAPE);
326         myAction.put(ACTION_ESCAPE, new CalendarAction());
327     }
328 
329     /**
330      * Close non-Modal.
331      */
332     void closeNonModal() {
333         /* Hide the dialog */
334         theDialog.setVisible(false);
335 
336         /* Note that no selection has been made */
337         theEventManager.fireEvent(TethysUIEvent.WINDOWCLOSED);
338     }
339 
340     /**
341      * CalendarAction Handle escape action.
342      */
343     private final class CalendarAction
344             extends AbstractAction {
345         /**
346          * SerialId.
347          */
348         @Serial
349         private static final long serialVersionUID = 5464442251457102478L;
350 
351         @Override
352         public void actionPerformed(final ActionEvent e) {
353             closeNonModal();
354         }
355     }
356 
357     /**
358      * CalendarFocus Handle loss of focus.
359      */
360     private final class CalendarFocus
361             extends WindowAdapter {
362         @Override
363         public void windowLostFocus(final WindowEvent e) {
364             final Window myOppo = e.getOppositeWindow();
365             /*
366              * Ignore loss of focus to unidentified window. This is to bypass a problem in browser
367              * applets where a temporary loss of focus occurs immediately upon display of a dialog.
368              */
369             if (myOppo != null) {
370                 closeNonModal();
371             }
372         }
373     }
374 
375     /**
376      * PanelNavigation class allowing navigation between months.
377      */
378     private static final class PanelNavigation {
379         /**
380          * ToolTip for Next Month.
381          */
382         private static final String NLS_NEXTMONTH = TethysUIResource.DIALOG_NEXTMONTH.getValue();
383 
384         /**
385          * ToolTip for Previous Month.
386          */
387         private static final String NLS_PREVMONTH = TethysUIResource.DIALOG_PREVMONTH.getValue();
388 
389         /**
390          * ToolTip for Next Year.
391          */
392         private static final String NLS_NEXTYEAR = TethysUIResource.DIALOG_NEXTYEAR.getValue();
393 
394         /**
395          * ToolTip for Previous Year.
396          */
397         private static final String NLS_PREVYEAR = TethysUIResource.DIALOG_PREVYEAR.getValue();
398 
399         /**
400          * The Panel.
401          */
402         private final JPanel thePanel;
403 
404         /**
405          * The owning dialog.
406          */
407         private final TethysUISwingDateDialog theDialog;
408 
409         /**
410          * The Date Configuration.
411          */
412         private final OceanusDateConfig theConfig;
413 
414         /**
415          * The Date Label.
416          */
417         private final JLabel theDateLabel;
418 
419         /**
420          * The Previous Month Button.
421          */
422         private final JButton thePrevMonthButton;
423 
424         /**
425          * The Next Month Button.
426          */
427         private final JButton theNextMonthButton;
428 
429         /**
430          * The Previous Year Button.
431          */
432         private final JButton thePrevYearButton;
433 
434         /**
435          * The Next Year Button.
436          */
437         private final JButton theNextYearButton;
438 
439         /**
440          * Constructor.
441          *
442          * @param pDialog the owning dialog
443          */
444         PanelNavigation(final TethysUISwingDateDialog pDialog) {
445             /* Record the dialog */
446             theDialog = pDialog;
447 
448             /* Create the panel */
449             thePanel = new JPanel();
450 
451             /* Store the Date Configuration */
452             theConfig = pDialog.getConfig();
453 
454             /* Create the label */
455             theDateLabel = new JLabel();
456 
457             /* Create the buttons */
458             thePrevMonthButton = new JButton(TethysUISwingArrowIcon.LEFT);
459             theNextMonthButton = new JButton(TethysUISwingArrowIcon.RIGHT);
460             thePrevYearButton = new JButton(TethysUISwingArrowIcon.DOUBLELEFT);
461             theNextYearButton = new JButton(TethysUISwingArrowIcon.DOUBLERIGHT);
462 
463             /* Add ToopTips */
464             theNextMonthButton.setToolTipText(NLS_NEXTMONTH);
465             thePrevMonthButton.setToolTipText(NLS_PREVMONTH);
466             theNextYearButton.setToolTipText(NLS_NEXTYEAR);
467             thePrevYearButton.setToolTipText(NLS_PREVYEAR);
468 
469             /* Listen for button events */
470             thePrevMonthButton.addActionListener(e -> {
471                 theConfig.previousMonth();
472                 theDialog.buildMonth();
473             });
474             theNextMonthButton.addActionListener(e -> {
475                 theConfig.nextMonth();
476                 theDialog.buildMonth();
477             });
478             thePrevYearButton.addActionListener(e -> {
479                 theConfig.previousYear();
480                 theDialog.buildMonth();
481             });
482             theNextYearButton.addActionListener(e -> {
483                 theConfig.nextYear();
484                 theDialog.buildMonth();
485             });
486 
487             /* Restrict the margins */
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             /* Add these elements into a box */
494             thePanel.setLayout(new BoxLayout(thePanel, BoxLayout.X_AXIS));
495             thePanel.add(thePrevYearButton);
496             thePanel.add(Box.createHorizontalStrut(1));
497             thePanel.add(thePrevMonthButton);
498             thePanel.add(Box.createHorizontalGlue());
499             thePanel.add(theDateLabel);
500             thePanel.add(Box.createHorizontalGlue());
501             thePanel.add(theNextMonthButton);
502             thePanel.add(Box.createHorizontalStrut(1));
503             thePanel.add(theNextYearButton);
504         }
505 
506         /**
507          * Obtain the panel.
508          *
509          * @return the panel
510          */
511         JPanel getPanel() {
512             return thePanel;
513         }
514 
515         /**
516          * Build month details.
517          */
518         private void buildMonth() {
519             /* Store the active month */
520             final OceanusDate myBase = theConfig.getCurrentMonth();
521             final Locale myLocale = theConfig.getLocale();
522 
523             /* Determine the display for the label */
524             final String myMonth = myBase.getMonthValue().getDisplayName(TextStyle.FULL, myLocale);
525             final String myYear = Integer.toString(myBase.getYear());
526 
527             /* Set the label */
528             theDateLabel.setText(myMonth
529                     + ", "
530                     + myYear);
531 
532             /* Access boundary dates */
533             final OceanusDate myEarliest = theConfig.getEarliestDate();
534             final OceanusDate myLatest = theConfig.getLatestDate();
535 
536             /* Enable/Disable buttons as required */
537             thePrevMonthButton.setEnabled(!OceanusDateConfig.isSameMonth(myEarliest, myBase));
538             thePrevYearButton.setEnabled(!OceanusDateConfig.isSameYear(myEarliest, myBase));
539             theNextMonthButton.setEnabled(!OceanusDateConfig.isSameMonth(myLatest, myBase));
540             theNextYearButton.setEnabled(!OceanusDateConfig.isSameYear(myLatest, myBase));
541         }
542     }
543 
544     /**
545      * PanelMonth class representing the set of PanelDay labels in a month.
546      */
547     private static final class PanelMonth {
548         /**
549          * Number of days in week.
550          */
551         private static final int DAYS_IN_WEEK = 7;
552 
553         /**
554          * Maximum # of weeks in month.
555          */
556         private static final int MAX_WEEKS_IN_MONTH = 6;
557 
558         /**
559          * The array of days of week (in column order).
560          */
561         private final DayOfWeek[] theDaysOfWk = new DayOfWeek[DAYS_IN_WEEK];
562 
563         /**
564          * The Array of Day Names.
565          */
566         private final JLabel[] theHdrs = new JLabel[DAYS_IN_WEEK];
567 
568         /**
569          * The Array of Day Labels.
570          */
571         private final PanelDay[][] theDays = new PanelDay[MAX_WEEKS_IN_MONTH][DAYS_IN_WEEK];
572 
573         /**
574          * The Panel.
575          */
576         private final JPanel thePanel;
577 
578         /**
579          * The Dialog.
580          */
581         private final TethysUISwingDateDialog theDialog;
582 
583         /**
584          * The Date Configuration.
585          */
586         private final OceanusDateConfig theConfig;
587 
588         /**
589          * The number of currently visible rows.
590          */
591         private int theNumRows = MAX_WEEKS_IN_MONTH;
592 
593         /**
594          * Constructor.
595          *
596          * @param pDialog the owning dialog
597          */
598         PanelMonth(final TethysUISwingDateDialog pDialog) {
599             /* Create the panel */
600             thePanel = new JPanel();
601 
602             /* Store the dialog */
603             theDialog = pDialog;
604 
605             /* Store the Date Configuration */
606             theConfig = pDialog.getConfig();
607 
608             /* Set this as a 7x7 GridLayout */
609             final GridLayout myLayout = new GridLayout();
610             myLayout.setColumns(DAYS_IN_WEEK);
611             myLayout.setRows(0);
612             thePanel.setLayout(myLayout);
613 
614             /* Loop through the labels */
615             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
616                 /* Access the label */
617                 final JLabel myLabel = new JLabel();
618                 theHdrs[iCol] = myLabel;
619 
620                 /* Set colour */
621                 myLabel.setHorizontalAlignment(SwingConstants.CENTER);
622                 myLabel.setBackground(Color.lightGray);
623                 myLabel.setOpaque(true);
624 
625                 /* Add to the grid */
626                 thePanel.add(myLabel);
627             }
628 
629             /* Add the Days to the layout */
630             for (int iRow = 0; iRow < MAX_WEEKS_IN_MONTH; iRow++) {
631                 for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
632                     final PanelDay myDay = new PanelDay(pDialog);
633                     theDays[iRow][iCol] = myDay;
634                     thePanel.add(myDay.getLabel());
635                 }
636             }
637         }
638 
639         /**
640          * Obtain the panel.
641          *
642          * @return the panel
643          */
644         JPanel getPanel() {
645             return thePanel;
646         }
647 
648         /**
649          * ReSize the number of visible rows.
650          *
651          * @param iNumRows number of visible rows
652          */
653         private void reSizeRows(final int iNumRows) {
654             /* Hide any visible rows that should now be hidden */
655             while (iNumRows < theNumRows) {
656                 /* Decrement number of rows */
657                 theNumRows--;
658 
659                 /* Loop through remaining rows */
660                 for (final PanelDay day : theDays[theNumRows]) {
661                     /* Remove from panel */
662                     thePanel.remove(day.getLabel());
663                 }
664             }
665 
666             /* Show any hidden rows that should now be visible */
667             while (iNumRows > theNumRows) {
668                 /* Loop through remaining rows */
669                 for (final PanelDay day : theDays[theNumRows]) {
670                     /* Add to panel */
671                     thePanel.add(day.getLabel());
672                 }
673 
674                 /* Increment number of rows */
675                 theNumRows++;
676             }
677 
678             /* RePack the Dialog */
679             theDialog.reSizeDialog();
680         }
681 
682         /**
683          * Is the DayOfWeek a Weekend day.
684          *
685          * @param pDoW the day of the week
686          * @return true/false
687          */
688         private static boolean isWeekend(final DayOfWeek pDoW) {
689             return switch (pDoW) {
690                 case SATURDAY, SUNDAY -> true;
691                 default -> false;
692             };
693         }
694 
695         /**
696          * obtain column number for DayOfWeek.
697          *
698          * @param pDoW the day of the week
699          * @return the column number
700          */
701         private int getDayColumn(final DayOfWeek pDoW) {
702             for (int i = 0; i < DAYS_IN_WEEK; i++) {
703                 if (theDaysOfWk[i].equals(pDoW)) {
704                     return i;
705                 }
706             }
707             return -1;
708         }
709 
710         /**
711          * build the month display for the requested month.
712          */
713         private void buildMonth() {
714             int iRow = 0;
715             int iCol = 0;
716 
717             /* Access the current month */
718             final OceanusDate myCurr = new OceanusDate(theConfig.getCurrentMonth());
719             final int iMonth = myCurr.getMonth();
720 
721             /* Access the Weekday of the 1st of the month */
722             final DayOfWeek myWeekDay = myCurr.getDayOfWeek();
723             final int iFirstCol = getDayColumn(myWeekDay);
724 
725             /* Access the interesting days of the month */
726             final int iCurrent = theConfig.getCurrentDay();
727             final int iSelected = theConfig.getSelectedDay();
728             final int iEarliest = theConfig.getEarliestDay();
729             final int iLatest = theConfig.getLatestDay();
730 
731             /* Adjust the day to beginning of week if required */
732             if (iFirstCol > 0) {
733                 myCurr.adjustDay(-iFirstCol);
734             }
735 
736             /* Loop through initial columns */
737             for (int iDay = myCurr.getDay(); iCol < iFirstCol; iCol++, iDay++, myCurr.adjustDay(1)) {
738                 /* Access the label */
739                 final PanelDay myLabel = theDays[0][iCol];
740 
741                 /* Reset the day and set no day */
742                 myLabel.resetDay(false);
743                 myLabel.setDay(iDay, false);
744             }
745 
746             /* Loop through the days of the month */
747             for (int iDay = 1; iMonth == myCurr.getMonth(); iCol++, iDay++, myCurr.adjustDay(1)) {
748                 /* Reset column if necessary */
749                 if (iCol > MAX_WEEKS_IN_MONTH) {
750                     iRow++;
751                     iCol = 0;
752                 }
753 
754                 /* Access the label */
755                 final PanelDay myLabel = theDays[iRow][iCol];
756 
757                 /* Set initial parts of the day */
758                 myLabel.resetDay(true);
759                 if (isWeekend(myCurr.getDayOfWeek())) {
760                     myLabel.setWeekend();
761                 }
762                 if (iCurrent == iDay) {
763                     myLabel.setCurrent();
764                 }
765                 if (iSelected == iDay) {
766                     myLabel.setSelected();
767                 }
768 
769                 /* Determine whether the day is select-able */
770                 boolean isSelectable = true;
771                 if (iEarliest > 0) {
772                     isSelectable &= iDay >= iEarliest;
773                 }
774                 if (iLatest > 0) {
775                     isSelectable &= iDay <= iLatest;
776                 }
777 
778                 /* Check for allowed date */
779                 isSelectable &= theConfig.isAllowed(iDay);
780 
781                 /* Set the day */
782                 myLabel.setDay(iDay, isSelectable);
783             }
784 
785             /* Loop through remaining columns */
786             for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
787                 /* Access the label */
788                 final PanelDay myLabel = theDays[iRow][iCol];
789 
790                 /* Reset the day and set no day */
791                 myLabel.resetDay(false);
792                 myLabel.setDay(iDay, false);
793             }
794 
795             /* Ensure correct number of rows are visible */
796             reSizeRows(iRow + 1);
797         }
798 
799         /**
800          * build Day names.
801          */
802         void buildDayNames() {
803             /* Get today's date */
804             final Locale myLocale = theConfig.getLocale();
805             final WeekFields myWeekFields = WeekFields.of(myLocale);
806 
807             /* Build the array of the days of the week */
808             DayOfWeek myDoW = myWeekFields.getFirstDayOfWeek();
809             for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
810                 /* Store the day into the array */
811                 theDaysOfWk[iDay] = myDoW;
812             }
813 
814             /* Loop through the labels */
815             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
816                 /* Access the label */
817                 final JLabel myLabel = theHdrs[iCol];
818 
819                 /* Access the required name */
820                 myDoW = theDaysOfWk[iCol];
821                 final TextStyle myStyle = theConfig.showNarrowDays()
822                         ? TextStyle.NARROW
823                         : TextStyle.SHORT;
824                 final String myName = myDoW.getDisplayName(myStyle, myLocale);
825 
826                 /* Set the name */
827                 myLabel.setText(myName);
828 
829                 /* Set colour */
830                 myLabel.setForeground(isWeekend(myDoW)
831                         ? Color.red
832                         : Color.black);
833             }
834         }
835     }
836 
837     /**
838      * Panel class representing a single day in the panel.
839      */
840     private static final class PanelDay {
841         /**
842          * The standard border.
843          */
844         private static final Border BORDER_STD = BorderFactory.createEmptyBorder();
845 
846         /**
847          * The selected border.
848          */
849         private static final Border BORDER_SEL = BorderFactory.createLineBorder(Color.green.darker());
850 
851         /**
852          * The highlighted border.
853          */
854         private static final Border BORDER_HLT = BorderFactory.createLineBorder(Color.orange);
855 
856         /**
857          * Standard font name.
858          */
859         private static final String FONT_NAME = "Courier";
860 
861         /**
862          * Standard font size.
863          */
864         private static final int FONT_SIZE = 10;
865 
866         /**
867          * Standard font.
868          */
869         private static final Font FONT_STANDARD = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
870 
871         /**
872          * Inactive font.
873          */
874         private static final Font FONT_INACTIVE = new Font(FONT_NAME, Font.ITALIC, FONT_SIZE);
875 
876         /**
877          * Selected font.
878          */
879         private static final Font FONT_SELECTED = new Font(FONT_NAME, Font.BOLD, FONT_SIZE);
880 
881         /**
882          * ToolTip for Current Day.
883          */
884         private static final String NLS_CURRENTDAY = TethysUIResource.DIALOG_CURRENT.getValue();
885 
886         /**
887          * ToolTip for Selected Day.
888          */
889         private static final String NLS_SELECTEDDAY = TethysUIResource.DIALOG_SELECTED.getValue();
890 
891         /**
892          * The Label.
893          */
894         private final JLabel theLabel;
895 
896         /**
897          * Owning dialog.
898          */
899         private final TethysUISwingDateDialog theDialog;
900 
901         /**
902          * The Day that this Label represents.
903          */
904         private int theDay = -1;
905 
906         /**
907          * Is the day select-able.
908          */
909         private boolean isSelectable;
910 
911         /**
912          * The font.
913          */
914         private Font theFont;
915 
916         /**
917          * The foreground colour.
918          */
919         private Color theForeGround;
920 
921         /**
922          * The background colour.
923          */
924         private Color theBackGround;
925 
926         /**
927          * The border.
928          */
929         private Border theBorder;
930 
931         /**
932          * The toolTip.
933          */
934         private String theToolTip;
935 
936         /**
937          * Constructor.
938          *
939          * @param pDialog the owning dialog
940          */
941         PanelDay(final TethysUISwingDateDialog pDialog) {
942             /* Store the parameter */
943             theDialog = pDialog;
944 
945             /* Create the label */
946             theLabel = new JLabel();
947 
948             /* Initialise values */
949             theLabel.setHorizontalAlignment(SwingConstants.CENTER);
950             theLabel.setOpaque(true);
951             theLabel.addMouseListener(new CalendarMouse());
952         }
953 
954         /**
955          * Obtain the label.
956          *
957          * @return the label
958          */
959         JLabel getLabel() {
960             return theLabel;
961         }
962 
963         /**
964          * Set day for label.
965          *
966          * @param pDay        the Day number
967          * @param pSelectable is the day select-able
968          */
969         void setDay(final int pDay,
970                     final boolean pSelectable) {
971             /* Record the day */
972             theDay = pDay;
973             isSelectable = pSelectable;
974 
975             /* Set the text for the item */
976             if (pDay > 0) {
977                 theLabel.setText(Integer.toString(theDay));
978             } else {
979                 theLabel.setText("");
980             }
981 
982             /* Set Characteristics */
983             theLabel.setFont(theFont);
984             theLabel.setForeground(theForeGround);
985             theLabel.setBackground(theBackGround);
986             theLabel.setBorder(theBorder);
987             theLabel.setToolTipText(theToolTip);
988 
989             /* Enable/Disable the label */
990             theLabel.setEnabled(isSelectable);
991         }
992 
993         /**
994          * Reset a Day Label.
995          *
996          * @param isActive true/false
997          */
998         void resetDay(final boolean isActive) {
999             /* Record detail */
1000             theFont = isActive
1001                     ? FONT_STANDARD
1002                     : FONT_INACTIVE;
1003             theForeGround = Color.black;
1004             theBackGround = Color.white;
1005             theBorder = BORDER_STD;
1006             theToolTip = null;
1007             isSelectable = isActive;
1008         }
1009 
1010         /**
1011          * Set a day as a Weekend.
1012          */
1013         void setWeekend() {
1014             /* Record detail */
1015             theForeGround = Color.red;
1016         }
1017 
1018         /**
1019          * Set a day as Current Day.
1020          */
1021         void setCurrent() {
1022             /* Record detail */
1023             theForeGround = Color.blue;
1024             theBackGround = Color.gray;
1025             theToolTip = NLS_CURRENTDAY;
1026         }
1027 
1028         /**
1029          * Set a day as Selected Day.
1030          */
1031         void setSelected() {
1032             /* Record detail */
1033             theFont = FONT_SELECTED;
1034             theForeGround = Color.green.darker();
1035             theBackGround = Color.green.brighter();
1036             theBorder = BORDER_SEL;
1037             theToolTip = NLS_SELECTEDDAY;
1038         }
1039 
1040         /**
1041          * Handle mouseClicked.
1042          */
1043         void handleMouseClicked() {
1044             /* If item is select-able */
1045             if (isSelectable) {
1046                 theDialog.setSelected(theDay);
1047             }
1048         }
1049 
1050         /**
1051          * Handle mouseEntered.
1052          */
1053         void handleMouseEntered() {
1054             /* Highlight the border of a select-able item */
1055             if (isSelectable) {
1056                 theLabel.setBorder(BORDER_HLT);
1057             }
1058         }
1059 
1060         /**
1061          * Handle mouseExited.
1062          */
1063         void handleMouseExited() {
1064             /* Reset border to standard for label that has changed */
1065             if (isSelectable) {
1066                 theLabel.setBorder(theBorder);
1067             }
1068         }
1069 
1070         /**
1071          * CalendarMouse.
1072          */
1073         private final class CalendarMouse
1074                 extends MouseAdapter {
1075             @Override
1076             public void mouseClicked(final MouseEvent e) {
1077                 handleMouseClicked();
1078             }
1079 
1080             @Override
1081             public void mouseEntered(final MouseEvent e) {
1082                 handleMouseEntered();
1083             }
1084 
1085             @Override
1086             public void mouseExited(final MouseEvent e) {
1087                 handleMouseExited();
1088             }
1089         }
1090     }
1091 }