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.util.Calendar;
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             switch (pDoW) {
690                 case SATURDAY:
691                 case SUNDAY:
692                     return true;
693                 default:
694                     return false;
695             }
696         }
697 
698         /**
699          * obtain column number for DayOfWeek.
700          *
701          * @param pDoW the day of the week
702          * @return the column number
703          */
704         private int getDayColumn(final DayOfWeek pDoW) {
705             for (int i = 0; i < DAYS_IN_WEEK; i++) {
706                 if (theDaysOfWk[i] == pDoW) {
707                     return i;
708                 }
709             }
710             return -1;
711         }
712 
713         /**
714          * build the month display for the requested month.
715          */
716         private void buildMonth() {
717             int iRow = 0;
718             int iCol = 0;
719 
720             /* Access the current month */
721             final OceanusDate myCurr = new OceanusDate(theConfig.getCurrentMonth());
722             final int iMonth = myCurr.getMonth();
723 
724             /* Access the Weekday of the 1st of the month */
725             final DayOfWeek myWeekDay = myCurr.getDayOfWeek();
726             final int iFirstCol = getDayColumn(myWeekDay);
727 
728             /* Access the interesting days of the month */
729             final int iCurrent = theConfig.getCurrentDay();
730             final int iSelected = theConfig.getSelectedDay();
731             final int iEarliest = theConfig.getEarliestDay();
732             final int iLatest = theConfig.getLatestDay();
733 
734             /* Adjust the day to beginning of week if required */
735             if (iFirstCol > 0) {
736                 myCurr.adjustDay(-iFirstCol);
737             }
738 
739             /* Loop through initial columns */
740             for (int iDay = myCurr.getDay(); iCol < iFirstCol; iCol++, iDay++, myCurr.adjustDay(1)) {
741                 /* Access the label */
742                 final PanelDay myLabel = theDays[0][iCol];
743 
744                 /* Reset the day and set no day */
745                 myLabel.resetDay(false);
746                 myLabel.setDay(iDay, false);
747             }
748 
749             /* Loop through the days of the month */
750             for (int iDay = 1; iMonth == myCurr.getMonth(); iCol++, iDay++, myCurr.adjustDay(1)) {
751                 /* Reset column if necessary */
752                 if (iCol > MAX_WEEKS_IN_MONTH) {
753                     iRow++;
754                     iCol = 0;
755                 }
756 
757                 /* Access the label */
758                 final PanelDay myLabel = theDays[iRow][iCol];
759 
760                 /* Set initial parts of the day */
761                 myLabel.resetDay(true);
762                 if (isWeekend(myCurr.getDayOfWeek())) {
763                     myLabel.setWeekend();
764                 }
765                 if (iCurrent == iDay) {
766                     myLabel.setCurrent();
767                 }
768                 if (iSelected == iDay) {
769                     myLabel.setSelected();
770                 }
771 
772                 /* Determine whether the day is select-able */
773                 boolean isSelectable = true;
774                 if (iEarliest > 0) {
775                     isSelectable &= iDay >= iEarliest;
776                 }
777                 if (iLatest > 0) {
778                     isSelectable &= iDay <= iLatest;
779                 }
780 
781                 /* Check for allowed date */
782                 isSelectable &= theConfig.isAllowed(iDay);
783 
784                 /* Set the day */
785                 myLabel.setDay(iDay, isSelectable);
786             }
787 
788             /* Loop through remaining columns */
789             for (int iDay = 1; iCol < DAYS_IN_WEEK; iCol++, iDay++) {
790                 /* Access the label */
791                 final PanelDay myLabel = theDays[iRow][iCol];
792 
793                 /* Reset the day and set no day */
794                 myLabel.resetDay(false);
795                 myLabel.setDay(iDay, false);
796             }
797 
798             /* Ensure correct number of rows are visible */
799             reSizeRows(iRow + 1);
800         }
801 
802         /**
803          * build Day names.
804          */
805         void buildDayNames() {
806             /* Get todays date */
807             final Locale myLocale = theConfig.getLocale();
808             final Calendar myDate = Calendar.getInstance(myLocale);
809             int myStart = myDate.getFirstDayOfWeek();
810             if (myStart == Calendar.SUNDAY) {
811                 myStart += DAYS_IN_WEEK;
812             }
813 
814             /* Build the array of the days of the week */
815             DayOfWeek myDoW = DayOfWeek.of(myStart - 1);
816             for (int iDay = 0; iDay < DAYS_IN_WEEK; iDay++, myDoW = myDoW.plus(1)) {
817                 /* Store the day into the array */
818                 theDaysOfWk[iDay] = myDoW;
819             }
820 
821             /* Loop through the labels */
822             for (int iCol = 0; iCol < DAYS_IN_WEEK; iCol++) {
823                 /* Access the label */
824                 final JLabel myLabel = theHdrs[iCol];
825 
826                 /* Access the required name */
827                 myDoW = theDaysOfWk[iCol];
828                 final TextStyle myStyle = theConfig.showNarrowDays()
829                         ? TextStyle.NARROW
830                         : TextStyle.SHORT;
831                 final String myName = myDoW.getDisplayName(myStyle, myLocale);
832 
833                 /* Set the name */
834                 myLabel.setText(myName);
835 
836                 /* Set colour */
837                 myLabel.setForeground(isWeekend(myDoW)
838                         ? Color.red
839                         : Color.black);
840             }
841         }
842     }
843 
844     /**
845      * Panel class representing a single day in the panel.
846      */
847     private static final class PanelDay {
848         /**
849          * The standard border.
850          */
851         private static final Border BORDER_STD = BorderFactory.createEmptyBorder();
852 
853         /**
854          * The selected border.
855          */
856         private static final Border BORDER_SEL = BorderFactory.createLineBorder(Color.green.darker());
857 
858         /**
859          * The highlighted border.
860          */
861         private static final Border BORDER_HLT = BorderFactory.createLineBorder(Color.orange);
862 
863         /**
864          * Standard font name.
865          */
866         private static final String FONT_NAME = "Courier";
867 
868         /**
869          * Standard font size.
870          */
871         private static final int FONT_SIZE = 10;
872 
873         /**
874          * Standard font.
875          */
876         private static final Font FONT_STANDARD = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
877 
878         /**
879          * Inactive font.
880          */
881         private static final Font FONT_INACTIVE = new Font(FONT_NAME, Font.ITALIC, FONT_SIZE);
882 
883         /**
884          * Selected font.
885          */
886         private static final Font FONT_SELECTED = new Font(FONT_NAME, Font.BOLD, FONT_SIZE);
887 
888         /**
889          * ToolTip for Current Day.
890          */
891         private static final String NLS_CURRENTDAY = TethysUIResource.DIALOG_CURRENT.getValue();
892 
893         /**
894          * ToolTip for Selected Day.
895          */
896         private static final String NLS_SELECTEDDAY = TethysUIResource.DIALOG_SELECTED.getValue();
897 
898         /**
899          * The Label.
900          */
901         private final JLabel theLabel;
902 
903         /**
904          * Owning dialog.
905          */
906         private final TethysUISwingDateDialog theDialog;
907 
908         /**
909          * The Day that this Label represents.
910          */
911         private int theDay = -1;
912 
913         /**
914          * Is the day select-able.
915          */
916         private boolean isSelectable;
917 
918         /**
919          * The font.
920          */
921         private Font theFont;
922 
923         /**
924          * The foreground colour.
925          */
926         private Color theForeGround;
927 
928         /**
929          * The background colour.
930          */
931         private Color theBackGround;
932 
933         /**
934          * The border.
935          */
936         private Border theBorder;
937 
938         /**
939          * The toolTip.
940          */
941         private String theToolTip;
942 
943         /**
944          * Constructor.
945          *
946          * @param pDialog the owning dialog
947          */
948         PanelDay(final TethysUISwingDateDialog pDialog) {
949             /* Store the parameter */
950             theDialog = pDialog;
951 
952             /* Create the label */
953             theLabel = new JLabel();
954 
955             /* Initialise values */
956             theLabel.setHorizontalAlignment(SwingConstants.CENTER);
957             theLabel.setOpaque(true);
958             theLabel.addMouseListener(new CalendarMouse());
959         }
960 
961         /**
962          * Obtain the label.
963          *
964          * @return the label
965          */
966         JLabel getLabel() {
967             return theLabel;
968         }
969 
970         /**
971          * Set day for label.
972          *
973          * @param pDay        the Day number
974          * @param pSelectable is the day select-able
975          */
976         void setDay(final int pDay,
977                     final boolean pSelectable) {
978             /* Record the day */
979             theDay = pDay;
980             isSelectable = pSelectable;
981 
982             /* Set the text for the item */
983             if (pDay > 0) {
984                 theLabel.setText(Integer.toString(theDay));
985             } else {
986                 theLabel.setText("");
987             }
988 
989             /* Set Characteristics */
990             theLabel.setFont(theFont);
991             theLabel.setForeground(theForeGround);
992             theLabel.setBackground(theBackGround);
993             theLabel.setBorder(theBorder);
994             theLabel.setToolTipText(theToolTip);
995 
996             /* Enable/Disable the label */
997             theLabel.setEnabled(isSelectable);
998         }
999 
1000         /**
1001          * Reset a Day Label.
1002          *
1003          * @param isActive true/false
1004          */
1005         void resetDay(final boolean isActive) {
1006             /* Record detail */
1007             theFont = isActive
1008                     ? FONT_STANDARD
1009                     : FONT_INACTIVE;
1010             theForeGround = Color.black;
1011             theBackGround = Color.white;
1012             theBorder = BORDER_STD;
1013             theToolTip = null;
1014             isSelectable = isActive;
1015         }
1016 
1017         /**
1018          * Set a day as a Weekend.
1019          */
1020         void setWeekend() {
1021             /* Record detail */
1022             theForeGround = Color.red;
1023         }
1024 
1025         /**
1026          * Set a day as Current Day.
1027          */
1028         void setCurrent() {
1029             /* Record detail */
1030             theForeGround = Color.blue;
1031             theBackGround = Color.gray;
1032             theToolTip = NLS_CURRENTDAY;
1033         }
1034 
1035         /**
1036          * Set a day as Selected Day.
1037          */
1038         void setSelected() {
1039             /* Record detail */
1040             theFont = FONT_SELECTED;
1041             theForeGround = Color.green.darker();
1042             theBackGround = Color.green.brighter();
1043             theBorder = BORDER_SEL;
1044             theToolTip = NLS_SELECTEDDAY;
1045         }
1046 
1047         /**
1048          * Handle mouseClicked.
1049          */
1050         void handleMouseClicked() {
1051             /* If item is select-able */
1052             if (isSelectable) {
1053                 theDialog.setSelected(theDay);
1054             }
1055         }
1056 
1057         /**
1058          * Handle mouseEntered.
1059          */
1060         void handleMouseEntered() {
1061             /* Highlight the border of a select-able item */
1062             if (isSelectable) {
1063                 theLabel.setBorder(BORDER_HLT);
1064             }
1065         }
1066 
1067         /**
1068          * Handle mouseExited.
1069          */
1070         void handleMouseExited() {
1071             /* Reset border to standard for label that has changed */
1072             if (isSelectable) {
1073                 theLabel.setBorder(theBorder);
1074             }
1075         }
1076 
1077         /**
1078          * CalendarMouse.
1079          */
1080         private final class CalendarMouse
1081                 extends MouseAdapter {
1082             @Override
1083             public void mouseClicked(final MouseEvent e) {
1084                 handleMouseClicked();
1085             }
1086 
1087             @Override
1088             public void mouseEntered(final MouseEvent e) {
1089                 handleMouseEntered();
1090             }
1091 
1092             @Override
1093             public void mouseExited(final MouseEvent e) {
1094                 handleMouseExited();
1095             }
1096         }
1097     }
1098 }