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 10 package com.ibm.icu.dev.demo.holiday; 11 12 import java.awt.BorderLayout; 13 import java.awt.Button; 14 import java.awt.Canvas; 15 import java.awt.Choice; 16 import java.awt.Color; 17 import java.awt.Component; 18 import java.awt.Container; 19 import java.awt.Dimension; 20 import java.awt.Font; 21 import java.awt.FontMetrics; 22 import java.awt.Frame; 23 import java.awt.Graphics; 24 import java.awt.GridBagConstraints; 25 import java.awt.GridBagLayout; 26 import java.awt.Label; 27 import java.awt.Panel; 28 import java.awt.Point; 29 import java.awt.event.ActionEvent; 30 import java.awt.event.ActionListener; 31 import java.awt.event.ItemEvent; 32 import java.awt.event.ItemListener; 33 import java.awt.event.WindowEvent; 34 import java.text.DateFormatSymbols; 35 import java.util.Date; 36 import java.util.Locale; 37 import java.util.Vector; 38 39 import com.ibm.icu.dev.demo.impl.DemoApplet; 40 import com.ibm.icu.dev.demo.impl.DemoTextBox; 41 import com.ibm.icu.dev.demo.impl.DemoUtility; 42 import com.ibm.icu.text.DateTimePatternGenerator; 43 import com.ibm.icu.text.SimpleDateFormat; 44 import com.ibm.icu.util.Calendar; 45 import com.ibm.icu.util.Holiday; 46 47 /** 48 * CalendarDemo demonstrates how Calendar works. 49 */ 50 public 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