Java Source Code: org.eclipse.swt.widgets.DateTime


   1: /*******************************************************************************
   2:  * Copyright (c) 2005, 2007 IBM Corporation and others.
   3:  * All rights reserved. This program and the accompanying materials
   4:  * are made available under the terms of the Eclipse Public License v1.0
   5:  * which accompanies this distribution, and is available at
   6:  * http://www.eclipse.org/legal/epl-v10.html
   7:  *
   8:  * Contributors:
   9:  *     IBM Corporation - initial API and implementation
  10:  *******************************************************************************/
  11: package org.eclipse.swt.widgets;
  12: 
  13: 
  14: import java.text.DateFormatSymbols; //TODO: not in CLDC
  15: import java.util.Calendar; // TODO: Gregorian not in CLDC
  16: 
  17: import org.eclipse.swt.*;
  18: import org.eclipse.swt.graphics.*;
  19: import org.eclipse.swt.events.*;
  20: 
  21: /**
  22:  * Instances of this class are selectable user interface
  23:  * objects that allow the user to enter and modify date
  24:  * or time values.
  25:  * <p>
  26:  * Note that although this class is a subclass of <code>Composite</code>,
  27:  * it does not make sense to add children to it, or set a layout on it.
  28:  * </p>
  29:  * <dl>
  30:  * <dt><b>Styles:</b></dt>
  31:  * <dd>DATE, TIME, CALENDAR, SHORT, MEDIUM, LONG</dd>
  32:  * <dt><b>Events:</b></dt>
  33:  * <dd>Selection</dd>
  34:  * </dl>
  35:  * <p>
  36:  * Note: Only one of the styles DATE, TIME, or CALENDAR may be specified,
  37:  * and only one of the styles SHORT, MEDIUM, or LONG may be specified.
  38:  * </p><p>
  39:  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
  40:  * </p>
  41:  *
  42:  * @since 3.3
  43:  */
  44: // TODO: locale is currently hard-coded to EN_US. This needs to be fixed. Use java.text.DateFormat?
  45: 
  46: // TODO: add accessibility to calendar - note: win32 calendar is not accessible... test gtk
  47: 
  48: // TODO: Consider allowing an optional drop-down calendar for SWT.DATE | SWT.DROP_DOWN
  49: 
  50: // TODO: Consider adding set/get day-of-week API, i.e. 1-7 (Sun-Sat)
  51: // Win, Mac, and Java all provide this (but GTK does not).
  52: 
  53:	  public class DateTime extends Composite {
  54:    Color fg, bg;
  55:    Calendar calendar;
  56:    DateFormatSymbols formatSymbols;
  57:    Button down, up, monthDown, monthUp, yearDown, yearUp;
  58:    Text text;
  59:    Point[] fieldIndices;
  60:    int[] fieldNames;
  61:    int fieldCount, currentField = 0, characterCount = 0;
  62:    boolean ignoreVerify = false;
  63:    
  64:    // TODO: default format strings need more work for locale
  65:    static final String DEFAULT_SHORT_DATE_FORMAT = "MM/YYYY";
  66:    static final String DEFAULT_MEDIUM_DATE_FORMAT = "MM/DD/YYYY";
  67:    static final String DEFAULT_LONG_DATE_FORMAT = "MM/DD/YYYY";
  68:    static final String DEFAULT_SHORT_TIME_FORMAT = "HH:MM AM";
  69:    static final String DEFAULT_MEDIUM_TIME_FORMAT = "HH:MM:SS AM";
  70:    static final String DEFAULT_LONG_TIME_FORMAT = "HH:MM:SS AM";
  71:    static final int MARGIN_WIDTH = 2;
  72:    static final int MARGIN_HEIGHT = 1;
  73:    static final int MIN_YEAR = 1752; // Gregorian switchover in North America: September 19, 1752
  74:    static final int MAX_YEAR = 9999;
  75:
  76:/**
  77: * Constructs a new instance of this class given its parent
  78: * and a style value describing its behavior and appearance.
  79: * <p>
  80: * The style value is either one of the style constants defined in
  81: * class <code>SWT</code> which is applicable to instances of this
  82: * class, or must be built by <em>bitwise OR</em>'ing together 
  83: * (that is, using the <code>int</code> "|" operator) two or more
  84: * of those <code>SWT</code> style constants. The class description
  85: * lists the style constants that are applicable to the class.
  86: * Style bits are also inherited from superclasses.
  87: * </p>
  88: *
  89: * @param parent a composite control which will be the parent of the new instance (cannot be null)
  90: * @param style the style of control to construct
  91: *
  92: * @exception IllegalArgumentException <ul>
  93: *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
  94: * </ul>
  95: * @exception SWTException <ul>
  96: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
  97: *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
  98: * </ul>
  99: *
 100: * @see SWT#DATE
 101: * @see SWT#TIME
 102: * @see SWT#CALENDAR
 103: * @see Widget#checkSubclass
 104: * @see Widget#getStyle
 105: */
 106:	  public DateTime(Composite parent, int style) {
 107:    super(parent, checkStyle(style) | SWT.NO_REDRAW_RESIZE);
 108:    calendar = Calendar.getInstance();
 109:    formatSymbols = new DateFormatSymbols();
 110:	      if ((this.style & SWT.CALENDAR) != 0) {
 111:	          Listener listener = new Listener() {
 112:	              public void handleEvent(Event event) {
 113:	                  switch(event.type) {
 114:                    case SWT.Paint:        handlePaint(event); break;
 115:                    case SWT.Resize:    handleResize(event); break;
 116:                    case SWT.MouseDown:    handleMouseDown(event); break;
 117:                    case SWT.KeyDown:    handleKeyDown(event); break;
 118:                    case SWT.Traverse:    handleTraverse(event); break;
 119:                }
 120:            }
 121:        };
 122:        addListener(SWT.Paint, listener);
 123:        addListener(SWT.Resize, listener);
 124:        addListener(SWT.MouseDown, listener);
 125:        addListener(SWT.KeyDown, listener);
 126:        addListener(SWT.Traverse, listener);
 127:        yearDown = new Button(this, SWT.ARROW | SWT.LEFT);
 128:        //yearDown.setToolTipText(SWT.getMessage ("SWT_Last_Year")); //$NON-NLS-1$
 129:        monthDown = new Button(this, SWT.ARROW | SWT.LEFT);
 130:        //monthDown.setToolTipText(SWT.getMessage ("SWT_Last_Month")); //$NON-NLS-1$
 131:        monthUp = new Button(this, SWT.ARROW | SWT.RIGHT);
 132:        //monthUp.setToolTipText(SWT.getMessage ("SWT_Next_Month")); //$NON-NLS-1$
 133:        yearUp = new Button(this, SWT.ARROW | SWT.RIGHT);
 134:        //yearUp.setToolTipText(SWT.getMessage ("SWT_Next_Year")); //$NON-NLS-1$
 135:	          listener = new Listener() {
 136:	              public void handleEvent(Event event) {
 137:                handleSelection(event);
 138:            }
 139:        };
 140:        yearDown.addListener(SWT.Selection, listener);
 141:        monthDown.addListener(SWT.Selection, listener);
 142:        monthUp.addListener(SWT.Selection, listener);
 143:        yearUp.addListener(SWT.Selection, listener);
 144:    } else {
 145:        text = new Text(this, SWT.SINGLE);
 146:	          if ((this.style & SWT.DATE) != 0) {
 147:            setFormat((this.style & SWT.SHORT) != 0 ? DEFAULT_SHORT_DATE_FORMAT : (this.style & SWT.LONG) != 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT);
 148:        } else { // SWT.TIME
 149:            setFormat((this.style & SWT.SHORT) != 0 ? DEFAULT_SHORT_TIME_FORMAT : (this.style & SWT.LONG) != 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT);
 150:        }
 151:        text.setText(getFormattedString(this.style));
 152:	          Listener listener = new Listener() {
 153:	              public void handleEvent(Event event) {
 154:	                  switch(event.type) {
 155:                    case SWT.KeyDown: onKeyDown(event); break;
 156:                    case SWT.FocusIn: onFocusIn(event); break;
 157:                    case SWT.FocusOut: onFocusOut(event); break;
 158:                    case SWT.MouseDown: onMouseClick(event); break;
 159:                    case SWT.MouseUp: onMouseClick(event); break;
 160:                    case SWT.Verify: onVerify(event); break;
 161:                }
 162:            }
 163:        };
 164:        text.addListener(SWT.KeyDown, listener);
 165:        text.addListener(SWT.FocusIn, listener);
 166:        text.addListener(SWT.FocusOut, listener);
 167:        text.addListener(SWT.MouseDown, listener);
 168:        text.addListener(SWT.MouseUp, listener);
 169:        text.addListener(SWT.Verify, listener);
 170:        up = new Button(this, SWT.ARROW | SWT.UP);
 171:        //up.setToolTipText(SWT.getMessage ("SWT_Up")); //$NON-NLS-1$
 172:        down = new Button(this, SWT.ARROW | SWT.DOWN);
 173:        //down.setToolTipText(SWT.getMessage ("SWT_Down")); //$NON-NLS-1$
 174:	          up.addListener(SWT.Selection, new Listener() {
 175:	              public void handleEvent(Event event) {
 176:                incrementField(+1);
 177:                text.setFocus();
 178:            }
 179:        });
 180:	          down.addListener(SWT.Selection, new Listener() {
 181:	              public void handleEvent(Event event) {
 182:                incrementField(-1);
 183:                text.setFocus();
 184:            }
 185:        });
 186:	          addListener(SWT.Resize, new Listener() {
 187:	              public void handleEvent(Event event) {
 188:                onResize(event);
 189:            }
 190:        });
 191:    }
 192:}
 193:
 194:	  static int checkStyle (int style) {
 195:    style = checkBits (style, SWT.DATE, SWT.TIME, SWT.CALENDAR, 0, 0, 0);
 196:    return checkBits (style, SWT.MEDIUM, SWT.SHORT, SWT.LONG, 0, 0, 0);
 197:}
 198:
 199:	  String formattedStringValue(int fieldName, int value, boolean adjust) {
 200:	      if (fieldName == Calendar.AM_PM) {
 201:        String[] ampm = formatSymbols.getAmPmStrings();
 202:        return ampm[value];
 203:    }
 204:	      if (adjust) {
 205:	          if (fieldName == Calendar.HOUR && value == 0) {
 206:            return String.valueOf(12); // TODO: needs more work for setFormat and locale
 207:        }
 208:	          if (fieldName == Calendar.MONTH) {
 209:            return String.valueOf(value + 1);
 210:        }
 211:    }
 212:    return String.valueOf(value);
 213:}
 214:
 215:	  String getFormattedString(int style) {
 216:	      if ((style & SWT.TIME) != 0) {
 217:        String[] ampm = formatSymbols.getAmPmStrings();
 218:        int h = calendar.get(Calendar.HOUR); if (h == 0) h = 12;
 219:        int m = calendar.get(Calendar.MINUTE);
 220:        int s = calendar.get(Calendar.SECOND);
 221:        int a = calendar.get(Calendar.AM_PM);
 222:        if ((style & SWT.SHORT) != 0) return "" + (h < 10 ? " " : "") + h + ":" + (m < 10 ? " " : "") + m + " " + ampm[a];
 223:        return "" + (h < 10 ? " " : "") + h + ":" + (m < 10 ? " " : "") + m + ":" + (s < 10 ? " " : "") + s + " " + ampm[a];
 224:    }
 225:    /* SWT.DATE */
 226:    int y = calendar.get(Calendar.YEAR);
 227:    int m = calendar.get(Calendar.MONTH) + 1;
 228:    int d = calendar.get(Calendar.DAY_OF_MONTH);
 229:    if ((style & SWT.SHORT) != 0) return "" + (m < 10 ? " " : "") + m + "/" + y;
 230:    return "" + (m < 10 ? " " : "") + m + "/" + (d < 10 ? " " : "") + d + "/" + y;
 231:}
 232:
 233:	  String getComputeSizeString(int style) {
 234:	      if ((style & SWT.DATE) != 0) {
 235:        return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_DATE_FORMAT : (style & SWT.LONG) != 0 ? DEFAULT_LONG_DATE_FORMAT : DEFAULT_MEDIUM_DATE_FORMAT;
 236:    }
 237:    // SWT.TIME
 238:    return (style & SWT.SHORT) != 0 ? DEFAULT_SHORT_TIME_FORMAT : (style & SWT.LONG) != 0 ? DEFAULT_LONG_TIME_FORMAT : DEFAULT_MEDIUM_TIME_FORMAT;
 239:}
 240:
 241:	  int getFieldIndex(int fieldName) {
 242:	      for (int i = 0; i < fieldCount; i++) {
 243:	          if (fieldNames[i] == fieldName) {
 244:            return i;
 245:        }
 246:    }
 247:    return -1;
 248:}
 249:
 250:/**
 251: * Adds the listener to the collection of listeners who will
 252: * be notified when the control is selected by the user, by sending
 253: * it one of the messages defined in the <code>SelectionListener</code>
 254: * interface.
 255: * <p>
 256: * <code>widgetSelected</code> is called when the user changes the control's value.
 257: * <code>widgetDefaultSelected</code> is not called.
 258: * </p>
 259: *
 260: * @param listener the listener which should be notified
 261: *
 262: * @exception IllegalArgumentException <ul>
 263: *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 264: * </ul>
 265: * @exception SWTException <ul>
 266: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 267: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 268: * </ul>
 269: *
 270: * @see SelectionListener
 271: * @see #removeSelectionListener
 272: * @see SelectionEvent
 273: */
 274:	  public void addSelectionListener(SelectionListener listener) {
 275:    checkWidget ();
 276:    if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
 277:    TypedListener typedListener = new TypedListener (listener);
 278:    addListener (SWT.Selection, typedListener);
 279:    addListener (SWT.DefaultSelection, typedListener);
 280:}
 281:
 282:	  protected void checkSubclass () {
 283:    if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
 284:}
 285:
 286:	  void commitCurrentField() {
 287:	      if (characterCount > 0) {
 288:        characterCount = 0;
 289:        int fieldName = fieldNames[currentField];
 290:        int start = fieldIndices[currentField].x;
 291:        int end = fieldIndices[currentField].y;
 292:        String value = text.getText(start, end - 1);
 293:        int s = value.lastIndexOf(' ');
 294:        if (s != -1) value = value.substring(s + 1);
 295:        int newValue = unformattedIntValue(fieldName, value, characterCount == 0, calendar.getActualMaximum(fieldName));
 296:        if (newValue != -1) setTextField(fieldName, newValue, true, true);
 297:    }
 298:}
 299:
 300:	  public Point computeSize (int wHint, int hHint, boolean changed) {
 301:    checkWidget();
 302:    int width = 0, height = 0;
 303:    Rectangle trim;
 304:	      if ((style & SWT.CALENDAR) != 0) {
 305:        Point cellSize = getCellSize(null);
 306:        Point buttonSize = monthDown.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
 307:        width = cellSize.x * 7;
 308:        height = cellSize.y * 7 + Math.max(cellSize.y, buttonSize.y);
 309:    } else { /* SWT.DATE and SWT.TIME */
 310:        GC gc = new GC(text);
 311:        Point textSize = gc.stringExtent(getComputeSizeString(style));
 312:        gc.dispose();
 313:        trim = text.computeTrim(0, 0, textSize.x, textSize.y);
 314:        Point buttonSize = up.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
 315:        width = trim.width + buttonSize.x;
 316:        height = Math.max(trim.height, buttonSize.y);
 317:    }
 318:    if (wHint != SWT.DEFAULT) width = wHint;
 319:    if (hHint != SWT.DEFAULT) height = hHint;
 320:    int borderWidth = getBorderWidth ();
 321:    return new Point (width + 2*borderWidth, height + 2*borderWidth);
 322:}
 323:
 324:	  void drawDay(GC gc, Point cellSize, int day) {
 325:    int cell = getCell(day);
 326:    Point location = getCellLocation(cell, cellSize);
 327:    String str = String.valueOf(day);
 328:    Point extent = gc.stringExtent(str);
 329:    int date = calendar.get(Calendar.DAY_OF_MONTH);
 330:	      if (day == date) {
 331:        Display display = getDisplay();
 332:        gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
 333:        gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
 334:        gc.fillRectangle(location.x, location.y, cellSize.x, cellSize.y);
 335:    }
 336:    gc.drawString(str, location.x + (cellSize.x - extent.x) / 2, location.y + (cellSize.y - extent.y) / 2, true);
 337:	      if (day == date) {
 338:        gc.setBackground(getBackground());
 339:        gc.setForeground(getForeground());
 340:    }
 341:}
 342:
 343:	  void drawDays(GC gc, Point cellSize, Rectangle client) {
 344:    gc.setBackground(getBackground());
 345:    gc.setForeground(getForeground());
 346:    gc.fillRectangle(0, cellSize.y, client.width, cellSize.y * 7);
 347:    int firstDay = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
 348:    int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
 349:	      for (int day = firstDay; day <= lastDay; day++) {
 350:        drawDay(gc, cellSize, day);
 351:    }
 352:}
 353:
 354:	  void drawDaysOfWeek(GC gc, Point cellSize, Rectangle client) {
 355:    Display display = getDisplay();
 356:    gc.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
 357:    gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
 358:    gc.fillRectangle(0, 0, client.width, cellSize.y);
 359:    String[] days = formatSymbols.getShortWeekdays();
 360:    int x = 0, y = 0; 
 361:	      for (int i = 1; i < days.length; i++) {
 362:        String day = days[i];
 363:        Point extent = gc.stringExtent(day);
 364:        gc.drawString(day, x + (cellSize.x - extent.x) / 2, y + (cellSize.y - extent.y) / 2, true);
 365:        x += cellSize.x;
 366:    }
 367:    gc.drawLine(0, cellSize.y - 1, client.width, cellSize.y - 1);
 368:}
 369:
 370:	  void drawMonth(GC gc, Point cellSize, Rectangle client) {
 371:    Display display = getDisplay();
 372:    gc.setBackground(display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
 373:    gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND));
 374:    int y = cellSize.y * 7;
 375:    gc.fillRectangle(0, y, client.width, cellSize.y);
 376:    gc.drawLine(0, y - 1, client.width, y - 1);
 377:    String str = formatSymbols.getShortMonths()[calendar.get(Calendar.MONTH)] + ", " + calendar.get(Calendar.YEAR);
 378:    Point extent = gc.stringExtent(str);
 379:    gc.drawString(str, (cellSize.x * 7 - extent.x) / 2, y + (cellSize.y - extent.y) / 2, true);
 380:}
 381:
 382:	  Point getCellSize(GC gc) {
 383:    boolean dispose = gc == null; 
 384:    if (gc == null) gc = new GC(this);
 385:    int width = 0, height = 0;
 386:    String[] days = formatSymbols.getShortWeekdays();
 387:	      for (int i = 0; i < days.length; i++) {
 388:        Point extent = gc.stringExtent(days[i]);
 389:        width = Math.max(width, extent.x);
 390:        height = Math.max(height, extent.y);
 391:    }
 392:    int firstDay = calendar.getMinimum(Calendar.DAY_OF_MONTH);
 393:    int lastDay = calendar.getMaximum(Calendar.DAY_OF_MONTH);
 394:	      for (int day = firstDay; day <= lastDay; day++) {
 395:        Point extent = gc.stringExtent(String.valueOf(day));
 396:        width = Math.max(width, extent.x);
 397:        height = Math.max(height, extent.y);    
 398:    }
 399:    if (dispose) gc.dispose();
 400:    return new Point(width + MARGIN_WIDTH * 2, height + MARGIN_HEIGHT * 2);
 401:}
 402:
 403:	  Point getCellLocation(int cell, Point cellSize) {
 404:    return new Point(cell % 7 * cellSize.x, cell / 7 * cellSize.y);
 405:}
 406:
 407:	  int getCell(int date) {
 408:    int day = calendar.get(Calendar.DAY_OF_MONTH);
 409:    calendar.set(Calendar.DAY_OF_MONTH, 1);
 410:    int result = date + calendar.get(Calendar.DAY_OF_WEEK) + 5;
 411:    calendar.set(Calendar.DAY_OF_MONTH, day);
 412:    return result;
 413:}
 414:
 415:	  int getDate(int cell) {
 416:    int day = calendar.get(Calendar.DAY_OF_MONTH);
 417:    calendar.set(Calendar.DAY_OF_MONTH, 1);
 418:    int result = cell - calendar.get(Calendar.DAY_OF_WEEK) - 5;
 419:    calendar.set(Calendar.DAY_OF_MONTH, day);
 420:    return result;
 421:}
 422:
 423:	  public Color getBackground() {
 424:    checkWidget();
 425:	      if (bg == null) {
 426:        return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
 427:    }
 428:    return bg;
 429:}
 430:
 431:/**
 432: * Returns the receiver's date, or day of the month.
 433: * <p>
 434: * The first day of the month is 1, and the last day depends on the month and year.
 435: * </p>
 436: *
 437: * @return a positive integer beginning with 1
 438: *
 439: * @exception SWTException <ul>
 440: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 441: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 442: * </ul>
 443: */
 444:	  public int getDay() {
 445:    checkWidget();
 446:    return calendar.get(Calendar.DAY_OF_MONTH);
 447:}
 448:
 449:	  public Color getForeground() {
 450:    checkWidget();
 451:	      if (fg == null) {
 452:        return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
 453:    }
 454:    return fg;
 455:}
 456:
 457:/**
 458: * Returns the receiver's hours.
 459: * <p>
 460: * Hours is an integer between 0 and 23.
 461: * </p>
 462: *
 463: * @return an integer between 0 and 23
 464: *
 465: * @exception SWTException <ul>
 466: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 467: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 468: * </ul>
 469: */
 470:	  public int getHours () {
 471:    checkWidget ();
 472:    return calendar.get(Calendar.HOUR_OF_DAY);
 473:}
 474:
 475:/**
 476: * Returns the receiver's minutes.
 477: * <p>
 478: * Minutes is an integer between 0 and 59.
 479: * </p>
 480: *
 481: * @return an integer between 0 and 59
 482: *
 483: * @exception SWTException <ul>
 484: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 485: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 486: * </ul>
 487: */
 488:	  public int getMinutes () {
 489:    checkWidget ();
 490:    return calendar.get(Calendar.MINUTE);
 491:}
 492:
 493:/**
 494: * Returns the receiver's month.
 495: * <p>
 496: * The first month of the year is 0, and the last month is 11.
 497: * </p>
 498: *
 499: * @return an integer between 0 and 11
 500: *
 501: * @exception SWTException <ul>
 502: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 503: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 504: * </ul>
 505: */
 506:	  public int getMonth() {
 507:    checkWidget();
 508:    return calendar.get(Calendar.MONTH);
 509:}
 510:
 511:/**
 512: * Returns the receiver's seconds.
 513: * <p>
 514: * Seconds is an integer between 0 and 59.
 515: * </p>
 516: *
 517: * @return an integer between 0 and 59
 518: *
 519: * @exception SWTException <ul>
 520: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 521: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 522: * </ul>
 523: */
 524:	  public int getSeconds () {
 525:    checkWidget ();
 526:    return calendar.get(Calendar.SECOND);
 527:}
 528:
 529:/**
 530: * Returns the receiver's year.
 531: * <p>
 532: * The first year is 1752 and the last year is 9999.
 533: * </p>
 534: *
 535: * @return an integer between 1752 and 9999
 536: *
 537: * @exception SWTException <ul>
 538: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 539: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 540: * </ul>
 541: */
 542:	  public int getYear() {
 543:    checkWidget();
 544:    return calendar.get(Calendar.YEAR);
 545:}
 546:
 547:	  void handleKeyDown(Event event) {
 548:    int newDay = calendar.get(Calendar.DAY_OF_MONTH);
 549:	      switch (event.keyCode) {
 550:        case SWT.ARROW_DOWN: newDay += 7; break;
 551:        case SWT.ARROW_UP: newDay -= 7; break;
 552:        case SWT.ARROW_RIGHT: newDay += 1; break;
 553:        case SWT.ARROW_LEFT: newDay -= 1; break;
 554:    }
 555:    setDay(newDay, true);
 556:}
 557:
 558:	  void handleMouseDown(Event event) {
 559:    setFocus();
 560:    Point cellSize = getCellSize(null);
 561:    int column = event.x / cellSize.x;
 562:    int row = event.y / cellSize.y;    
 563:    int cell = row * 7 + column;
 564:    int newDay = getDate(cell);
 565:    setDay(newDay, true);
 566:}
 567:
 568:	  void handlePaint(Event event) {
 569:    GC gc = event.gc;
 570:    Rectangle client = getClientArea();
 571:    Point cellSize = getCellSize(gc);
 572:    drawDaysOfWeek(gc, cellSize, client);
 573:    drawDays(gc, cellSize, client);
 574:    drawMonth(gc, cellSize, client);
 575:}
 576:
 577:	  void handleResize(Event event) {
 578:    yearDown.pack();
 579:    monthDown.pack();
 580:    monthUp.pack();
 581:    yearUp.pack();
 582:    Point cellSize = getCellSize(null);
 583:    Point size = monthDown.getSize();
 584:    int height = Math.max(cellSize.y, size.y);
 585:    int y = cellSize.y * 7 + (height - size.y) / 2;
 586:    yearDown.setLocation(0, y);
 587:    monthDown.setLocation(size.x, y);
 588:    int x = cellSize.x * 7 - size.x;
 589:    monthUp.setLocation(x - size.x, y);
 590:    yearUp.setLocation(x, y);
 591:}
 592:
 593:	  void handleSelection(Event event) {
 594:	      if (event.widget == monthDown) {
 595:        calendar.add(Calendar.MONTH, -1);
 596:    } else if (event.widget == monthUp) {
 597:        calendar.add(Calendar.MONTH, 1);
 598:    } else if (event.widget == yearDown) {
 599:        calendar.add(Calendar.YEAR, -1);
 600:    } else if (event.widget == yearUp) {                
 601:        calendar.add(Calendar.YEAR, 1);
 602:    } else {
 603:        return;
 604:    }
 605:    redraw();
 606:    notifyListeners(SWT.Selection, new Event());
 607:}
 608:
 609:	  void handleTraverse(Event event) {
 610:	      switch (event.detail) {
 611:        case SWT.TRAVERSE_ESCAPE:
 612:        case SWT.TRAVERSE_PAGE_NEXT:
 613:        case SWT.TRAVERSE_PAGE_PREVIOUS:
 614:        case SWT.TRAVERSE_RETURN:
 615:        case SWT.TRAVERSE_TAB_NEXT:
 616:        case SWT.TRAVERSE_TAB_PREVIOUS:
 617:            event.doit = true;
 618:            break;
 619:    }    
 620:}
 621:
 622:	  boolean isValid(int fieldName, int value) {
 623:    int min = calendar.getActualMinimum(fieldName);
 624:    int max = calendar.getActualMaximum(fieldName);
 625:    return value >= min && value <= max;
 626:}
 627:
 628:	  void onKeyDown(Event event) {
 629:    int fieldName;
 630:	      switch (event.keyCode) {
 631:        case SWT.ARROW_RIGHT:
 632:        case SWT.KEYPAD_DIVIDE:
 633:            // a right arrow or a valid separator navigates to the field on the right, with wraping
 634:            selectField((currentField + 1) % fieldCount);
 635:            break;
 636:        case SWT.ARROW_LEFT:
 637:            // navigate to the field on the left, with wrapping
 638:            int index = currentField - 1;
 639:            selectField(index < 0 ? fieldCount - 1 : index);
 640:            break;
 641:        case SWT.ARROW_UP:
 642:        case SWT.KEYPAD_ADD:
 643:            // set the value of the current field to value + 1, with wrapping
 644:            commitCurrentField();
 645:            incrementField(+1);
 646:            break;
 647:        case SWT.ARROW_DOWN:
 648:        case SWT.KEYPAD_SUBTRACT:
 649:            // set the value of the current field to value - 1, with wrapping
 650:            commitCurrentField();
 651:            incrementField(-1);
 652:            break;
 653:        case SWT.HOME:
 654:            // set the value of the current field to its minimum
 655:            fieldName = fieldNames[currentField];
 656:            setTextField(fieldName, calendar.getActualMinimum(fieldName), true, true);
 657:            break;
 658:        case SWT.END:
 659:            // set the value of the current field to its maximum
 660:            fieldName = fieldNames[currentField];
 661:            setTextField(fieldName, calendar.getActualMaximum(fieldName), true, true);
 662:            break;
 663:        default:
 664:	              switch (event.character) {
 665:                case '/':
 666:                case ':':
 667:                case '-':
 668:                case '.':
 669:                    // a valid separator navigates to the field on the right, with wraping
 670:                    selectField((currentField + 1) % fieldCount);
 671:                    break;
 672:            }
 673:    }
 674:}
 675:
 676:	  void onFocusIn(Event event) {
 677:    selectField(currentField);
 678:}
 679:
 680:	  void onFocusOut(Event event) {
 681:    commitCurrentField();
 682:}
 683:
 684:	  void onMouseClick(Event event) {
 685:    if (event.button != 1) return;
 686:    Point sel = text.getSelection();
 687:	      for (int i = 0; i < fieldCount; i++) {
 688:	          if (sel.x >= fieldIndices[i].x && sel.x <= fieldIndices[i].y) {
 689:            currentField = i;
 690:            break;
 691:        }
 692:    }
 693:    selectField(currentField);
 694:}
 695:
 696:	  void onResize(Event event) {
 697:    Rectangle rect = getClientArea ();
 698:    int width = rect.width;
 699:    int height = rect.height;
 700:    Point buttonSize = up.computeSize(SWT.DEFAULT, height);
 701:    int buttonHeight = buttonSize.y / 2;
 702:    text.setBounds(0, 0, width - buttonSize.x, height);
 703:    up.setBounds(width - buttonSize.x, 0, buttonSize.x, buttonHeight);
 704:    down.setBounds(width - buttonSize.x, buttonHeight, buttonSize.x, buttonHeight);
 705:}
 706:
 707:	  void onVerify(Event event) {
 708:    if (ignoreVerify) return;
 709:    event.doit = false;
 710:    int fieldName = fieldNames[currentField];
 711:    int start = fieldIndices[currentField].x;
 712:    int end = fieldIndices[currentField].y;
 713:    int length = end - start;
 714:    String newText = event.text;
 715:	      if (fieldName == Calendar.AM_PM) {
 716:        String[] ampm = formatSymbols.getAmPmStrings();
 717:	          if (newText.equalsIgnoreCase(ampm[Calendar.AM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.AM])) {
 718:            setTextField(fieldName, Calendar.AM, true, false);
 719:        } else if (newText.equalsIgnoreCase(ampm[Calendar.PM].substring(0, 1)) || newText.equalsIgnoreCase(ampm[Calendar.PM])) {
 720:            setTextField(fieldName, Calendar.PM, true, false);
 721:        }
 722:        return;
 723:    }
 724:	      if (characterCount > 0) {
 725:	          try {
 726:            Integer.parseInt(newText);
 727:        } catch (NumberFormatException ex) {
 728:            return;
 729:        }
 730:        String value = text.getText(start, end - 1);
 731:        int s = value.lastIndexOf(' ');
 732:        if (s != -1) value = value.substring(s + 1);
 733:        newText = "" + value + newText;
 734:    }
 735:    int newTextLength = newText.length();
 736:    boolean first = characterCount == 0;
 737:    characterCount = (newTextLength < length) ? newTextLength : 0;
 738:    int max = calendar.getActualMaximum(fieldName);
 739:    int min = calendar.getActualMinimum(fieldName);
 740:    int newValue = unformattedIntValue(fieldName, newText, characterCount == 0, max);
 741:	      if (newValue == -1) {
 742:        characterCount = 0;
 743:        return;
 744:    }
 745:	      if (first && newValue == 0 && length > 1) {
 746:        setTextField(fieldName, newValue, false, false);
 747:    } else if (min <= newValue && newValue <= max) {
 748:        setTextField(fieldName, newValue, characterCount == 0, characterCount == 0);
 749:    } else {
 750:	          if (newTextLength >= length) {
 751:            newText = newText.substring(newTextLength - length + 1);
 752:            newValue = unformattedIntValue(fieldName, newText, characterCount == 0, max);
 753:	              if (newValue != -1) {
 754:                characterCount = length - 1;
 755:	                  if (min <= newValue && newValue <= max) {
 756:                    setTextField(fieldName, newValue, characterCount == 0, true);
 757:                }
 758:            }
 759:        }
 760:    }
 761:}
 762:
 763:	  void incrementField(int amount) {
 764:    int fieldName = fieldNames[currentField];
 765:    int value = calendar.get(fieldName);
 766:	      if (fieldName == Calendar.HOUR) {
 767:        int max = calendar.getMaximum(Calendar.HOUR);
 768:        int min = calendar.getMinimum(Calendar.HOUR);
 769:	          if ((value == max && amount == 1) || (value == min && amount == -1)) {
 770:            int temp = currentField;
 771:            currentField = getFieldIndex(Calendar.AM_PM);
 772:            setTextField(Calendar.AM_PM, (calendar.get(Calendar.AM_PM) + 1) % 2, true, true);
 773:            currentField = temp;
 774:        }
 775:    }
 776:    setTextField(fieldName, value + amount, true, true);
 777:}
 778:
 779:	  void selectField(int index) {
 780:	      if (index != currentField) {
 781:        commitCurrentField();
 782:    }
 783:    final int start = fieldIndices[index].x;
 784:    final int end = fieldIndices[index].y;
 785:    Point pt = text.getSelection();
 786:    if (index == currentField && start == pt.x && end == pt.y) return;
 787:    currentField = index;
 788:	      display.asyncExec(new Runnable() {
 789:	          public void run() {
 790:	              if (!text.isDisposed()) {
 791:                String value = text.getText(start, end - 1);
 792:                int s = value.lastIndexOf(' ');
 793:                if (s == -1) s = start;
 794:                else s = start + s + 1;
 795:                text.setSelection(s, end);
 796:            }
 797:        }
 798:    });    
 799:}
 800:
 801:	  void setTextField(int fieldName, int value, boolean commit, boolean adjust) {
 802:	      if (commit) {
 803:        int max = calendar.getActualMaximum(fieldName);
 804:        int min = calendar.getActualMinimum(fieldName);
 805:	          if (fieldName == Calendar.YEAR) {
 806:            max = MAX_YEAR;
 807:            min = MIN_YEAR;
 808:            /* Special case: convert 1 or 2-digit years into reasonable 4-digit years. */
 809:            int currentYear = Calendar.getInstance().get(Calendar.YEAR);
 810:            int currentCentury = (currentYear / 100) * 100;
 811:            if (value < (currentYear + 30) % 100) value += currentCentury;
 812:            else if (value < 100) value += currentCentury - 100;
 813:        }
 814:        if (value > max) value = min; // wrap
 815:        if (value < min) value = max; // wrap
 816:    }
 817:    int start = fieldIndices[currentField].x;
 818:    int end = fieldIndices[currentField].y;
 819:    text.setSelection(start, end);
 820:    String newValue = formattedStringValue(fieldName, value, adjust);
 821:    StringBuffer buffer = new StringBuffer(newValue);
 822:    /* Convert leading 0's into spaces. */
 823:    int prependCount = end - start - buffer.length();
 824:	      for (int i = 0; i < prependCount; i++) {
 825:        buffer.insert(0, ' ');
 826:    }
 827:    newValue = buffer.toString();
 828:    ignoreVerify = true;
 829:    text.insert(newValue);
 830:    ignoreVerify = false;
 831:    selectField(currentField);
 832:    if (commit) setField(fieldName, value);
 833:}
 834:
 835:	  void setField(int fieldName, int value) {
 836:    if (calendar.get(fieldName) == value) return;
 837:	      if (fieldName == Calendar.AM_PM) {
 838:        calendar.roll(Calendar.HOUR_OF_DAY, 12); // TODO: needs more work for setFormat and locale
 839:    }
 840:    calendar.set(fieldName, value);
 841:    notifyListeners(SWT.Selection, new Event());
 842:}
 843:
 844:/**
 845: * Removes the listener from the collection of listeners who will
 846: * be notified when the control is selected by the user.
 847: *
 848: * @param listener the listener which should no longer be notified
 849: *
 850: * @exception IllegalArgumentException <ul>
 851: *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
 852: * </ul>
 853: * @exception SWTException <ul>
 854: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 855: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 856: * </ul>
 857: *
 858: * @see SelectionListener
 859: * @see #addSelectionListener
 860: */
 861:	  public void removeSelectionListener (SelectionListener listener) {
 862:    checkWidget ();
 863:    if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
 864:    removeListener (SWT.Selection, listener);
 865:    removeListener (SWT.DefaultSelection,listener);    
 866:}
 867:
 868:	  void redraw(int cell, Point cellSize) {
 869:    Point location = getCellLocation(cell, cellSize);
 870:    redraw(location.x, location.y, cellSize.x, cellSize.y, false);    
 871:}
 872:
 873:	  public void setBackground(Color color) {
 874:    checkWidget();
 875:    super.setBackground(color);
 876:    bg = color;
 877:    if (text != null) text.setBackground(color);
 878:}
 879:
 880:/**
 881: * Sets the receiver's date, or day of the month, to the specified day.
 882: * <p>
 883: * The first day of the month is 1, and the last day depends on the month and year.
 884: * </p>
 885: *
 886: * @param day a positive integer beginning with 1
 887: *
 888: * @exception SWTException <ul>
 889: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 890: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 891: * </ul>
 892: */
 893:	  public void setDay(int day) {
 894:    checkWidget();
 895:    if (!isValid(Calendar.DAY_OF_MONTH, day)) return;
 896:	      if ((style & SWT.CALENDAR) != 0) {
 897:        setDay(day, false);
 898:    } else {
 899:        calendar.set(Calendar.DAY_OF_MONTH, day);
 900:        updateControl();
 901:    }
 902:}
 903:
 904:	  void setDay(int newDay, boolean notify) {
 905:    int firstDay = calendar.getActualMinimum(Calendar.DAY_OF_MONTH);
 906:    int lastDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
 907:    if (!(firstDay <= newDay && newDay <= lastDay)) return;
 908:    Point cellSize = getCellSize(null);
 909:    redraw(getCell(calendar.get(Calendar.DAY_OF_MONTH)), cellSize);
 910:    calendar.set(Calendar.DAY_OF_MONTH, newDay);
 911:    redraw(getCell(calendar.get(Calendar.DAY_OF_MONTH)), cellSize);
 912:    if (notify) notifyListeners(SWT.Selection, new Event());
 913:}
 914:
 915:	  public void setFont(Font font) {
 916:    checkWidget();
 917:    super.setFont(font);
 918:    if (text != null) text.setFont(font);
 919:    redraw();
 920:}
 921:
 922:	  public void setForeground(Color color) {
 923:    checkWidget();
 924:    super.setForeground(color);
 925:    fg = color;
 926:    if (text != null) text.setForeground(color);
 927:}
 928:
 929:	  void setFormat(String string) {
 930:    checkWidget();
 931:    // TODO: this needs to be locale sensitive
 932:    fieldCount = (style & SWT.DATE) != 0 ? ((style & SWT.SHORT) != 0 ? 2 : 3) : ((style & SWT.SHORT) != 0 ? 3 : 4);
 933:    fieldIndices = new Point[fieldCount];
 934:    fieldNames = new int[fieldCount];
 935:	      if ((style & SWT.DATE) != 0) {
 936:        fieldNames[0] = Calendar.MONTH;
 937:        fieldIndices[0] = new Point(0, 2);
 938:	          if ((style & SWT.SHORT) != 0) {
 939:            fieldNames[1] = Calendar.YEAR;
 940:            fieldIndices[1] = new Point(3, 7);
 941:        } else {
 942:            fieldNames[1] = Calendar.DAY_OF_MONTH;
 943:            fieldIndices[1] = new Point(3, 5);
 944:            fieldNames[2] = Calendar.YEAR;
 945:            fieldIndices[2] = new Point(6, 10);
 946:        }
 947:    } else { /* SWT.TIME */
 948:        fieldNames[0] = Calendar.HOUR;
 949:        fieldIndices[0] = new Point(0, 2);
 950:        fieldNames[1] = Calendar.MINUTE;
 951:        fieldIndices[1] = new Point(3, 5);
 952:	          if ((style & SWT.SHORT) != 0) {
 953:            fieldNames[2] = Calendar.AM_PM;
 954:            fieldIndices[2] = new Point(6, 8);
 955:        } else {
 956:            fieldNames[2] = Calendar.SECOND;
 957:            fieldIndices[2] = new Point(6, 8);
 958:            fieldNames[3] = Calendar.AM_PM;
 959:            fieldIndices[3] = new Point(9, 11);
 960:        }
 961:    }
 962:}
 963:
 964:/**
 965: * Sets the receiver's hours.
 966: * <p>
 967: * Hours is an integer between 0 and 23.
 968: * </p>
 969: *
 970: * @param hours an integer between 0 and 23
 971: *
 972: * @exception SWTException <ul>
 973: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 974: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 975: * </ul>
 976: */
 977:	  public void setHours (int hours) {
 978:    checkWidget ();
 979:    if (!isValid(Calendar.HOUR_OF_DAY, hours)) return;
 980:    calendar.set(Calendar.HOUR_OF_DAY, hours);
 981:    updateControl();
 982:}
 983:
 984:/**
 985: * Sets the receiver's minutes.
 986: * <p>
 987: * Minutes is an integer between 0 and 59.
 988: * </p>
 989: *
 990: * @param minutes an integer between 0 and 59
 991: *
 992: * @exception SWTException <ul>
 993: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
 994: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
 995: * </ul>
 996: */
 997:	  public void setMinutes (int minutes) {
 998:    checkWidget ();
 999:    if (!isValid(Calendar.MINUTE, minutes)) return;
1000:    calendar.set(Calendar.MINUTE, minutes);
1001:    updateControl();
1002:}
1003:
1004:/**
1005: * Sets the receiver's month.
1006: * <p>
1007: * The first month of the year is 0, and the last month is 11.
1008: * </p>
1009: *
1010: * @param month an integer between 0 and 11
1011: *
1012: * @exception SWTException <ul>
1013: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1014: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1015: * </ul>
1016: */
1017:	  public void setMonth(int month) {
1018:    checkWidget();
1019:    if (!isValid(Calendar.MONTH, month)) return;
1020:    calendar.set(Calendar.MONTH, month);
1021:    updateControl();
1022:}
1023:
1024:/**
1025: * Sets the receiver's seconds.
1026: * <p>
1027: * Seconds is an integer between 0 and 59.
1028: * </p>
1029: *
1030: * @param seconds an integer between 0 and 59
1031: *
1032: * @exception SWTException <ul>
1033: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1034: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1035: * </ul>
1036: */
1037:	  public void setSeconds (int seconds) {
1038:    checkWidget ();
1039:    if (!isValid(Calendar.SECOND, seconds)) return;
1040:    calendar.set(Calendar.SECOND, seconds);
1041:    updateControl();
1042:}
1043:
1044:/**
1045: * Sets the receiver's year.
1046: * <p>
1047: * The first year is 1752 and the last year is 9999.
1048: * </p>
1049: *
1050: * @param year an integer between 1752 and 9999
1051: *
1052: * @exception SWTException <ul>
1053: *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1054: *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1055: * </ul>
1056: */
1057:	  public void setYear(int year) {
1058:    checkWidget();
1059:    //if (!isValid(Calendar.YEAR, year)) return;
1060:    if (year < MIN_YEAR || year > MAX_YEAR) return;
1061:    calendar.set(Calendar.YEAR, year);
1062:    updateControl();
1063:}
1064:
1065:	  int unformattedIntValue(int fieldName, String newText, boolean adjust, int max) {
1066:    int newValue;
1067:	      try {
1068:        newValue = Integer.parseInt(newText);
1069:    } catch (NumberFormatException ex) {
1070:        return -1;
1071:    }
1072:	      if (fieldName == Calendar.MONTH && adjust) {
1073:        newValue--;
1074:        if (newValue == -1) newValue = max;
1075:    }
1076:	      if (fieldName == Calendar.HOUR && adjust) {
1077:        if (newValue == 12) newValue = 0; // TODO: needs more work for setFormat and locale
1078:    }
1079:    return newValue;
1080:}
1081:
1082:	  public void updateControl() {
1083:	      if (text != null) {
1084:        String string = getFormattedString(style);
1085:        ignoreVerify = true;
1086:        text.setText(string);
1087:        ignoreVerify = false;
1088:    }
1089:    redraw();    
1090:}
1091:}