001: /*
002: *******************************************************************************
003: * Copyright (C) 1996-2004, International Business Machines Corporation and *
004: * others. All Rights Reserved. *
005: *******************************************************************************
006: */
007:
008: package com.ibm.icu.dev.demo.holiday;
009:
010: import java.awt.BorderLayout;
011: import java.awt.Button;
012: import java.awt.Canvas;
013: import java.awt.Choice;
014: import java.awt.Color;
015: import java.awt.Component;
016: import java.awt.Container;
017: import java.awt.Dimension;
018: import java.awt.Font;
019: import java.awt.FontMetrics;
020: import java.awt.Frame;
021: import java.awt.Graphics;
022: import java.awt.GridBagConstraints;
023: import java.awt.GridBagLayout;
024: import java.awt.Label;
025: import java.awt.Panel;
026: import java.awt.Point;
027: import java.awt.event.ActionEvent;
028: import java.awt.event.ActionListener;
029: import java.awt.event.ItemEvent;
030: import java.awt.event.ItemListener;
031: import java.awt.event.WindowEvent;
032: import java.text.DateFormatSymbols;
033: import java.util.Date;
034: import java.util.Locale;
035: import java.util.Vector;
036:
037: import com.ibm.icu.dev.demo.impl.DemoApplet;
038: import com.ibm.icu.dev.demo.impl.DemoTextBox;
039: import com.ibm.icu.dev.demo.impl.DemoUtility;
040: import com.ibm.icu.text.SimpleDateFormat;
041: import com.ibm.icu.util.Calendar;
042: import com.ibm.icu.util.Holiday;
043: import com.ibm.icu.util.SimpleTimeZone;
044:
045: /**
046: * CalendarDemo demonstrates how Calendar works.
047: */
048: public class HolidayCalendarDemo extends DemoApplet {
049: /**
050: * The main function which defines the behavior of the CalendarDemo
051: * applet when an applet is started.
052: */
053: public static void main(String argv[]) {
054:
055: new HolidayCalendarDemo().showDemo();
056: }
057:
058: /* This creates a CalendarFrame for the demo applet. */
059: public Frame createDemoFrame(DemoApplet applet) {
060: return new CalendarFrame(applet);
061: }
062:
063: /**
064: * A Frame is a top-level window with a title. The default layout for a frame
065: * is BorderLayout. The CalendarFrame class defines the window layout of
066: * CalendarDemo.
067: */
068: private static class CalendarFrame extends Frame implements
069: ActionListener, ItemListener {
070: //private static final String creditString = ""; // unused
071:
072: private static final boolean DEBUG = false;
073:
074: //private Locale curLocale = Locale.US; // unused
075:
076: private DemoApplet applet;
077:
078: private static final Locale[] calendars = {
079: //new Locale("de","AT"),
080: Locale.CANADA, Locale.CANADA_FRENCH, Locale.FRANCE,
081: Locale.GERMANY, new Locale("iw", "IL"),
082: new Locale("el", "GR"),
083: //new Locale("es","MX"),
084: Locale.UK, Locale.US, };
085: private static final Locale[] displays = { Locale.CANADA,
086: Locale.UK, Locale.US, Locale.FRANCE,
087: Locale.CANADA_FRENCH,
088: //new Locale("de","AT"),
089: Locale.GERMAN, new Locale("el", "GR"),
090: //new Locale("iw","IL"),
091: new Locale("es", "MX"), };
092:
093: /**
094: * Constructs a new CalendarFrame that is initially invisible.
095: */
096: public CalendarFrame(DemoApplet applet) {
097: super ("Calendar Demo");
098: this .applet = applet;
099: init();
100: start();
101: enableEvents(WindowEvent.WINDOW_CLOSING);
102: }
103:
104: /**
105: * Initializes the applet. You never need to call this directly, it
106: * is called automatically by the system once the applet is created.
107: */
108: public void init() {
109: // Get G7 locales only for demo purpose. To get all the locales
110: // supported, switch to calling Calendar.getAvailableLocales().
111: // commented
112: locales = displays;
113:
114: buildGUI();
115: }
116:
117: //------------------------------------------------------------
118: // package private
119: //------------------------------------------------------------
120: void addWithFont(Container container, Component foo, Font font) {
121: if (font != null)
122: foo.setFont(font);
123: container.add(foo);
124: }
125:
126: /**
127: * Called to start the applet. You never need to call this method
128: * directly, it is called when the applet's document is visited.
129: */
130: public void start() {
131: // do nothing
132: }
133:
134: private Choice localeMenu;
135: private Choice displayMenu;
136: private Locale[] locales;
137:
138: private Label monthLabel;
139: private Button prevYear;
140: private Button prevMonth;
141: private Button gotoToday;
142: private Button nextMonth;
143: private Button nextYear;
144: private CalendarPanel calendarPanel;
145:
146: private static final Locale kFirstLocale = Locale.US;
147:
148: private static void add(Container container,
149: Component component, GridBagLayout g,
150: GridBagConstraints c) {
151: g.setConstraints(component, c);
152: container.add(component);
153: }
154:
155: public void buildGUI() {
156: setBackground(DemoUtility.bgColor);
157: setLayout(new BorderLayout(10, 10));
158:
159: // Label for the demo's title
160: Label titleLabel = new Label("Calendar Demo", Label.CENTER);
161: titleLabel.setFont(DemoUtility.titleFont);
162:
163: // Label for the current month name
164: monthLabel = new Label("", Label.LEFT);
165: monthLabel.setFont(new Font(
166: DemoUtility.titleFont.getName(),
167: DemoUtility.titleFont.getStyle(),
168: (DemoUtility.titleFont.getSize() * 3) / 2));
169:
170: // Make the locale popup menus
171: localeMenu = new Choice();
172: localeMenu.addItemListener(this );
173: int selectMe = 0;
174:
175: for (int i = 0; i < calendars.length; i++) {
176: if (i > 0
177: && calendars[i].getCountry().equals(
178: calendars[i - 1].getCountry())
179: || i < calendars.length - 1
180: && calendars[i].getCountry().equals(
181: calendars[i + 1].getCountry())) {
182: localeMenu.addItem(calendars[i].getDisplayCountry()
183: + " (" + calendars[i].getDisplayLanguage()
184: + ")");
185: } else {
186: localeMenu
187: .addItem(calendars[i].getDisplayCountry());
188: }
189:
190: if (calendars[i].equals(kFirstLocale)) {
191: selectMe = i;
192: }
193: }
194:
195: localeMenu.setBackground(DemoUtility.choiceColor);
196: localeMenu.select(selectMe);
197:
198: displayMenu = new Choice();
199: displayMenu.addItemListener(this );
200:
201: selectMe = 0;
202: for (int i = 0; i < locales.length; i++) {
203: if (i > 0
204: && locales[i].getLanguage().equals(
205: locales[i - 1].getLanguage())
206: || i < locales.length - 1
207: && locales[i].getLanguage().equals(
208: locales[i + 1].getLanguage())) {
209: displayMenu.addItem(locales[i].getDisplayName());
210: } else {
211: displayMenu
212: .addItem(locales[i].getDisplayLanguage());
213: }
214:
215: if (locales[i].equals(kFirstLocale)) {
216: selectMe = i;
217: }
218: }
219:
220: displayMenu.setBackground(DemoUtility.choiceColor);
221: displayMenu.select(selectMe);
222:
223: // Make all the next/previous/today buttons
224: prevYear = new Button("<<");
225: prevYear.addActionListener(this );
226: prevMonth = new Button("<");
227: prevMonth.addActionListener(this );
228: gotoToday = new Button("Today");
229: gotoToday.addActionListener(this );
230: nextMonth = new Button(">");
231: nextMonth.addActionListener(this );
232: nextYear = new Button(">>");
233: nextYear.addActionListener(this );
234:
235: // The month name and the control buttons are bunched together
236: Panel monthPanel = new Panel();
237: {
238: GridBagLayout g = new GridBagLayout();
239: GridBagConstraints c = new GridBagConstraints();
240: monthPanel.setLayout(g);
241:
242: c.weightx = 1;
243: c.weighty = 1;
244:
245: c.gridwidth = 1;
246: c.fill = GridBagConstraints.HORIZONTAL;
247: c.gridwidth = GridBagConstraints.REMAINDER;
248: add(monthPanel, monthLabel, g, c);
249:
250: c.gridwidth = 1;
251: add(monthPanel, prevYear, g, c);
252: add(monthPanel, prevMonth, g, c);
253: add(monthPanel, gotoToday, g, c);
254: add(monthPanel, nextMonth, g, c);
255: c.gridwidth = GridBagConstraints.REMAINDER;
256: add(monthPanel, nextYear, g, c);
257: }
258:
259: // Stick the menu and buttons in a little "control panel"
260: Panel menuPanel = new Panel();
261: {
262: GridBagLayout g = new GridBagLayout();
263: GridBagConstraints c = new GridBagConstraints();
264: menuPanel.setLayout(g);
265:
266: c.weightx = 1;
267: c.weighty = 1;
268:
269: c.fill = GridBagConstraints.HORIZONTAL;
270:
271: c.gridwidth = GridBagConstraints.RELATIVE;
272: Label l1 = new Label("Holidays");
273: l1.setFont(DemoUtility.labelFont);
274: add(menuPanel, l1, g, c);
275:
276: c.gridwidth = GridBagConstraints.REMAINDER;
277: add(menuPanel, localeMenu, g, c);
278:
279: c.gridwidth = GridBagConstraints.RELATIVE;
280: Label l2 = new Label("Display:");
281: l2.setFont(DemoUtility.labelFont);
282: add(menuPanel, l2, g, c);
283:
284: c.gridwidth = GridBagConstraints.REMAINDER;
285: add(menuPanel, displayMenu, g, c);
286: }
287:
288: // The title, buttons, etc. go in a panel at the top of the window
289: Panel topPanel = new Panel();
290: {
291: topPanel.setLayout(new BorderLayout());
292:
293: //topPanel.add("North", titleLabel);
294: topPanel.add("Center", monthPanel);
295: topPanel.add("East", menuPanel);
296: }
297: add("North", topPanel);
298:
299: // The copyright notice goes at the bottom of the window
300: Label copyright = new Label(DemoUtility.copyright1,
301: Label.LEFT);
302: copyright.setFont(DemoUtility.creditFont);
303: add("South", copyright);
304:
305: // Now create the big calendar panel and stick it in the middle
306: calendarPanel = new CalendarPanel(kFirstLocale);
307: add("Center", calendarPanel);
308:
309: updateMonthName();
310: }
311:
312: private void updateMonthName() {
313: SimpleDateFormat f = new SimpleDateFormat("MMMM yyyyy",
314: calendarPanel.getDisplayLocale());
315: f.setCalendar(calendarPanel.getCalendar());
316: f.setTimeZone(new SimpleTimeZone(0, "UTC")); // JDK 1.1.2 workaround
317: monthLabel.setText(f.format(calendarPanel.firstOfMonth()));
318: }
319:
320: /**
321: * Handles the event. Returns true if the event is handled and should not
322: * be passed to the parent of this component. The default event handler
323: * calls some helper methods to make life easier on the programmer.
324: */
325: public void actionPerformed(ActionEvent e) {
326: Object obj = e.getSource();
327:
328: // *** Button events are handled here.
329: if (obj instanceof Button) {
330: if (obj == nextMonth) {
331: calendarPanel.add(Calendar.MONTH, +1);
332: } else if (obj == prevMonth) {
333: calendarPanel.add(Calendar.MONTH, -1);
334: } else if (obj == prevYear) {
335: calendarPanel.add(Calendar.YEAR, -1);
336: } else if (obj == nextYear) {
337: calendarPanel.add(Calendar.YEAR, +1);
338: } else if (obj == gotoToday) {
339: calendarPanel.set(new Date());
340: }
341: updateMonthName();
342: }
343: }
344:
345: public void itemStateChanged(ItemEvent e) {
346: Object obj = e.getSource();
347: if (obj == localeMenu) {
348: calendarPanel.setCalendarLocale(calendars[localeMenu
349: .getSelectedIndex()]);
350: updateMonthName();
351: } else if (obj == displayMenu) {
352: calendarPanel.setDisplayLocale(locales[displayMenu
353: .getSelectedIndex()]);
354: updateMonthName();
355: }
356: }
357:
358: /**
359: * Print out the error message while debugging this program.
360: */
361: public void errorText(String s) {
362: if (DEBUG) {
363: System.out.println(s);
364: }
365: }
366:
367: protected void processWindowEvent(WindowEvent e) {
368: System.out.println("event " + e);
369: if (e.getID() == WindowEvent.WINDOW_CLOSING) {
370: this .hide();
371: this .dispose();
372:
373: if (applet != null) {
374: applet.demoClosed();
375: } else {
376: System.exit(0);
377: }
378: }
379: }
380: }
381:
382: private static class CalendarPanel extends Canvas {
383:
384: public CalendarPanel(Locale locale) {
385: set(locale, locale, new Date());
386: }
387:
388: public void setCalendarLocale(Locale locale) {
389: set(locale, fDisplayLocale, fCalendar.getTime());
390: }
391:
392: public void setDisplayLocale(Locale locale) {
393: set(fCalendarLocale, locale, fCalendar.getTime());
394: }
395:
396: public void set(Date date) {
397: set(fCalendarLocale, fDisplayLocale, date);
398: }
399:
400: public void set(Locale loc, Locale display, Date date) {
401: if (fCalendarLocale == null || !loc.equals(fCalendarLocale)) {
402: fCalendarLocale = loc;
403: fCalendar = Calendar.getInstance(fCalendarLocale);
404: fAllHolidays = Holiday.getHolidays(fCalendarLocale);
405: }
406: if (fDisplayLocale == null
407: || !display.equals(fDisplayLocale)) {
408: fDisplayLocale = display;
409: fSymbols = new DateFormatSymbols(fDisplayLocale);
410: }
411:
412: fStartOfMonth = date;
413:
414: dirty = true;
415: repaint();
416: }
417:
418: public void add(int field, int delta) {
419: synchronized (fCalendar) {
420: fCalendar.setTime(fStartOfMonth);
421: fCalendar.add(field, delta);
422: fStartOfMonth = fCalendar.getTime();
423: }
424: dirty = true;
425: repaint();
426: }
427:
428: public com.ibm.icu.util.Calendar getCalendar() {
429: return fCalendar;
430: }
431:
432: public Locale getCalendarLocale() {
433: return fCalendarLocale;
434: }
435:
436: public Locale getDisplayLocale() {
437: return fDisplayLocale;
438: }
439:
440: public Date firstOfMonth() {
441: return fStartOfMonth;
442: }
443:
444: private Date startOfMonth(Date dateInMonth) {
445: synchronized (fCalendar) {
446: fCalendar.setTime(dateInMonth); // TODO: synchronization
447:
448: int era = fCalendar.get(Calendar.ERA);
449: int year = fCalendar.get(Calendar.YEAR);
450: int month = fCalendar.get(Calendar.MONTH);
451:
452: fCalendar.clear();
453: fCalendar.set(Calendar.ERA, era);
454: fCalendar.set(Calendar.YEAR, year);
455: fCalendar.set(Calendar.MONTH, month);
456: fCalendar.set(Calendar.DATE, 1);
457:
458: return fCalendar.getTime();
459: }
460: }
461:
462: private void calculate() {
463: //
464: // As a workaround for JDK 1.1.3 and below, where Calendars and time
465: // zones are a bit goofy, always set my calendar's time zone to UTC.
466: // You would think I would want to do this in the "set" function above,
467: // but if I do that, the program hangs when this class is loaded,
468: // perhaps due to some sort of static initialization ordering problem.
469: // So I do it here instead.
470: //
471: fCalendar.setTimeZone(new SimpleTimeZone(0, "UTC"));
472:
473: Calendar c = (Calendar) fCalendar.clone(); // Temporary copy
474:
475: fStartOfMonth = startOfMonth(fStartOfMonth);
476:
477: // Stash away a few useful constants for this calendar and display
478: minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
479: daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay
480: + 1;
481:
482: firstDayOfWeek = Calendar.getInstance(fDisplayLocale)
483: .getFirstDayOfWeek();
484:
485: // Stash away a Date for the start of this month
486:
487: // Find the day of week of the first day in this month
488: c.setTime(fStartOfMonth);
489: firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
490:
491: // Now find the # of days in the month
492: c.roll(Calendar.DATE, false);
493: daysInMonth = c.get(Calendar.DATE);
494:
495: // Finally, find the end of the month, i.e. the start of the next one
496: c.roll(Calendar.DATE, true);
497: c.add(Calendar.MONTH, 1);
498: c.getTime(); // JDK 1.1.2 bug workaround
499: c.add(Calendar.SECOND, -1);
500: Date endOfMonth = c.getTime();
501:
502: //
503: // Calculate the number of full or partial weeks in this month.
504: // To do this I can just reuse the code that calculates which
505: // calendar cell contains a given date.
506: //
507: numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1;
508:
509: // Remember which holidays fall on which days in this month,
510: // to save the trouble of having to do it later
511: fHolidays.setSize(0);
512:
513: for (int h = 0; h < fAllHolidays.length; h++) {
514: Date d = fStartOfMonth;
515: while ((d = fAllHolidays[h].firstBetween(d, endOfMonth)) != null) {
516: c.setTime(d);
517: fHolidays.addElement(new HolidayInfo(c
518: .get(Calendar.DATE), fAllHolidays[h],
519: fAllHolidays[h]
520: .getDisplayName(fDisplayLocale)));
521:
522: d.setTime(d.getTime() + 1000); // "d++"
523: }
524: }
525: dirty = false;
526: }
527:
528: static final int INSET = 2;
529:
530: /*
531: * Convert from the day number within a month (1-based)
532: * to the cell coordinates on the calendar (0-based)
533: */
534: private void dateToCell(int date, Point pos) {
535: int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
536: if (firstDayInMonth < firstDayOfWeek) {
537: cell += daysInWeek;
538: }
539:
540: pos.x = cell % daysInWeek;
541: pos.y = cell / daysInWeek;
542: }
543:
544: private Point dateToCell(int date) {
545: Point p = new Point(0, 0);
546: dateToCell(date, p);
547: return p;
548: }
549:
550: public void paint(Graphics g) {
551:
552: if (dirty) {
553: calculate();
554: }
555:
556: Point cellPos = new Point(0, 0); // Temporary variable
557: Dimension d = getSize();
558:
559: g.setColor(DemoUtility.bgColor);
560: g.fillRect(0, 0, d.width, d.height);
561:
562: // Draw the day names at the top
563: g.setColor(Color.black);
564: g.setFont(DemoUtility.labelFont);
565: FontMetrics fm = g.getFontMetrics();
566: int labelHeight = fm.getHeight() + INSET * 2;
567:
568: int v = fm.getAscent() + INSET;
569: for (int i = 0; i < daysInWeek; i++) {
570: int dayNum = (i + minDay + firstDayOfWeek - 2)
571: % daysInWeek + 1;
572: String dayName = fSymbols.getWeekdays()[dayNum];
573:
574: int h = (int) (d.width * (i + 0.5)) / daysInWeek;
575: h -= fm.stringWidth(dayName) / 2;
576:
577: g.drawString(dayName, h, v);
578: }
579:
580: double cellHeight = (d.height - labelHeight - 1) / numWeeks;
581: double cellWidth = (double) (d.width - 1) / daysInWeek;
582:
583: // Draw a white background in the part of the calendar
584: // that displays this month.
585: // First figure out how much of the first week should be shaded.
586: {
587: g.setColor(Color.white);
588: dateToCell(1, cellPos);
589: int width = (int) (cellPos.x * cellWidth); // Width of unshaded area
590:
591: g.fillRect((int) (width), labelHeight,
592: (int) (d.width - width), (int) cellHeight);
593:
594: // All of the intermediate weeks get shaded completely
595: g.fillRect(0, (int) (labelHeight + cellHeight),
596: d.width, (int) (cellHeight * (numWeeks - 2)));
597:
598: // Now figure out the last week.
599: dateToCell(daysInMonth, cellPos);
600: width = (int) ((cellPos.x + 1) * cellWidth); // Width of shaded area
601:
602: g.fillRect(0, (int) (labelHeight + (numWeeks - 1)
603: * cellHeight), width, (int) (cellHeight));
604:
605: }
606: // Draw the X/Y grid lines
607: g.setColor(Color.black);
608: for (int i = 0; i <= numWeeks; i++) {
609: int y = (int) (labelHeight + i * cellHeight);
610: g.drawLine(0, y, d.width - 1, y);
611: }
612: for (int i = 0; i <= daysInWeek; i++) {
613: int x = (int) (i * cellWidth);
614: g.drawLine(x, labelHeight, x, d.height - 1);
615: }
616:
617: // Now loop through all of the days in the month, figure out where
618: // they go in the grid, and draw the day # for each one
619: Font numberFont = new Font("Helvetica", Font.PLAIN, 12);
620: // not used Font holidayFont = DemoUtility.creditFont;
621:
622: Calendar c = (Calendar) fCalendar.clone();
623: c.setTime(fStartOfMonth);
624:
625: for (int i = 1, h = 0; i <= daysInMonth; i++) {
626: g.setFont(numberFont);
627: g.setColor(Color.black);
628: fm = g.getFontMetrics();
629:
630: dateToCell(i, cellPos);
631: int x = (int) ((cellPos.x + 1) * cellWidth);
632: int y = (int) (cellPos.y * cellHeight + labelHeight);
633:
634: StringBuffer buffer = new StringBuffer();
635: buffer.append(i);
636: String dayNum = buffer.toString();
637:
638: x = x - INSET - fm.stringWidth(dayNum);
639: y = y + fm.getAscent() + INSET;
640:
641: g.drawString(dayNum, x, y);
642:
643: // See if any of the holidays land on this day....
644: HolidayInfo info = null;
645: int count = 0;
646:
647: // Coordinates of lower-left corner of cell.
648: x = (int) ((cellPos.x) * cellWidth);
649: y = (int) ((cellPos.y + 1) * cellHeight) + labelHeight;
650:
651: while (h < fHolidays.size()
652: && (info = (HolidayInfo) fHolidays.elementAt(h)).date <= i) {
653: if (info.date == i) {
654: // Draw the holiday here.
655: g.setFont(numberFont);
656: g.setColor(Color.red);
657:
658: DemoTextBox box = new DemoTextBox(g, info.name,
659: (int) (cellWidth - INSET));
660: box.draw(g, x + INSET, y - INSET
661: - box.getHeight());
662:
663: y -= (box.getHeight() + INSET);
664: count++;
665: }
666: h++;
667: }
668: }
669: }
670:
671: // Important state variables
672: private Locale fCalendarLocale; // Whose calendar
673: private Calendar fCalendar; // Calendar for calculations
674:
675: private Locale fDisplayLocale; // How to display it
676: private DateFormatSymbols fSymbols; // Symbols for drawing
677:
678: private Date fStartOfMonth; // 00:00:00 on first day of month
679:
680: // Cached calculations to make drawing faster.
681: private transient int minDay; // Minimum legal day #
682: private transient int daysInWeek; // # of days in a week
683: private transient int firstDayOfWeek; // First day to display in week
684: private transient int numWeeks; // # full or partial weeks in month
685: private transient int daysInMonth; // # days in this month
686: private transient int firstDayInMonth; // Day of week of first day in month
687:
688: private transient Holiday[] fAllHolidays;
689: private transient Vector fHolidays = new Vector(5, 5);
690:
691: private transient boolean dirty = true;
692: }
693:
694: private static class HolidayInfo {
695: public HolidayInfo(int date, Holiday holiday, String name) {
696: this .date = date;
697: this .holiday = holiday;
698: this .name = name;
699: }
700:
701: public Holiday holiday;
702: public int date;
703: public String name;
704: }
705: }
|