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) 1997-2007, International Business Machines Corporation and    *
6 * others. All Rights Reserved.                                                *
7 *******************************************************************************
8 */
9
10package com.ibm.icu.dev.demo.calendar;
11
12import java.awt.Canvas;
13import java.awt.Color;
14import java.awt.Dimension;
15import java.awt.FontMetrics;
16import java.awt.Graphics;
17import java.awt.Point;
18import java.util.Date;
19import java.util.Locale;
20
21import com.ibm.icu.dev.demo.impl.DemoUtility;
22import com.ibm.icu.text.DateFormatSymbols;
23import com.ibm.icu.util.Calendar;
24import com.ibm.icu.util.SimpleTimeZone;
25
26class CalendarPanel extends Canvas {
27
28    /**
29     * For serialization
30     */
31    private static final long serialVersionUID = 625400018027387141L;
32
33    public CalendarPanel( Locale locale ) {
34        setLocale(locale);
35    }
36
37    public void setLocale(Locale locale) {
38        if (fDisplayLocale == null || !fDisplayLocale.equals(locale)) {
39            fDisplayLocale = locale;
40            dirty = true;
41
42            for (int i = 0; i < fCalendar.length; i++) {
43                if (fCalendar[i] != null) {
44                    fSymbols[i] = new DateFormatSymbols(fCalendar[i],
45                                                        fDisplayLocale);
46                }
47            }
48            String lang = locale.getLanguage();
49            leftToRight = !(lang.equals("iw") || lang.equals("ar"));
50
51            repaint();
52        }
53    }
54
55    public void setDate(Date date) {
56        fStartOfMonth = date;
57        dirty = true;
58        repaint();
59    }
60
61    public void add(int field, int delta)
62    {
63        synchronized(fCalendar) {
64            fCalendar[0].setTime(fStartOfMonth);
65            fCalendar[0].add(field, delta);
66            fStartOfMonth = fCalendar[0].getTime();
67        }
68        dirty = true;
69        repaint();
70    }
71
72    public void setColor(int index, Color c) {
73        fColor[index] = c;
74        repaint();
75    }
76
77    public void setCalendar(int index, Calendar c) {
78        Date date = (fCalendar[index] == null) ? new Date()
79                                               : fCalendar[index].getTime();
80
81        fCalendar[index] = c;
82        fCalendar[index].setTime(date);
83
84        fSymbols[index] = new DateFormatSymbols(c, fDisplayLocale);
85        dirty = true;
86        repaint();
87    }
88
89    public Calendar getCalendar(int index) {
90        return fCalendar[index];
91    }
92
93    public Locale getDisplayLocale() {
94        return fDisplayLocale;
95    }
96
97    public Date firstOfMonth() {
98        return fStartOfMonth;
99    }
100
101    private Date startOfMonth(Date dateInMonth)
102    {
103        synchronized(fCalendar) {
104            fCalendar[0].setTime(dateInMonth);
105
106            int era = fCalendar[0].get(Calendar.ERA);
107            int year = fCalendar[0].get(Calendar.YEAR);
108            int month = fCalendar[0].get(Calendar.MONTH);
109
110            fCalendar[0].clear();
111            fCalendar[0].set(Calendar.ERA, era);
112            fCalendar[0].set(Calendar.YEAR, year);
113            fCalendar[0].set(Calendar.MONTH, month);
114            fCalendar[0].set(Calendar.DATE, 1);
115
116            return fCalendar[0].getTime();
117        }
118    }
119
120    private void calculate()
121    {
122        //
123        // As a workaround for JDK 1.1.3 and below, where Calendars and time
124        // zones are a bit goofy, always set my calendar's time zone to UTC.
125        // You would think I would want to do this in the "set" function above,
126        // but if I do that, the program hangs when this class is loaded,
127        // perhaps due to some sort of static initialization ordering problem.
128        // So I do it here instead.
129        //
130        fCalendar[0].setTimeZone(new SimpleTimeZone(0, "UTC"));
131
132        Calendar c = (Calendar)fCalendar[0].clone(); // Temporary copy
133
134        fStartOfMonth = startOfMonth(fStartOfMonth);
135
136        // Stash away a few useful constants for this calendar and display
137        minDay = c.getMinimum(Calendar.DAY_OF_WEEK);
138        daysInWeek = c.getMaximum(Calendar.DAY_OF_WEEK) - minDay + 1;
139
140        firstDayOfWeek = Calendar.getInstance(fDisplayLocale).getFirstDayOfWeek();
141
142        // Stash away a Date for the start of this month
143
144        // Find the day of week of the first day in this month
145        c.setTime(fStartOfMonth);
146        firstDayInMonth = c.get(Calendar.DAY_OF_WEEK);
147        int firstWeek = c.get(Calendar.WEEK_OF_MONTH);
148
149        // Now find the # of days in the month
150        c.roll(Calendar.DATE, false);
151        daysInMonth = c.get(Calendar.DATE);
152
153        // Finally, find the end of the month, i.e. the start of the next one
154        c.roll(Calendar.DATE, true);
155        c.add(Calendar.MONTH, 1);
156        c.getTime();        // JDK 1.1.2 bug workaround
157        c.add(Calendar.SECOND, -1);
158        Date endOfMonth = c.getTime();
159        if(endOfMonth==null){
160         //do nothing
161        }
162        endOfMonth = null;
163        int lastWeek = c.get(Calendar.WEEK_OF_MONTH);
164
165        // Calculate the number of full or partial weeks in this month.
166        numWeeks = lastWeek - firstWeek + 1;
167
168        dirty = false;
169    }
170
171    static final int XINSET = 4;
172    static final int YINSET = 2;
173
174    /*
175     * Convert from the day number within a month (1-based)
176     * to the cell coordinates on the calendar (0-based)
177     */
178    private void dateToCell(int date, Point pos)
179    {
180        int cell = (date + firstDayInMonth - firstDayOfWeek - minDay);
181        if (firstDayInMonth < firstDayOfWeek) {
182            cell += daysInWeek;
183        }
184
185        pos.x = cell % daysInWeek;
186        pos.y = cell / daysInWeek;
187    }
188    //private Point dateToCell(int date) {
189    //    Point p = new Point(0,0);
190    //    dateToCell(date, p);
191    //    return p;
192    //}
193
194    public void paint(Graphics g) {
195
196        if (dirty) {
197            calculate();
198        }
199
200        Point cellPos = new Point(0,0);     // Temporary variable
201        Dimension d = this.getSize();
202
203        g.setColor(Color.lightGray);
204        g.fillRect(0,0,d.width,d.height);
205
206        // Draw the day names at the top
207        g.setColor(Color.black);
208        g.setFont(DemoUtility.labelFont);
209        FontMetrics fm = g.getFontMetrics();
210        int labelHeight = fm.getHeight() + YINSET * 2;
211
212        int v = fm.getAscent() + YINSET;
213        for (int i = 0; i < daysInWeek; i++) {
214            int dayNum = (i + minDay + firstDayOfWeek - 2) % daysInWeek + 1;
215            String dayName = fSymbols[0].getWeekdays()[dayNum];
216
217
218            double h;
219            if (leftToRight) {
220                h = d.width*(i + 0.5) / daysInWeek;
221            } else {
222                h = d.width*(daysInWeek - i - 0.5) / daysInWeek;
223            }
224            h -= fm.stringWidth(dayName) / 2;
225
226            g.drawString(dayName, (int)h, v);
227        }
228
229        double cellHeight = (d.height - labelHeight - 1) / numWeeks;
230        double cellWidth = (double)(d.width - 1) / daysInWeek;
231
232        // Draw a white background in the part of the calendar
233        // that displays this month.
234        // First figure out how much of the first week should be shaded.
235        {
236            g.setColor(Color.white);
237            dateToCell(1, cellPos);
238            int width = (int)(cellPos.x*cellWidth);  // Width of unshaded area
239
240            if (leftToRight) {
241                g.fillRect((int)(width), labelHeight ,
242                           d.width - width, (int)cellHeight);
243            } else {
244                g.fillRect(0, labelHeight ,
245                           d.width - width, (int)cellHeight);
246            }
247
248            // All of the intermediate weeks get shaded completely
249            g.fillRect(0, (int)(labelHeight + cellHeight),
250                        d.width, (int)(cellHeight * (numWeeks - 2)));
251
252            // Now figure out the last week.
253            dateToCell(daysInMonth, cellPos);
254            width = (int)((cellPos.x+1)*cellWidth);  // Width of shaded area
255
256            if (leftToRight) {
257                g.fillRect(0, (int)(labelHeight + (numWeeks-1) * cellHeight),
258                           width, (int)cellHeight);
259            } else {
260                g.fillRect(d.width - width, (int)(labelHeight + (numWeeks-1) * cellHeight),
261                           width, (int)cellHeight);
262            }
263
264        }
265        // Draw the X/Y grid lines
266        g.setColor(Color.black);
267        for (int i = 0; i <= numWeeks; i++) {
268            int y = (int)(labelHeight + i * cellHeight);
269            g.drawLine(0, y, d.width - 1, y);
270        }
271        for (int i = 0; i <= daysInWeek; i++) {
272            int x = (int)(i * cellWidth);
273            g.drawLine(x, labelHeight, x, d.height - 1);
274        }
275
276        // Now loop through all of the days in the month, figure out where
277        // they go in the grid, and draw the day # for each one
278
279        // Figure out the date of the first cell in the calendar display
280        int cell = (1 + firstDayInMonth - firstDayOfWeek - minDay);
281        if (firstDayInMonth < firstDayOfWeek) {
282            cell += daysInWeek;
283        }
284
285        Calendar c = (Calendar)fCalendar[0].clone();
286        c.setTime(fStartOfMonth);
287        c.add(Calendar.DATE, -cell);
288
289        StringBuffer buffer = new StringBuffer();
290
291        for (int row = 0; row < numWeeks; row++) {
292            for (int col = 0; col < daysInWeek; col++) {
293
294                g.setFont(DemoUtility.numberFont);
295                g.setColor(Color.black);
296                fm = g.getFontMetrics();
297
298                int cellx;
299                if (leftToRight) {
300                    cellx = (int)((col) * cellWidth);
301                } else {
302                    cellx = (int)((daysInWeek - col - 1) * cellWidth);
303                }
304
305                int celly = (int)(row * cellHeight + labelHeight);
306
307                for (int i = 0; i < 2; i++) {
308                    fCalendar[i].setTime(c.getTime());
309
310                    int date = fCalendar[i].get(Calendar.DATE);
311                    buffer.setLength(0);
312                    buffer.append(date);
313                    String dayNum = buffer.toString();
314
315                    int x;
316
317                    if (leftToRight) {
318                        x = cellx + (int)cellWidth - XINSET - fm.stringWidth(dayNum);
319                    } else {
320                        x = cellx + XINSET;
321                    }
322                    int y = celly + + fm.getAscent() + YINSET + i * fm.getHeight();
323
324                    if (fColor[i] != null) {
325                        g.setColor(fColor[i]);
326                    }
327                    g.drawString(dayNum, x, y);
328
329                    if (date == 1 || row == 0 && col == 0) {
330                        g.setFont(DemoUtility.numberFont);
331                        String month = fSymbols[i].getMonths()[
332                                            fCalendar[i].get(Calendar.MONTH)];
333
334                        if (leftToRight) {
335                            x = cellx + XINSET;
336                        } else {
337                            x = cellx + (int)cellWidth - XINSET - fm.stringWidth(month);
338                        }
339                        g.drawString(month, x, y);
340                    }
341                }
342
343                c.add(Calendar.DATE, 1);
344            }
345        }
346    }
347
348    // Important state variables
349    private Calendar[]          fCalendar = new Calendar[4];
350    private Color[]             fColor = new Color[4];
351
352    private Locale              fDisplayLocale;
353    private DateFormatSymbols[] fSymbols = new DateFormatSymbols[4];
354
355    private Date                fStartOfMonth = new Date();     // 00:00:00 on first day of month
356
357    // Cached calculations to make drawing faster.
358    private transient int       minDay;           // Minimum legal day #
359    private transient int       daysInWeek;       // # of days in a week
360    private transient int       firstDayOfWeek;   // First day to display in week
361    private transient int       numWeeks;         // # full or partial weeks in month
362    private transient int       daysInMonth;      // # days in this month
363    private transient int       firstDayInMonth;  // Day of week of first day in month
364    private transient boolean   leftToRight;
365
366    private transient boolean dirty = true;
367}
368