1// © 2016 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html#License
3/*
4 *******************************************************************************
5 * Copyright (C) 1996-2014, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9
10package com.ibm.icu.dev.demo.holiday;
11
12import java.awt.BorderLayout;
13import java.awt.Button;
14import java.awt.Canvas;
15import java.awt.Choice;
16import java.awt.Color;
17import java.awt.Component;
18import java.awt.Container;
19import java.awt.Dimension;
20import java.awt.Font;
21import java.awt.FontMetrics;
22import java.awt.Frame;
23import java.awt.Graphics;
24import java.awt.GridBagConstraints;
25import java.awt.GridBagLayout;
26import java.awt.Label;
27import java.awt.Panel;
28import java.awt.Point;
29import java.awt.event.ActionEvent;
30import java.awt.event.ActionListener;
31import java.awt.event.ItemEvent;
32import java.awt.event.ItemListener;
33import java.awt.event.WindowEvent;
34import java.text.DateFormatSymbols;
35import java.util.Date;
36import java.util.Locale;
37import java.util.Vector;
38
39import com.ibm.icu.dev.demo.impl.DemoApplet;
40import com.ibm.icu.dev.demo.impl.DemoTextBox;
41import com.ibm.icu.dev.demo.impl.DemoUtility;
42import com.ibm.icu.text.DateTimePatternGenerator;
43import com.ibm.icu.text.SimpleDateFormat;
44import com.ibm.icu.util.Calendar;
45import com.ibm.icu.util.Holiday;
46
47/**
48 * CalendarDemo demonstrates how Calendar works.
49 */
50public class HolidayCalendarDemo extends DemoApplet
51{
52    /**
53     * For serialization
54     */
55    private static final long serialVersionUID = 4546085430817359372L;
56
57    /**
58     * The main function which defines the behavior of the CalendarDemo
59     * applet when an applet is started.
60     */
61    public static void main(String argv[]) {
62
63        new HolidayCalendarDemo().showDemo();
64    }
65
66    /* This creates a CalendarFrame for the demo applet. */
67    public Frame createDemoFrame(DemoApplet applet) {
68        return new CalendarFrame(applet);
69    }
70
71    /**
72    * A Frame is a top-level window with a title. The default layout for a frame
73    * is BorderLayout.  The CalendarFrame class defines the window layout of
74    * CalendarDemo.
75    */
76    private static class CalendarFrame extends Frame implements ActionListener,
77                                                                ItemListener
78    {
79        /**
80         * For serialization
81         */
82        private static final long serialVersionUID = -7023296782393042761L;
83
84        private static final boolean DEBUG = false;
85
86        //private Locale curLocale = Locale.US; // unused
87
88        private DemoApplet applet;
89
90        private static final Locale[] calendars = {
91            //new Locale("de","AT"),
92            Locale.CANADA,
93            Locale.CANADA_FRENCH,
94            Locale.FRANCE,
95            Locale.GERMANY,
96            new Locale("iw","IL"),
97            new Locale("el","GR"),
98            //new Locale("es","MX"),
99            Locale.UK,
100            Locale.US,
101        };
102        private static final Locale[] displays = {
103            Locale.CANADA,
104            Locale.UK,
105            Locale.US,
106            Locale.FRANCE,
107            Locale.CANADA_FRENCH,
108            //new Locale("de","AT"),
109            Locale.GERMAN,
110            new Locale("el","GR"),
111            //new Locale("iw","IL"),
112            new Locale("es","MX"),
113        };
114
115        /**
116        * Constructs a new CalendarFrame that is initially invisible.
117        */
118        public CalendarFrame(DemoApplet applet)
119        {
120            super("Calendar Demo");
121            this.applet = applet;
122            init();
123            start();
124            enableEvents(WindowEvent.WINDOW_CLOSING);
125        }
126
127        /**
128        * Initializes the applet. You never need to call this directly, it
129        * is called automatically by the system once the applet is created.
130        */
131        public void init()
132        {
133            // Get G7 locales only for demo purpose. To get all the locales
134            // supported, switch to calling Calendar.getAvailableLocales().
135            // commented
136            locales = displays;
137
138            buildGUI();
139        }
140
141        //------------------------------------------------------------
142        // package private
143        //------------------------------------------------------------
144        void addWithFont(Container container, Component foo, Font font) {
145            if (font != null)
146                foo.setFont(font);
147            container.add(foo);
148        }
149
150        /**
151        * Called to start the applet. You never need to call this method
152        * directly, it is called when the applet's document is visited.
153        */
154        public void start()
155        {
156            // do nothing
157        }
158
159        private Choice          localeMenu;
160        private Choice          displayMenu;
161        private Locale[]        locales;
162
163        private Label           monthLabel;
164        private Button          prevYear;
165        private Button          prevMonth;
166        private Button          gotoToday;
167        private Button          nextMonth;
168        private Button          nextYear;
169        private CalendarPanel   calendarPanel;
170
171        private static final Locale kFirstLocale = Locale.US;
172
173        private static void add(Container container, Component component,
174                                GridBagLayout g, GridBagConstraints c)
175        {
176            g.setConstraints(component, c);
177            container.add(component);
178        }
179
180        public void buildGUI()
181        {
182            setBackground(DemoUtility.bgColor);
183            setLayout(new BorderLayout(10,10));
184
185            // Label for the demo's title
186            Label titleLabel = new Label("Calendar Demo", Label.CENTER);
187            titleLabel.setFont(DemoUtility.titleFont);
188
189            // Label for the current month name
190            monthLabel = new Label("", Label.LEFT);
191            monthLabel.setFont(new Font(DemoUtility.titleFont.getName(),
192                                        DemoUtility.titleFont.getStyle(),
193                                        (DemoUtility.titleFont.getSize() * 3)/2));
194
195            // Make the locale popup menus
196            localeMenu= new Choice();
197            localeMenu.addItemListener(this);
198            int selectMe = 0;
199
200            for (int i = 0; i < calendars.length; i++) {
201                if (i > 0 &&
202                        calendars[i].getCountry().equals(calendars[i-1].getCountry()) ||
203                    i < calendars.length - 1 &&
204                        calendars[i].getCountry().equals(calendars[i+1].getCountry()))
205                {
206                    localeMenu.addItem(calendars[i].getDisplayCountry() + " (" +
207                                    calendars[i].getDisplayLanguage() + ")");
208                } else {
209                    localeMenu.addItem( calendars[i].getDisplayCountry() );
210                }
211
212                if (calendars[i].equals(kFirstLocale)) {
213                    selectMe = i;
214                }
215            }
216
217            localeMenu.setBackground(DemoUtility.choiceColor);
218            localeMenu.select(selectMe);
219
220            displayMenu = new Choice();
221            displayMenu.addItemListener(this);
222
223            selectMe = 0;
224            for (int i = 0; i < locales.length; i++) {
225                if (i > 0 &&
226                        locales[i].getLanguage().equals(locales[i-1].getLanguage()) ||
227                    i < locales.length - 1 &&
228                        locales[i].getLanguage().equals(locales[i+1].getLanguage()))
229                {
230                    displayMenu.addItem( locales[i].getDisplayName() );
231                } else {
232                    displayMenu.addItem( locales[i].getDisplayLanguage());
233                }
234
235                if (locales[i].equals(kFirstLocale)) {
236                    selectMe = i;
237                }
238            }
239
240            displayMenu.setBackground(DemoUtility.choiceColor);
241            displayMenu.select(selectMe);
242
243            // Make all the next/previous/today buttons
244            prevYear = new Button("<<");
245            prevYear.addActionListener(this);
246            prevMonth = new Button("<");
247            prevMonth.addActionListener(this);
248            gotoToday = new Button("Today");
249            gotoToday.addActionListener(this);
250            nextMonth = new Button(">");
251            nextMonth.addActionListener(this);
252            nextYear = new Button(">>");
253            nextYear.addActionListener(this);
254
255            // The month name and the control buttons are bunched together
256            Panel monthPanel = new Panel();
257            {
258                GridBagLayout g = new GridBagLayout();
259                GridBagConstraints c = new GridBagConstraints();
260                monthPanel.setLayout(g);
261
262                c.weightx = 1;
263                c.weighty = 1;
264
265                c.gridwidth = 1;
266                c.fill = GridBagConstraints.HORIZONTAL;
267                c.gridwidth = GridBagConstraints.REMAINDER;
268                add(monthPanel, monthLabel, g, c);
269
270                c.gridwidth = 1;
271                add(monthPanel, prevYear, g, c);
272                add(monthPanel, prevMonth, g, c);
273                add(monthPanel, gotoToday, g, c);
274                add(monthPanel, nextMonth, g, c);
275                c.gridwidth = GridBagConstraints.REMAINDER;
276                add(monthPanel, nextYear, g, c);
277            }
278
279            // Stick the menu and buttons in a little "control panel"
280            Panel menuPanel = new Panel();
281            {
282                GridBagLayout g = new GridBagLayout();
283                GridBagConstraints c = new GridBagConstraints();
284                menuPanel.setLayout(g);
285
286                c.weightx = 1;
287                c.weighty = 1;
288
289                c.fill = GridBagConstraints.HORIZONTAL;
290
291                c.gridwidth = GridBagConstraints.RELATIVE;
292                Label l1 = new Label("Holidays");
293                l1.setFont(DemoUtility.labelFont);
294                add(menuPanel, l1, g, c);
295
296                c.gridwidth = GridBagConstraints.REMAINDER;
297                add(menuPanel, localeMenu, g, c);
298
299                c.gridwidth = GridBagConstraints.RELATIVE;
300                Label l2 = new Label("Display:");
301                l2.setFont(DemoUtility.labelFont);
302                add(menuPanel, l2, g, c);
303
304                c.gridwidth = GridBagConstraints.REMAINDER;
305                add(menuPanel, displayMenu, g, c);
306            }
307
308            // The title, buttons, etc. go in a panel at the top of the window
309            Panel topPanel = new Panel();
310            {
311                topPanel.setLayout(new BorderLayout());
312
313                //topPanel.add("North", titleLabel);
314                topPanel.add("Center", monthPanel);
315                topPanel.add("East", menuPanel);
316            }
317            add("North", topPanel);
318
319            // The copyright notice goes at the bottom of the window
320            Label copyright = new Label(DemoUtility.copyright1, Label.LEFT);
321            copyright.setFont(DemoUtility.creditFont);
322            add("South", copyright);
323
324            // Now create the big calendar panel and stick it in the middle
325            calendarPanel = new CalendarPanel( kFirstLocale );
326            add("Center", calendarPanel);
327
328            updateMonthName();
329        }
330
331        private void updateMonthName()
332        {
333            final Locale displayLocale = calendarPanel.getDisplayLocale();
334            final String pattern = DateTimePatternGenerator.
335                    getInstance(displayLocale).getBestPattern("MMMMy");
336            SimpleDateFormat f = new SimpleDateFormat(pattern,
337                                                        displayLocale);
338            f.setCalendar(calendarPanel.getCalendar());
339            monthLabel.setText( f.format( calendarPanel.firstOfMonth() ));
340        }
341
342        /**
343        * Handles the event. Returns true if the event is handled and should not
344        * be passed to the parent of this component. The default event handler
345        * calls some helper methods to make life easier on the programmer.
346        */
347        public void actionPerformed(ActionEvent e)
348        {
349            Object obj = e.getSource();
350
351            // *** Button events are handled here.
352            if (obj instanceof Button) {
353                if (obj == nextMonth) {
354                    calendarPanel.add(Calendar.MONTH, +1);
355                }
356                else
357                if (obj == prevMonth) {
358                    calendarPanel.add(Calendar.MONTH, -1);
359                }
360                else
361                if (obj == prevYear) {
362                    calendarPanel.add(Calendar.YEAR, -1);
363                }
364                else
365                if (obj == nextYear) {
366                    calendarPanel.add(Calendar.YEAR, +1);
367                }
368                else
369                if (obj == gotoToday) {
370                    calendarPanel.set( new Date() );
371                }
372                updateMonthName();
373            }
374        }
375
376        public void itemStateChanged(ItemEvent e)
377        {
378            Object obj = e.getSource();
379            if (obj == localeMenu) {
380                calendarPanel.setCalendarLocale(calendars[localeMenu.getSelectedIndex()]);
381                updateMonthName();
382            }
383            else
384                if (obj == displayMenu) {
385                    calendarPanel.setDisplayLocale(locales[displayMenu.getSelectedIndex()]);
386                    updateMonthName();
387                }
388        }
389
390        /**
391        * Print out the error message while debugging this program.
392        */
393        public void errorText(String s)
394        {
395            if (DEBUG)
396            {
397                System.out.println(s);
398            }
399        }
400
401        protected void processWindowEvent(WindowEvent e)
402        {
403            System.out.println("event " + e);
404            if (e.getID() == WindowEvent.WINDOW_CLOSING) {
405                this.hide();
406                this.dispose();
407
408                if (applet != null) {
409                    applet.demoClosed();
410                } else {
411                    System.exit(0);
412                }
413            }
414        }
415    }
416
417
418    private static class CalendarPanel extends Canvas {
419
420        /**
421         * For serialization
422         */
423        private static final long serialVersionUID = 1521099412250120821L;
424
425        public CalendarPanel( Locale locale ) {
426            set(locale, locale, new Date());
427        }
428
429        public void setCalendarLocale(Locale locale) {
430            set(locale, fDisplayLocale, fCalendar.getTime());
431        }
432
433        public void setDisplayLocale(Locale locale) {
434            set(fCalendarLocale, locale, fCalendar.getTime());
435        }
436
437        public void set(Date date) {
438            set(fCalendarLocale, fDisplayLocale, date);
439        }
440
441        public void set(Locale loc, Locale display, Date date)
442        {
443            if (fCalendarLocale == null || !loc.equals(fCalendarLocale)) {
444                fCalendarLocale = loc;
445                fCalendar = Calendar.getInstance(fCalendarLocale);
446                fAllHolidays = Holiday.getHolidays(fCalendarLocale);
447            }
448            if (fDisplayLocale == null || !display.equals(fDisplayLocale)) {
449                fDisplayLocale = display;
450                fSymbols = new DateFormatSymbols(fDisplayLocale);
451            }
452
453            fStartOfMonth = date;
454
455            dirty = true;
456            repaint();
457        }
458
459        public void add(int field, int delta)
460        {
461            synchronized(fCalendar) {
462                fCalendar.setTime(fStartOfMonth);
463                fCalendar.add(field, delta);
464                fStartOfMonth = fCalendar.getTime();
465            }
466            dirty = true;
467            repaint();
468        }
469
470        public com.ibm.icu.util.Calendar getCalendar() {
471            return fCalendar;
472        }
473
474        public Locale getCalendarLocale() {
475            return fCalendarLocale;
476        }
477
478        public Locale getDisplayLocale() {
479            return fDisplayLocale;
480        }
481
482
483        public Date firstOfMonth() {
484            return fStartOfMonth;
485        }
486
487        private Date startOfMonth(Date dateInMonth)
488        {
489            synchronized(fCalendar) {
490                fCalendar.setTime(dateInMonth);             // TODO: synchronization
491
492                int era = fCalendar.get(Calendar.ERA);
493                int year = fCalendar.get(Calendar.YEAR);
494                int month = fCalendar.get(Calendar.MONTH);
495
496                fCalendar.clear();
497                fCalendar.set(Calendar.ERA, era);
498                fCalendar.set(Calendar.YEAR, year);
499                fCalendar.set(Calendar.MONTH, month);
500                fCalendar.set(Calendar.DATE, 1);
501
502                return fCalendar.getTime();
503            }
504        }
505
506        private void calculate()
507        {
508            Calendar c = (Calendar)fCalendar.clone(); // Temporary copy
509
510            fStartOfMonth = startOfMonth(fStartOfMonth);
511
512            // Stash away a few useful constants for this calendar and display
513            minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
514            daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;
515
516            firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();
517
518            // Stash away a Date for the start of this month
519
520            // Find the day of week of the first day in this month
521            c.setTime(fStartOfMonth);
522            firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
523
524            // Now find the # of days in the month
525            c.roll(Calendar.DATE, false);
526            daysInMonth = c.get(Calendar.DATE);
527
528            // Finally, find the end of the month, i.e. the start of the next one
529            c.roll(Calendar.DATE, true);
530            c.add(Calendar.MONTH, 1);
531            c.getTime();        // JDK 1.1.2 bug workaround
532            c.add(Calendar.SECOND, -1);
533            Date endOfMonth = c.getTime();
534
535            //
536            // Calculate the number of full or partial weeks in this month.
537            // To do this I can just reuse the code that calculates which
538            // calendar cell contains a given date.
539            //
540            numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1;
541
542            // Remember which holidays fall on which days in this month,
543            // to save the trouble of having to do it later
544            fHolidays.setSize(0);
545
546            for (int h = 0; h < fAllHolidays.length; h++)
547            {
548                Date d = fStartOfMonth;
549                while ( (d = fAllHolidays[h].firstBetween(d, endOfMonth) ) != null)
550                {
551                    if(d.after(endOfMonth)) {
552                        throw new InternalError("Error: for " + fAllHolidays[h].getDisplayName()+
553                                "  #" + h + "/"+fAllHolidays.length+": " + d +" is after end of month " + endOfMonth);
554                    }
555                    c.setTime(d);
556                    fHolidays.addElement( new HolidayInfo(c.get(Calendar.DATE),
557                                            fAllHolidays[h],
558                                            fAllHolidays[h].getDisplayName(fDisplayLocale) ));
559
560                    d.setTime( d.getTime() + 1000 );    // "d++"
561                }
562            }
563            dirty = false;
564        }
565
566        static final int INSET = 2;
567
568        /*
569        * Convert from the day number within a month (1-based)
570        * to the cell coordinates on the calendar (0-based)
571        */
572        private void dateToCell(int date, Point pos)
573        {
574            int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
575            if (firstDayInMonth < firstDayOfWeek) {
576                cell += daysInWeek;
577            }
578
579            pos.x = cell % daysInWeek;
580            pos.y = cell / daysInWeek;
581        }
582        private Point dateToCell(int date) {
583            Point p = new Point(0,0);
584            dateToCell(date, p);
585            return p;
586        }
587
588        public void paint(Graphics g) {
589
590            if (dirty) {
591                calculate();
592            }
593
594            Point cellPos = new Point(0,0);     // Temporary variable
595            Dimension d = getSize();
596
597            g.setColor(DemoUtility.bgColor);
598            g.fillRect(0,0,d.width,d.height);
599
600            // Draw the day names at the top
601            g.setColor(Color.black);
602            g.setFont(DemoUtility.labelFont);
603            FontMetrics fm = g.getFontMetrics();
604            int labelHeight = fm.getHeight() + INSET * 2;
605
606            int v = fm.getAscent() + INSET;
607            for (int i = 0; i < daysInWeek; i++) {
608                int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1;
609                String dayName = fSymbols.getWeekdays()[dayNum];
610
611                int h = (int) (d.width * (i + 0.5)) / daysInWeek;
612                h -= fm.stringWidth(dayName) / 2;
613
614                g.drawString(dayName, h, v);
615            }
616
617            double cellHeight = (d.height - labelHeight - 1) / numWeeks;
618            double cellWidth = (double)(d.width - 1) / daysInWeek;
619
620            // Draw a white background in the part of the calendar
621            // that displays this month.
622            // First figure out how much of the first week should be shaded.
623            {
624                g.setColor(Color.white);
625                dateToCell(1, cellPos);
626                int width = (int)(cellPos.x*cellWidth);  // Width of unshaded area
627
628                g.fillRect((int)(width), labelHeight ,
629                        (int)(d.width - width), (int)cellHeight);
630
631                // All of the intermediate weeks get shaded completely
632                g.fillRect(0, (int)(labelHeight + cellHeight),
633                            d.width, (int)(cellHeight * (numWeeks - 2)));
634
635                // Now figure out the last week.
636                dateToCell(daysInMonth, cellPos);
637                width = (int)((cellPos.x+1)*cellWidth);  // Width of shaded area
638
639                g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight),
640                            width, (int)(cellHeight));
641
642            }
643            // Draw the X/Y grid lines
644            g.setColor(Color.black);
645            for (int i = 0; i <= numWeeks; i++) {
646                int y = (int)(labelHeight + i * cellHeight);
647                g.drawLine(0, y, d.width - 1, y);
648            }
649            for (int i = 0; i <= daysInWeek; i++) {
650                int x = (int)(i * cellWidth);
651                g.drawLine(x, labelHeight, x, d.height - 1);
652            }
653
654            // Now loop through all of the days in the month, figure out where
655            // they go in the grid, and draw the day # for each one
656            Font numberFont = new Font("Helvetica",Font.PLAIN,12);
657            // not used Font holidayFont = DemoUtility.creditFont;
658
659            Calendar c = (Calendar)fCalendar.clone();
660            c.setTime(fStartOfMonth);
661
662            for (int i = 1, h = 0; i <= daysInMonth; i++) {
663                g.setFont(numberFont);
664                g.setColor(Color.black);
665                fm = g.getFontMetrics();
666
667                dateToCell(i, cellPos);
668                int x = (int)((cellPos.x + 1) * cellWidth);
669                int y = (int)(cellPos.y * cellHeight + labelHeight);
670
671                StringBuffer buffer = new StringBuffer();
672                buffer.append(i);
673                String dayNum = buffer.toString();
674
675                x = x - INSET - fm.stringWidth(dayNum);
676                y = y + fm.getAscent() + INSET;
677
678                g.drawString(dayNum, x, y);
679
680                // See if any of the holidays land on this day....
681                HolidayInfo info = null;
682
683                // Coordinates of lower-left corner of cell.
684                x = (int)((cellPos.x) * cellWidth);
685                y = (int)((cellPos.y+1) * cellHeight) + labelHeight;
686
687                while (h < fHolidays.size() &&
688                        (info = (HolidayInfo)fHolidays.elementAt(h)).date <= i)
689                {
690                    if (info.date == i) {
691                        // Draw the holiday here.
692                        g.setFont(numberFont);
693                        g.setColor(Color.red);
694
695                        DemoTextBox box = new DemoTextBox(g, info.name, (int)(cellWidth - INSET));
696                        box.draw(g, x + INSET, y - INSET - box.getHeight());
697
698                        y -= (box.getHeight() + INSET);
699                    }
700                    h++;
701                }
702            }
703        }
704
705        // Important state variables
706        private Locale              fCalendarLocale;    // Whose calendar
707        private Calendar            fCalendar;          // Calendar for calculations
708
709        private Locale              fDisplayLocale;     // How to display it
710        private DateFormatSymbols   fSymbols;           // Symbols for drawing
711
712        private Date                fStartOfMonth;      // 00:00:00 on first day of month
713
714        // Cached calculations to make drawing faster.
715        private transient int minDay;           // Minimum legal day #
716        private transient int daysInWeek;       // # of days in a week
717        private transient int firstDayOfWeek;   // First day to display in week
718        private transient int numWeeks;         // # full or partial weeks in month
719        private transient int daysInMonth;      // # days in this month
720        private transient int firstDayInMonth;  // Day of week of first day in month
721
722        private transient Holiday[] fAllHolidays;
723        private transient Vector    fHolidays = new Vector(5,5);
724
725        private transient boolean dirty = true;
726    }
727
728    private static class HolidayInfo {
729        public HolidayInfo(int date, Holiday holiday, String name) {
730            this.date = date;
731            this.holiday = holiday;
732            this.name = name;
733        }
734
735        public Holiday holiday;
736        public int date;
737        public String name;
738    }
739}
740
741