/* ******************************************************************************* * Copyright (C) 1996-2007, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.dev.demo.holiday; import java.awt.BorderLayout; import java.awt.Button; import java.awt.Canvas; import java.awt.Choice; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Label; import java.awt.Panel; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.WindowEvent; import java.text.DateFormatSymbols; import java.util.Date; import java.util.Locale; import java.util.Vector; import com.ibm.icu.dev.demo.impl.DemoApplet; import com.ibm.icu.dev.demo.impl.DemoTextBox; import com.ibm.icu.dev.demo.impl.DemoUtility; import com.ibm.icu.text.SimpleDateFormat; import com.ibm.icu.util.Calendar; import com.ibm.icu.util.Holiday; import com.ibm.icu.util.SimpleTimeZone; /** * CalendarDemo demonstrates how Calendar works. */ public class HolidayCalendarDemo extends DemoApplet { /** * For serialization */ private static final long serialVersionUID = 4546085430817359372L; /** * The main function which defines the behavior of the CalendarDemo * applet when an applet is started. */ public static void main(String argv[]) { new HolidayCalendarDemo().showDemo(); } /* This creates a CalendarFrame for the demo applet. */ public Frame createDemoFrame(DemoApplet applet) { return new CalendarFrame(applet); } /** * A Frame is a top-level window with a title. The default layout for a frame * is BorderLayout. The CalendarFrame class defines the window layout of * CalendarDemo. */ private static class CalendarFrame extends Frame implements ActionListener, ItemListener { /** * For serialization */ private static final long serialVersionUID = -7023296782393042761L; private static final boolean DEBUG = false; //private Locale curLocale = Locale.US; // unused private DemoApplet applet; private static final Locale[] calendars = { //new Locale("de","AT"), Locale.CANADA, Locale.CANADA_FRENCH, Locale.FRANCE, Locale.GERMANY, new Locale("iw","IL"), new Locale("el","GR"), //new Locale("es","MX"), Locale.UK, Locale.US, }; private static final Locale[] displays = { Locale.CANADA, Locale.UK, Locale.US, Locale.FRANCE, Locale.CANADA_FRENCH, //new Locale("de","AT"), Locale.GERMAN, new Locale("el","GR"), //new Locale("iw","IL"), new Locale("es","MX"), }; /** * Constructs a new CalendarFrame that is initially invisible. */ public CalendarFrame(DemoApplet applet) { super("Calendar Demo"); this.applet = applet; init(); start(); enableEvents(WindowEvent.WINDOW_CLOSING); } /** * Initializes the applet. You never need to call this directly, it * is called automatically by the system once the applet is created. */ public void init() { // Get G7 locales only for demo purpose. To get all the locales // supported, switch to calling Calendar.getAvailableLocales(). // commented locales = displays; buildGUI(); } //------------------------------------------------------------ // package private //------------------------------------------------------------ void addWithFont(Container container, Component foo, Font font) { if (font != null) foo.setFont(font); container.add(foo); } /** * Called to start the applet. You never need to call this method * directly, it is called when the applet's document is visited. */ public void start() { // do nothing } private Choice localeMenu; private Choice displayMenu; private Locale[] locales; private Label monthLabel; private Button prevYear; private Button prevMonth; private Button gotoToday; private Button nextMonth; private Button nextYear; private CalendarPanel calendarPanel; private static final Locale kFirstLocale = Locale.US; private static void add(Container container, Component component, GridBagLayout g, GridBagConstraints c) { g.setConstraints(component, c); container.add(component); } public void buildGUI() { setBackground(DemoUtility.bgColor); setLayout(new BorderLayout(10,10)); // Label for the demo's title Label titleLabel = new Label("Calendar Demo", Label.CENTER); titleLabel.setFont(DemoUtility.titleFont); // Label for the current month name monthLabel = new Label("", Label.LEFT); monthLabel.setFont(new Font(DemoUtility.titleFont.getName(), DemoUtility.titleFont.getStyle(), (DemoUtility.titleFont.getSize() * 3)/2)); // Make the locale popup menus localeMenu= new Choice(); localeMenu.addItemListener(this); int selectMe = 0; for (int i = 0; i < calendars.length; i++) { if (i > 0 && calendars[i].getCountry().equals(calendars[i-1].getCountry()) || i < calendars.length - 1 && calendars[i].getCountry().equals(calendars[i+1].getCountry())) { localeMenu.addItem(calendars[i].getDisplayCountry() + " (" + calendars[i].getDisplayLanguage() + ")"); } else { localeMenu.addItem( calendars[i].getDisplayCountry() ); } if (calendars[i].equals(kFirstLocale)) { selectMe = i; } } localeMenu.setBackground(DemoUtility.choiceColor); localeMenu.select(selectMe); displayMenu = new Choice(); displayMenu.addItemListener(this); selectMe = 0; for (int i = 0; i < locales.length; i++) { if (i > 0 && locales[i].getLanguage().equals(locales[i-1].getLanguage()) || i < locales.length - 1 && locales[i].getLanguage().equals(locales[i+1].getLanguage())) { displayMenu.addItem( locales[i].getDisplayName() ); } else { displayMenu.addItem( locales[i].getDisplayLanguage()); } if (locales[i].equals(kFirstLocale)) { selectMe = i; } } displayMenu.setBackground(DemoUtility.choiceColor); displayMenu.select(selectMe); // Make all the next/previous/today buttons prevYear = new Button("<<"); prevYear.addActionListener(this); prevMonth = new Button("<"); prevMonth.addActionListener(this); gotoToday = new Button("Today"); gotoToday.addActionListener(this); nextMonth = new Button(">"); nextMonth.addActionListener(this); nextYear = new Button(">>"); nextYear.addActionListener(this); // The month name and the control buttons are bunched together Panel monthPanel = new Panel(); { GridBagLayout g = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); monthPanel.setLayout(g); c.weightx = 1; c.weighty = 1; c.gridwidth = 1; c.fill = GridBagConstraints.HORIZONTAL; c.gridwidth = GridBagConstraints.REMAINDER; add(monthPanel, monthLabel, g, c); c.gridwidth = 1; add(monthPanel, prevYear, g, c); add(monthPanel, prevMonth, g, c); add(monthPanel, gotoToday, g, c); add(monthPanel, nextMonth, g, c); c.gridwidth = GridBagConstraints.REMAINDER; add(monthPanel, nextYear, g, c); } // Stick the menu and buttons in a little "control panel" Panel menuPanel = new Panel(); { GridBagLayout g = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); menuPanel.setLayout(g); c.weightx = 1; c.weighty = 1; c.fill = GridBagConstraints.HORIZONTAL; c.gridwidth = GridBagConstraints.RELATIVE; Label l1 = new Label("Holidays"); l1.setFont(DemoUtility.labelFont); add(menuPanel, l1, g, c); c.gridwidth = GridBagConstraints.REMAINDER; add(menuPanel, localeMenu, g, c); c.gridwidth = GridBagConstraints.RELATIVE; Label l2 = new Label("Display:"); l2.setFont(DemoUtility.labelFont); add(menuPanel, l2, g, c); c.gridwidth = GridBagConstraints.REMAINDER; add(menuPanel, displayMenu, g, c); } // The title, buttons, etc. go in a panel at the top of the window Panel topPanel = new Panel(); { topPanel.setLayout(new BorderLayout()); //topPanel.add("North", titleLabel); topPanel.add("Center", monthPanel); topPanel.add("East", menuPanel); } add("North", topPanel); // The copyright notice goes at the bottom of the window Label copyright = new Label(DemoUtility.copyright1, Label.LEFT); copyright.setFont(DemoUtility.creditFont); add("South", copyright); // Now create the big calendar panel and stick it in the middle calendarPanel = new CalendarPanel( kFirstLocale ); add("Center", calendarPanel); updateMonthName(); } private void updateMonthName() { SimpleDateFormat f = new SimpleDateFormat("MMMM yyyyy", calendarPanel.getDisplayLocale()); f.setCalendar(calendarPanel.getCalendar()); f.setTimeZone(new SimpleTimeZone(0, "UTC")); // JDK 1.1.2 workaround monthLabel.setText( f.format( calendarPanel.firstOfMonth() )); } /** * Handles the event. Returns true if the event is handled and should not * be passed to the parent of this component. The default event handler * calls some helper methods to make life easier on the programmer. */ public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); // *** Button events are handled here. if (obj instanceof Button) { if (obj == nextMonth) { calendarPanel.add(Calendar.MONTH, +1); } else if (obj == prevMonth) { calendarPanel.add(Calendar.MONTH, -1); } else if (obj == prevYear) { calendarPanel.add(Calendar.YEAR, -1); } else if (obj == nextYear) { calendarPanel.add(Calendar.YEAR, +1); } else if (obj == gotoToday) { calendarPanel.set( new Date() ); } updateMonthName(); } } public void itemStateChanged(ItemEvent e) { Object obj = e.getSource(); if (obj == localeMenu) { calendarPanel.setCalendarLocale(calendars[localeMenu.getSelectedIndex()]); updateMonthName(); } else if (obj == displayMenu) { calendarPanel.setDisplayLocale(locales[displayMenu.getSelectedIndex()]); updateMonthName(); } } /** * Print out the error message while debugging this program. */ public void errorText(String s) { if (DEBUG) { System.out.println(s); } } protected void processWindowEvent(WindowEvent e) { System.out.println("event " + e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { this.hide(); this.dispose(); if (applet != null) { applet.demoClosed(); } else { System.exit(0); } } } } private static class CalendarPanel extends Canvas { /** * For serialization */ private static final long serialVersionUID = 1521099412250120821L; public CalendarPanel( Locale locale ) { set(locale, locale, new Date()); } public void setCalendarLocale(Locale locale) { set(locale, fDisplayLocale, fCalendar.getTime()); } public void setDisplayLocale(Locale locale) { set(fCalendarLocale, locale, fCalendar.getTime()); } public void set(Date date) { set(fCalendarLocale, fDisplayLocale, date); } public void set(Locale loc, Locale display, Date date) { if (fCalendarLocale == null || !loc.equals(fCalendarLocale)) { fCalendarLocale = loc; fCalendar = Calendar.getInstance(fCalendarLocale); fAllHolidays = Holiday.getHolidays(fCalendarLocale); } if (fDisplayLocale == null || !display.equals(fDisplayLocale)) { fDisplayLocale = display; fSymbols = new DateFormatSymbols(fDisplayLocale); } fStartOfMonth = date; dirty = true; repaint(); } public void add(int field, int delta) { synchronized(fCalendar) { fCalendar.setTime(fStartOfMonth); fCalendar.add(field, delta); fStartOfMonth = fCalendar.getTime(); } dirty = true; repaint(); } public com.ibm.icu.util.Calendar getCalendar() { return fCalendar; } public Locale getCalendarLocale() { return fCalendarLocale; } public Locale getDisplayLocale() { return fDisplayLocale; } public Date firstOfMonth() { return fStartOfMonth; } private Date startOfMonth(Date dateInMonth) { synchronized(fCalendar) { fCalendar.setTime(dateInMonth); // TODO: synchronization int era = fCalendar.get(Calendar.ERA); int year = fCalendar.get(Calendar.YEAR); int month = fCalendar.get(Calendar.MONTH); fCalendar.clear(); fCalendar.set(Calendar.ERA, era); fCalendar.set(Calendar.YEAR, year); fCalendar.set(Calendar.MONTH, month); fCalendar.set(Calendar.DATE, 1); return fCalendar.getTime(); } } private void calculate() { // // As a workaround for JDK 1.1.3 and below, where Calendars and time // zones are a bit goofy, always set my calendar's time zone to UTC. // You would think I would want to do this in the "set" function above, // but if I do that, the program hangs when this class is loaded, // perhaps due to some sort of static initialization ordering problem. // So I do it here instead. // fCalendar.setTimeZone(new SimpleTimeZone(0, "UTC")); Calendar c = (Calendar)fCalendar.clone(); // Temporary copy fStartOfMonth = startOfMonth(fStartOfMonth); // Stash away a few useful constants for this calendar and display minDay = c.getMinimum(Calendar.DAY_OF_WEEK); daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1; firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek(); // Stash away a Date for the start of this month // Find the day of week of the first day in this month c.setTime(fStartOfMonth); firstDayInMonth = c.get(Calendar.DAY_OF_WEEK); // Now find the # of days in the month c.roll(Calendar.DATE, false); daysInMonth = c.get(Calendar.DATE); // Finally, find the end of the month, i.e. the start of the next one c.roll(Calendar.DATE, true); c.add(Calendar.MONTH, 1); c.getTime(); // JDK 1.1.2 bug workaround c.add(Calendar.SECOND, -1); Date endOfMonth = c.getTime(); // // Calculate the number of full or partial weeks in this month. // To do this I can just reuse the code that calculates which // calendar cell contains a given date. // numWeeks = dateToCell(daysInMonth).y - dateToCell(1).y + 1; // Remember which holidays fall on which days in this month, // to save the trouble of having to do it later fHolidays.setSize(0); for (int h = 0; h < fAllHolidays.length; h++) { Date d = fStartOfMonth; while ( (d = fAllHolidays[h].firstBetween(d, endOfMonth) ) != null) { c.setTime(d); fHolidays.addElement( new HolidayInfo(c.get(Calendar.DATE), fAllHolidays[h], fAllHolidays[h].getDisplayName(fDisplayLocale) )); d.setTime( d.getTime() + 1000 ); // "d++" } } dirty = false; } static final int INSET = 2; /* * Convert from the day number within a month (1-based) * to the cell coordinates on the calendar (0-based) */ private void dateToCell(int date, Point pos) { int cell = (date + firstDayInMonth - firstDayOfWeek - minDay); if (firstDayInMonth < firstDayOfWeek) { cell += daysInWeek; } pos.x = cell % daysInWeek; pos.y = cell / daysInWeek; } private Point dateToCell(int date) { Point p = new Point(0,0); dateToCell(date, p); return p; } public void paint(Graphics g) { if (dirty) { calculate(); } Point cellPos = new Point(0,0); // Temporary variable Dimension d = getSize(); g.setColor(DemoUtility.bgColor); g.fillRect(0,0,d.width,d.height); // Draw the day names at the top g.setColor(Color.black); g.setFont(DemoUtility.labelFont); FontMetrics fm = g.getFontMetrics(); int labelHeight = fm.getHeight() + INSET * 2; int v = fm.getAscent() + INSET; for (int i = 0; i < daysInWeek; i++) { int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1; String dayName = fSymbols.getWeekdays()[dayNum]; int h = (int) (d.width * (i + 0.5)) / daysInWeek; h -= fm.stringWidth(dayName) / 2; g.drawString(dayName, h, v); } double cellHeight = (d.height - labelHeight - 1) / numWeeks; double cellWidth = (double)(d.width - 1) / daysInWeek; // Draw a white background in the part of the calendar // that displays this month. // First figure out how much of the first week should be shaded. { g.setColor(Color.white); dateToCell(1, cellPos); int width = (int)(cellPos.x*cellWidth); // Width of unshaded area g.fillRect((int)(width), labelHeight , (int)(d.width - width), (int)cellHeight); // All of the intermediate weeks get shaded completely g.fillRect(0, (int)(labelHeight + cellHeight), d.width, (int)(cellHeight * (numWeeks - 2))); // Now figure out the last week. dateToCell(daysInMonth, cellPos); width = (int)((cellPos.x+1)*cellWidth); // Width of shaded area g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight), width, (int)(cellHeight)); } // Draw the X/Y grid lines g.setColor(Color.black); for (int i = 0; i <= numWeeks; i++) { int y = (int)(labelHeight + i * cellHeight); g.drawLine(0, y, d.width - 1, y); } for (int i = 0; i <= daysInWeek; i++) { int x = (int)(i * cellWidth); g.drawLine(x, labelHeight, x, d.height - 1); } // Now loop through all of the days in the month, figure out where // they go in the grid, and draw the day # for each one Font numberFont = new Font("Helvetica",Font.PLAIN,12); // not used Font holidayFont = DemoUtility.creditFont; Calendar c = (Calendar)fCalendar.clone(); c.setTime(fStartOfMonth); for (int i = 1, h = 0; i <= daysInMonth; i++) { g.setFont(numberFont); g.setColor(Color.black); fm = g.getFontMetrics(); dateToCell(i, cellPos); int x = (int)((cellPos.x + 1) * cellWidth); int y = (int)(cellPos.y * cellHeight + labelHeight); StringBuffer buffer = new StringBuffer(); buffer.append(i); String dayNum = buffer.toString(); x = x - INSET - fm.stringWidth(dayNum); y = y + fm.getAscent() + INSET; g.drawString(dayNum, x, y); // See if any of the holidays land on this day.... HolidayInfo info = null; int count = 0; // Coordinates of lower-left corner of cell. x = (int)((cellPos.x) * cellWidth); y = (int)((cellPos.y+1) * cellHeight) + labelHeight; while (h < fHolidays.size() && (info = (HolidayInfo)fHolidays.elementAt(h)).date <= i) { if (info.date == i) { // Draw the holiday here. g.setFont(numberFont); g.setColor(Color.red); DemoTextBox box = new DemoTextBox(g, info.name, (int)(cellWidth - INSET)); box.draw(g, x + INSET, y - INSET - box.getHeight()); y -= (box.getHeight() + INSET); count++; } h++; } } } // Important state variables private Locale fCalendarLocale; // Whose calendar private Calendar fCalendar; // Calendar for calculations private Locale fDisplayLocale; // How to display it private DateFormatSymbols fSymbols; // Symbols for drawing private Date fStartOfMonth; // 00:00:00 on first day of month // Cached calculations to make drawing faster. private transient int minDay; // Minimum legal day # private transient int daysInWeek; // # of days in a week private transient int firstDayOfWeek; // First day to display in week private transient int numWeeks; // # full or partial weeks in month private transient int daysInMonth; // # days in this month private transient int firstDayInMonth; // Day of week of first day in month private transient Holiday[] fAllHolidays; private transient Vector fHolidays = new Vector(5,5); private transient boolean dirty = true; } private static class HolidayInfo { public HolidayInfo(int date, Holiday holiday, String name) { this.date = date; this.holiday = holiday; this.name = name; } public Holiday holiday; public int date; public String name; } }