2 *******************************************************************************
3 * Copyright (C) 1996-2007, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
8 package com.ibm.icu.dev.demo.holiday;
10 import java.awt.BorderLayout;
11 import java.awt.Button;
12 import java.awt.Canvas;
13 import java.awt.Choice;
14 import java.awt.Color;
15 import java.awt.Component;
16 import java.awt.Container;
17 import java.awt.Dimension;
19 import java.awt.FontMetrics;
20 import java.awt.Frame;
21 import java.awt.Graphics;
22 import java.awt.GridBagConstraints;
23 import java.awt.GridBagLayout;
24 import java.awt.Label;
25 import java.awt.Panel;
26 import java.awt.Point;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.ItemEvent;
30 import java.awt.event.ItemListener;
31 import java.awt.event.WindowEvent;
32 import java.text.DateFormatSymbols;
33 import java.util.Date;
34 import java.util.Locale;
35 import java.util.Vector;
37 import com.ibm.icu.dev.demo.impl.DemoApplet;
38 import com.ibm.icu.dev.demo.impl.DemoTextBox;
39 import com.ibm.icu.dev.demo.impl.DemoUtility;
40 import com.ibm.icu.text.SimpleDateFormat;
41 import com.ibm.icu.util.Calendar;
42 import com.ibm.icu.util.Holiday;
43 import com.ibm.icu.util.SimpleTimeZone;
46 * CalendarDemo demonstrates how Calendar works.
48 public class HolidayCalendarDemo extends DemoApplet
53 private static final long serialVersionUID = 4546085430817359372L;
56 * The main function which defines the behavior of the CalendarDemo
57 * applet when an applet is started.
59 public static void main(String argv[]) {
61 new HolidayCalendarDemo().showDemo();
64 /* This creates a CalendarFrame for the demo applet. */
65 public Frame createDemoFrame(DemoApplet applet) {
66 return new CalendarFrame(applet);
70 * A Frame is a top-level window with a title. The default layout for a frame
71 * is BorderLayout. The CalendarFrame class defines the window layout of
74 private static class CalendarFrame extends Frame implements ActionListener,
80 private static final long serialVersionUID = -7023296782393042761L;
82 private static final boolean DEBUG = false;
84 //private Locale curLocale = Locale.US; // unused
86 private DemoApplet applet;
88 private static final Locale[] calendars = {
89 //new Locale("de","AT"),
94 new Locale("iw","IL"),
95 new Locale("el","GR"),
96 //new Locale("es","MX"),
100 private static final Locale[] displays = {
105 Locale.CANADA_FRENCH,
106 //new Locale("de","AT"),
108 new Locale("el","GR"),
109 //new Locale("iw","IL"),
110 new Locale("es","MX"),
114 * Constructs a new CalendarFrame that is initially invisible.
116 public CalendarFrame(DemoApplet applet)
118 super("Calendar Demo");
119 this.applet = applet;
122 enableEvents(WindowEvent.WINDOW_CLOSING);
126 * Initializes the applet. You never need to call this directly, it
127 * is called automatically by the system once the applet is created.
131 // Get G7 locales only for demo purpose. To get all the locales
132 // supported, switch to calling Calendar.getAvailableLocales().
139 //------------------------------------------------------------
141 //------------------------------------------------------------
142 void addWithFont(Container container, Component foo, Font font) {
149 * Called to start the applet. You never need to call this method
150 * directly, it is called when the applet's document is visited.
157 private Choice localeMenu;
158 private Choice displayMenu;
159 private Locale[] locales;
161 private Label monthLabel;
162 private Button prevYear;
163 private Button prevMonth;
164 private Button gotoToday;
165 private Button nextMonth;
166 private Button nextYear;
167 private CalendarPanel calendarPanel;
169 private static final Locale kFirstLocale = Locale.US;
171 private static void add(Container container, Component component,
172 GridBagLayout g, GridBagConstraints c)
174 g.setConstraints(component, c);
175 container.add(component);
178 public void buildGUI()
180 setBackground(DemoUtility.bgColor);
181 setLayout(new BorderLayout(10,10));
183 // Label for the demo's title
184 Label titleLabel = new Label("Calendar Demo", Label.CENTER);
185 titleLabel.setFont(DemoUtility.titleFont);
187 // Label for the current month name
188 monthLabel = new Label("", Label.LEFT);
189 monthLabel.setFont(new Font(DemoUtility.titleFont.getName(),
190 DemoUtility.titleFont.getStyle(),
191 (DemoUtility.titleFont.getSize() * 3)/2));
193 // Make the locale popup menus
194 localeMenu= new Choice();
195 localeMenu.addItemListener(this);
198 for (int i = 0; i < calendars.length; i++) {
200 calendars[i].getCountry().equals(calendars[i-1].getCountry()) ||
201 i < calendars.length - 1 &&
202 calendars[i].getCountry().equals(calendars[i+1].getCountry()))
204 localeMenu.addItem(calendars[i].getDisplayCountry() + " (" +
205 calendars[i].getDisplayLanguage() + ")");
207 localeMenu.addItem( calendars[i].getDisplayCountry() );
210 if (calendars[i].equals(kFirstLocale)) {
215 localeMenu.setBackground(DemoUtility.choiceColor);
216 localeMenu.select(selectMe);
218 displayMenu = new Choice();
219 displayMenu.addItemListener(this);
222 for (int i = 0; i < locales.length; i++) {
224 locales[i].getLanguage().equals(locales[i-1].getLanguage()) ||
225 i < locales.length - 1 &&
226 locales[i].getLanguage().equals(locales[i+1].getLanguage()))
228 displayMenu.addItem( locales[i].getDisplayName() );
230 displayMenu.addItem( locales[i].getDisplayLanguage());
233 if (locales[i].equals(kFirstLocale)) {
238 displayMenu.setBackground(DemoUtility.choiceColor);
239 displayMenu.select(selectMe);
241 // Make all the next/previous/today buttons
242 prevYear = new Button("<<");
243 prevYear.addActionListener(this);
244 prevMonth = new Button("<");
245 prevMonth.addActionListener(this);
246 gotoToday = new Button("Today");
247 gotoToday.addActionListener(this);
248 nextMonth = new Button(">");
249 nextMonth.addActionListener(this);
250 nextYear = new Button(">>");
251 nextYear.addActionListener(this);
253 // The month name and the control buttons are bunched together
254 Panel monthPanel = new Panel();
256 GridBagLayout g = new GridBagLayout();
257 GridBagConstraints c = new GridBagConstraints();
258 monthPanel.setLayout(g);
264 c.fill = GridBagConstraints.HORIZONTAL;
265 c.gridwidth = GridBagConstraints.REMAINDER;
266 add(monthPanel, monthLabel, g, c);
269 add(monthPanel, prevYear, g, c);
270 add(monthPanel, prevMonth, g, c);
271 add(monthPanel, gotoToday, g, c);
272 add(monthPanel, nextMonth, g, c);
273 c.gridwidth = GridBagConstraints.REMAINDER;
274 add(monthPanel, nextYear, g, c);
277 // Stick the menu and buttons in a little "control panel"
278 Panel menuPanel = new Panel();
280 GridBagLayout g = new GridBagLayout();
281 GridBagConstraints c = new GridBagConstraints();
282 menuPanel.setLayout(g);
287 c.fill = GridBagConstraints.HORIZONTAL;
289 c.gridwidth = GridBagConstraints.RELATIVE;
290 Label l1 = new Label("Holidays");
291 l1.setFont(DemoUtility.labelFont);
292 add(menuPanel, l1, g, c);
294 c.gridwidth = GridBagConstraints.REMAINDER;
295 add(menuPanel, localeMenu, g, c);
297 c.gridwidth = GridBagConstraints.RELATIVE;
298 Label l2 = new Label("Display:");
299 l2.setFont(DemoUtility.labelFont);
300 add(menuPanel, l2, g, c);
302 c.gridwidth = GridBagConstraints.REMAINDER;
303 add(menuPanel, displayMenu, g, c);
306 // The title, buttons, etc. go in a panel at the top of the window
307 Panel topPanel = new Panel();
309 topPanel.setLayout(new BorderLayout());
311 //topPanel.add("North", titleLabel);
312 topPanel.add("Center", monthPanel);
313 topPanel.add("East", menuPanel);
315 add("North", topPanel);
317 // The copyright notice goes at the bottom of the window
318 Label copyright = new Label(DemoUtility.copyright1, Label.LEFT);
319 copyright.setFont(DemoUtility.creditFont);
320 add("South", copyright);
322 // Now create the big calendar panel and stick it in the middle
323 calendarPanel = new CalendarPanel( kFirstLocale );
324 add("Center", calendarPanel);
329 private void updateMonthName()
331 SimpleDateFormat f = new SimpleDateFormat("MMMM yyyyy",
332 calendarPanel.getDisplayLocale());
333 f.setCalendar(calendarPanel.getCalendar());
334 f.setTimeZone(new SimpleTimeZone(0, "UTC")); // JDK 1.1.2 workaround
335 monthLabel.setText( f.format( calendarPanel.firstOfMonth() ));
339 * Handles the event. Returns true if the event is handled and should not
340 * be passed to the parent of this component. The default event handler
341 * calls some helper methods to make life easier on the programmer.
343 public void actionPerformed(ActionEvent e)
345 Object obj = e.getSource();
347 // *** Button events are handled here.
348 if (obj instanceof Button) {
349 if (obj == nextMonth) {
350 calendarPanel.add(Calendar.MONTH, +1);
353 if (obj == prevMonth) {
354 calendarPanel.add(Calendar.MONTH, -1);
357 if (obj == prevYear) {
358 calendarPanel.add(Calendar.YEAR, -1);
361 if (obj == nextYear) {
362 calendarPanel.add(Calendar.YEAR, +1);
365 if (obj == gotoToday) {
366 calendarPanel.set( new Date() );
372 public void itemStateChanged(ItemEvent e)
374 Object obj = e.getSource();
375 if (obj == localeMenu) {
376 calendarPanel.setCalendarLocale(calendars[localeMenu.getSelectedIndex()]);
380 if (obj == displayMenu) {
381 calendarPanel.setDisplayLocale(locales[displayMenu.getSelectedIndex()]);
387 * Print out the error message while debugging this program.
389 public void errorText(String s)
393 System.out.println(s);
397 protected void processWindowEvent(WindowEvent e)
399 System.out.println("event " + e);
400 if (e.getID() == WindowEvent.WINDOW_CLOSING) {
404 if (applet != null) {
414 private static class CalendarPanel extends Canvas {
419 private static final long serialVersionUID = 1521099412250120821L;
421 public CalendarPanel( Locale locale ) {
422 set(locale, locale, new Date());
425 public void setCalendarLocale(Locale locale) {
426 set(locale, fDisplayLocale, fCalendar.getTime());
429 public void setDisplayLocale(Locale locale) {
430 set(fCalendarLocale, locale, fCalendar.getTime());
433 public void set(Date date) {
434 set(fCalendarLocale, fDisplayLocale, date);
437 public void set(Locale loc, Locale display, Date date)
439 if (fCalendarLocale == null || !loc.equals(fCalendarLocale)) {
440 fCalendarLocale = loc;
441 fCalendar = Calendar.getInstance(fCalendarLocale);
442 fAllHolidays = Holiday.getHolidays(fCalendarLocale);
444 if (fDisplayLocale == null || !display.equals(fDisplayLocale)) {
445 fDisplayLocale = display;
446 fSymbols = new DateFormatSymbols(fDisplayLocale);
449 fStartOfMonth = date;
455 public void add(int field, int delta)
457 synchronized(fCalendar) {
458 fCalendar.setTime(fStartOfMonth);
459 fCalendar.add(field, delta);
460 fStartOfMonth = fCalendar.getTime();
466 public com.ibm.icu.util.Calendar getCalendar() {
470 public Locale getCalendarLocale() {
471 return fCalendarLocale;
474 public Locale getDisplayLocale() {
475 return fDisplayLocale;
479 public Date firstOfMonth() {
480 return fStartOfMonth;
483 private Date startOfMonth(Date dateInMonth)
485 synchronized(fCalendar) {
486 fCalendar.setTime(dateInMonth); // TODO: synchronization
488 int era = fCalendar.get(Calendar.ERA);
489 int year = fCalendar.get(Calendar.YEAR);
490 int month = fCalendar.get(Calendar.MONTH);
493 fCalendar.set(Calendar.ERA, era);
494 fCalendar.set(Calendar.YEAR, year);
495 fCalendar.set(Calendar.MONTH, month);
496 fCalendar.set(Calendar.DATE, 1);
498 return fCalendar.getTime();
502 private void calculate()
505 // As a workaround for JDK 1.1.3 and below, where Calendars and time
506 // zones are a bit goofy, always set my calendar's time zone to UTC.
507 // You would think I would want to do this in the "set" function above,
508 // but if I do that, the program hangs when this class is loaded,
509 // perhaps due to some sort of static initialization ordering problem.
510 // So I do it here instead.
512 fCalendar.setTimeZone(new SimpleTimeZone(0, "UTC"));
514 Calendar c = (Calendar)fCalendar.clone(); // Temporary copy
516 fStartOfMonth = startOfMonth(fStartOfMonth);
518 // Stash away a few useful constants for this calendar and display
519 minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
520 daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;
522 firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();
524 // Stash away a Date for the start of this month
526 // Find the day of week of the first day in this month
527 c.setTime(fStartOfMonth);
528 firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
530 // Now find the # of days in the month
531 c.roll(Calendar.DATE, false);
532 daysInMonth = c.get(Calendar.DATE);
534 // Finally, find the end of the month, i.e. the start of the next one
535 c.roll(Calendar.DATE, true);
536 c.add(Calendar.MONTH, 1);
537 c.getTime(); // JDK 1.1.2 bug workaround
538 c.add(Calendar.SECOND, -1);
539 Date endOfMonth = c.getTime();
542 // Calculate the number of full or partial weeks in this month.
543 // To do this I can just reuse the code that calculates which
544 // calendar cell contains a given date.
546 numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1;
548 // Remember which holidays fall on which days in this month,
549 // to save the trouble of having to do it later
550 fHolidays.setSize(0);
552 for (int h = 0; h < fAllHolidays.length; h++)
554 Date d = fStartOfMonth;
555 while ( (d = fAllHolidays[h].firstBetween(d, endOfMonth) ) != null)
558 fHolidays.addElement( new HolidayInfo(c.get(Calendar.DATE),
560 fAllHolidays[h].getDisplayName(fDisplayLocale) ));
562 d.setTime( d.getTime() + 1000 ); // "d++"
568 static final int INSET = 2;
571 * Convert from the day number within a month (1-based)
572 * to the cell coordinates on the calendar (0-based)
574 private void dateToCell(int date, Point pos)
576 int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
577 if (firstDayInMonth < firstDayOfWeek) {
581 pos.x = cell % daysInWeek;
582 pos.y = cell / daysInWeek;
584 private Point dateToCell(int date) {
585 Point p = new Point(0,0);
590 public void paint(Graphics g) {
596 Point cellPos = new Point(0,0); // Temporary variable
597 Dimension d = getSize();
599 g.setColor(DemoUtility.bgColor);
600 g.fillRect(0,0,d.width,d.height);
602 // Draw the day names at the top
603 g.setColor(Color.black);
604 g.setFont(DemoUtility.labelFont);
605 FontMetrics fm = g.getFontMetrics();
606 int labelHeight = fm.getHeight() + INSET * 2;
608 int v = fm.getAscent() + INSET;
609 for (int i = 0; i < daysInWeek; i++) {
610 int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1;
611 String dayName = fSymbols.getWeekdays()[dayNum];
613 int h = (int) (d.width * (i + 0.5)) / daysInWeek;
614 h -= fm.stringWidth(dayName) / 2;
616 g.drawString(dayName, h, v);
619 double cellHeight = (d.height - labelHeight - 1) / numWeeks;
620 double cellWidth = (double)(d.width - 1) / daysInWeek;
622 // Draw a white background in the part of the calendar
623 // that displays this month.
624 // First figure out how much of the first week should be shaded.
626 g.setColor(Color.white);
627 dateToCell(1, cellPos);
628 int width = (int)(cellPos.x*cellWidth); // Width of unshaded area
630 g.fillRect((int)(width), labelHeight ,
631 (int)(d.width - width), (int)cellHeight);
633 // All of the intermediate weeks get shaded completely
634 g.fillRect(0, (int)(labelHeight + cellHeight),
635 d.width, (int)(cellHeight * (numWeeks - 2)));
637 // Now figure out the last week.
638 dateToCell(daysInMonth, cellPos);
639 width = (int)((cellPos.x+1)*cellWidth); // Width of shaded area
641 g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight),
642 width, (int)(cellHeight));
645 // Draw the X/Y grid lines
646 g.setColor(Color.black);
647 for (int i = 0; i <= numWeeks; i++) {
648 int y = (int)(labelHeight + i * cellHeight);
649 g.drawLine(0, y, d.width - 1, y);
651 for (int i = 0; i <= daysInWeek; i++) {
652 int x = (int)(i * cellWidth);
653 g.drawLine(x, labelHeight, x, d.height - 1);
656 // Now loop through all of the days in the month, figure out where
657 // they go in the grid, and draw the day # for each one
658 Font numberFont = new Font("Helvetica",Font.PLAIN,12);
659 // not used Font holidayFont = DemoUtility.creditFont;
661 Calendar c = (Calendar)fCalendar.clone();
662 c.setTime(fStartOfMonth);
664 for (int i = 1, h = 0; i <= daysInMonth; i++) {
665 g.setFont(numberFont);
666 g.setColor(Color.black);
667 fm = g.getFontMetrics();
669 dateToCell(i, cellPos);
670 int x = (int)((cellPos.x + 1) * cellWidth);
671 int y = (int)(cellPos.y * cellHeight + labelHeight);
673 StringBuffer buffer = new StringBuffer();
675 String dayNum = buffer.toString();
677 x = x - INSET - fm.stringWidth(dayNum);
678 y = y + fm.getAscent() + INSET;
680 g.drawString(dayNum, x, y);
682 // See if any of the holidays land on this day....
683 HolidayInfo info = null;
686 // Coordinates of lower-left corner of cell.
687 x = (int)((cellPos.x) * cellWidth);
688 y = (int)((cellPos.y+1) * cellHeight) + labelHeight;
690 while (h < fHolidays.size() &&
691 (info = (HolidayInfo)fHolidays.elementAt(h)).date <= i)
693 if (info.date == i) {
694 // Draw the holiday here.
695 g.setFont(numberFont);
696 g.setColor(Color.red);
698 DemoTextBox box = new DemoTextBox(g, info.name, (int)(cellWidth - INSET));
699 box.draw(g, x + INSET, y - INSET - box.getHeight());
701 y -= (box.getHeight() + INSET);
709 // Important state variables
710 private Locale fCalendarLocale; // Whose calendar
711 private Calendar fCalendar; // Calendar for calculations
713 private Locale fDisplayLocale; // How to display it
714 private DateFormatSymbols fSymbols; // Symbols for drawing
716 private Date fStartOfMonth; // 00:00:00 on first day of month
718 // Cached calculations to make drawing faster.
719 private transient int minDay; // Minimum legal day #
720 private transient int daysInWeek; // # of days in a week
721 private transient int firstDayOfWeek; // First day to display in week
722 private transient int numWeeks; // # full or partial weeks in month
723 private transient int daysInMonth; // # days in this month
724 private transient int firstDayInMonth; // Day of week of first day in month
726 private transient Holiday[] fAllHolidays;
727 private transient Vector fHolidays = new Vector(5,5);
729 private transient boolean dirty = true;
732 private static class HolidayInfo {
733 public HolidayInfo(int date, Holiday holiday, String name) {
735 this.holiday = holiday;
739 public Holiday holiday;