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