/*******************************************************************************
* Copyright (c) 2005, 2009 Eric Wuillai.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eric Wuillai (eric@wdev91.com) - initial API and implementation
*******************************************************************************/
package org.eclipse.nebula.widgets.datechooser;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.TimeZone;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TypedListener;
/**
* Calendar widget. Presents the monthly view of a calendar for date picking.<p>
*
* Calendar is composed of a header and a grid for date selection. The header
* display the current month and year, and the two buttons for navigation
* (previous and next month). An optional footer display the today date.<p>
*
* Features:
* <ul>
* <li>Month names, weekday names and first day of week depend of the locale
* set on the calendar</li>
* <li>GUI (colors, font...) are customizable through themes (3 provided)</li>
* <li>Shows days from adjacent months</li>
* <li>Optionally shows weeks numbers</li>
* <li>Optional footer showing today date</li>
* <li>Multi selection and interval selection</li>
* <li>Keyboard support.</li>
* </ul><p>
*
* To know which dates have been selected in the calendar, there is two means :
* <ul>
* <li>The <code>getSelectedDate()</code> method returns the currently
* selected date (single selection). The <code>getSelectedDates()</code>
* returns a list of all selected dates (multi selection).</li>
* <li>Add a <code>CalendarListener</code> to the calendar. This listener
* will be notified of selection. <code>event.data</code> contain the
* selection if in single selection mode.</li>
* </ul><p>
*
* Keyboard navigation :
* <ul>
* <li>Arrows: Change the focus cell.</li>
* <li>Page UP / DOWN: Next / previous month.</li>
* <li>Ctrl-Page UP / DOWN: Next / previous year.</li>
* <li>SPACE: Select the cell having the focus. If in multi selection mode,
* all previous selected dates are cleared.</li>
* <li>Ctrl-SPACE: Add the cell having the focus to the selection (multi
* selection mode).</li>
* <li>Shift-SPACE: Select all dates in the interval between the current
* focus and the last selected date.</li>
* <li>HOME: Set the focus on the today date, and select it if
* autoselectOnFooter is true.</li>
* </ul>
*/
public class DateChooser extends Composite {
/** Bundle name constant */
public static final String BUNDLE_NAME = "org.eclipse.nebula.widgets.datechooser.resources"; //$NON-NLS-1$
/** Header spacing constant */
protected static final int HEADER_SPACING = 3;
/** Value to use when there is none internal widget having the focus */
protected static final int NOFOCUS = -100;
public static final int GRID_NONE = 0;
public static final int GRID_LINES = 1;
public static final int GRID_FULL = 2;
// ----- Selection -----
/** Multi selection flag */
protected boolean multi;
/** Selection */
protected List selection;
/** Begin date of selection interval */
protected Date beginInterval;
/** End date of selection interval */
protected Date endInterval;
/** If true, the today date is automatically selected on footer selection event */
protected boolean autoSelectOnFooter = false;
// ----- Calendar month header -----
/** Month header panel */
protected Composite monthPanel;
/** Navigation button for previous month */
protected Button prevMonth;
/** Label for the display of current month and year (corresponding to curDate) */
protected Label currentMonth;
/** Navigation button for next month */
protected Button nextMonth;
/** Popup menu for month selection */
protected Menu monthsMenu;
// Calendar grid -----
/** Grid panel */
protected Composite gridPanel;
/** Layout of the grid */
protected DateChooserLayout gridLayout;
/** Panel for display of weekday names */
protected Composite headersPanel;
/** Grid headers, displaying weekday names */
protected Label[] headers;
/** Panel for display of day numbers */
protected Composite daysPanel;
/** Days numbers cells */
protected Cell[] days;
/** Panel for display of week numbers */
protected Composite weeksPanel;
/** Weeks numbers cells */
protected Cell[] weeks;
/** Index in the grid of the first day of displayed month */
protected int firstDayIndex;
// ----- Calendar footer -----
/** Today label of the footer */
protected Label todayLabel;
// ---- Localization -----
/** Locale used for localized names and formats */
protected Locale locale;
/** Format for the display of month and year in the header */
protected SimpleDateFormat df1;
/** Format for the today date in the footer */
protected DateFormat df2;
/** Index of the first day of week */
protected int firstDayOfWeek;
/** Minimal number of days in the first week */
protected int minimalDaysInFirstWeek;
/** Resources bundle */
protected ResourceBundle resources;
// ----- Dates -----
/** Date of 1st day of the currently displayed month */
protected Calendar currentMonthCal;
/** The today date */
protected Calendar todayCal;
// ----- GUI settings -----
/** Calendar theme */
protected DateChooserTheme theme;
/** Flag to set grid visible or not */
protected int gridVisible = GRID_FULL;
/** Flag to set weeks numbers visible or not */
protected boolean weeksVisible = false;
/** Flag to set footer visible or not */
protected boolean footerVisible = false;
/** Flag to set navigation enabled or not */
protected boolean navigationEnabled = true;
/** If true, change the current month if an adjacent day is clicked */
protected boolean autoChangeOnAdjacent = true;
// ----- GUI interactions -----
/** Listener for all internal events */
protected Listener listener;
/** Listener for external events */
protected Listener filter;
/** Flag indicating if the calendar has the focus */
protected boolean hasFocus = false;
/** Index of the focus in the days numbers grid */
protected int focusIndex = NOFOCUS;
private int redraw = 0;
/**
* Defines a grid cell. Each cell displays a day or week number.
*/
protected class Cell {
Label label;
int index;
Calendar cal;
boolean weekend;
int adjacent;
boolean selected;
boolean today;
Cell(Composite parent, int idx) {
label = new Label(parent, SWT.CENTER);
index = idx;
label.addListener(SWT.MouseDown, listener);
label.setData(this);
}
}
/**
* Calendar grid specific layout.
*/
protected class DateChooserLayout extends Layout {
private Point gridPanelSize = new Point(0, 0);
private Point headersPanelSize = new Point(0, 0);
private Point weeksPanelSize = new Point(0, 0);
private Point daysPanelSize = new Point(0, 0);
private Point todayLabelSize = new Point(0, 0);
private int cellWidth = 0;
private int cellHeight = 0;
protected void compute() {
GC gc = new GC(days[0].label);
int headerWidth = 0;
String[] months = df1.getDateFormatSymbols().getMonths();
for (int i = 0; i < months.length; i++) {
headerWidth = Math.max(headerWidth, gc.textExtent(months[i]).x);
}
headerWidth += prevMonth.computeSize(SWT.DEFAULT, SWT.DEFAULT, false).x * 2
+ HEADER_SPACING * 4 + gc.textExtent(" 9999").x; //$NON-NLS-1$
cellWidth = gc.textExtent("99").x + theme.cellPadding * 2;
cellWidth = Math.max(cellWidth, (headerWidth - 8) / 7 + 1);
cellHeight = days[0].label.computeSize(SWT.DEFAULT, SWT.DEFAULT, false).y;
weeksPanel.setVisible(weeksVisible);
headers[0].setVisible(weeksVisible);
if ( weeksVisible ) {
gridPanelSize.x = (cellWidth + 1) * 8 + 1;
weeksPanelSize.x = cellWidth + 1;
weeksPanelSize.y = (cellHeight + 1) * 6 - 1;
} else {
gridPanelSize.x = (cellWidth + 1) * 7 + 1;
headersPanelSize.x += 1;
}
headersPanelSize.x = gridPanelSize.x;
headersPanelSize.y = cellHeight + 1;
daysPanelSize.x = headersPanelSize.x;
daysPanelSize.y = (cellHeight + 1) * 6 - 1;
gridPanelSize.y = headersPanelSize.y + daysPanelSize.y + 2;
todayLabel.setVisible(footerVisible);
if ( footerVisible ) {
todayLabelSize.x = gridPanelSize.x;
todayLabelSize.y = headersPanelSize.y + 1;
gridPanelSize.y += todayLabelSize.y;
}
gc.dispose();
}
protected Point computeSize(Composite composite, int wHint, int hHint,
boolean flushCache) {
if ( flushCache ) compute();
return gridPanelSize;
}
protected void layout(Composite composite, boolean flushCache) {
if ( flushCache ) compute();
headersPanel.setBounds(0, 0, headersPanelSize.x, headersPanelSize.y);
if ( weeksVisible ) {
for (int i = 0; i < 8; i++) {
headers[i].setBounds((cellWidth + 1) * i + 1, 1, cellWidth, cellHeight);
}
weeksPanel.setBounds(0, headersPanelSize.y + 1, weeksPanelSize.x, weeksPanelSize.y);
for (int i = 0; i < 6; i++) {
weeks[i].label.setBounds(1, (cellHeight + 1) * i, cellWidth, cellHeight);
}
} else {
for (int i = 0; i < 7; i++) {
headers[i + 1].setBounds((cellWidth + 1) * i + 1, 1, cellWidth, cellHeight);
}
}
int x = weeksVisible ? 0 : 1;
int px = weeksVisible ? weeksPanelSize.x + 1 : 0;
daysPanel.setBounds(px, headersPanelSize.y + 1, daysPanelSize.x, daysPanelSize.y);
for (int r = 0; r < 6; r++) {
for (int c = 0; c < 7; c++) {
days[r * 7 + c].label.setBounds(x + (cellWidth + 1) * c, (cellHeight + 1) * r, cellWidth, cellHeight);
}
}
todayLabel.setBounds(0, headersPanelSize.y + daysPanelSize.y + 2, todayLabelSize.x, todayLabelSize.y);
}
}
/**
* Constructs a new instance of this class given its parent and a style value
* describing its behavior and appearance.<p>
* The calendar is initialized by default with the default Locale, and the
* current date for today and selected date attributes.
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
*/
public DateChooser(Composite parent, int style) {
super(parent, style);
multi = (style & SWT.MULTI) > 0;
selection = new ArrayList();
createContent();
setLocale(Locale.getDefault());
setTheme(DateChooserTheme.getDefaultTheme());
setTodayDate(new Date());
setCurrentMonth(todayCal.getTime());
computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
}
/**
* Adds the listener to the collection of listeners who will
* be notified when the receiver's selection changes, by sending
* it one of the messages defined in the <code>SelectionListener</code>
* interface.
* <p>
* <code>widgetSelected</code> is called when the dates selection changes.
* </p>
*
* @param lsnr the listener which should be notified
* @see SelectionListener
* @see #removeSelectionListener
*/
public void addSelectionListener(SelectionListener lsnr) {
checkWidget();
if ( lsnr == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
TypedListener typedListener = new TypedListener(lsnr);
addListener(SWT.Selection, typedListener);
}
/**
* Manages navigation buttons events.
*
* @param event event
*/
protected void buttonsEvent(Event event) {
switch ( event.type ) {
case SWT.MouseUp : {
Rectangle r = ((Control) event.widget).getBounds();
if ( event.x < 0 || event.x >= r.width || event.y < 0
|| event.y >= r.height ) return;
boolean ctrl = (event.stateMask & SWT.CTRL) != 0;
if ( event.widget == prevMonth ) {
changeCurrentMonth(ctrl ? -12 : -1);
} else if ( event.widget == nextMonth ) {
changeCurrentMonth(ctrl ? 12 : 1);
}
break;
}
case SWT.FocusIn :
handleFocus(event.type);
break;
}
}
/**
* Manages event at the calendar level.
*
* @param event event
*/
protected void calendarEvent(Event event) {
switch ( event.type ) {
case SWT.Traverse :
switch (event.detail) {
case SWT.TRAVERSE_ARROW_NEXT :
case SWT.TRAVERSE_ARROW_PREVIOUS :
case SWT.TRAVERSE_PAGE_NEXT :
case SWT.TRAVERSE_PAGE_PREVIOUS :
event.doit = false;
break;
default :
event.doit = true;
}
break;
case SWT.FocusIn :
handleFocus(event.type);
break;
case SWT.KeyDown : {
boolean ctrl = (event.stateMask & SWT.CTRL) != 0;
switch ( event.keyCode ) {
case SWT.ARROW_LEFT :
if ( event.stateMask != 0 ) return;
setFocus(focusIndex - 1);
break;
case SWT.ARROW_RIGHT :
if ( event.stateMask != 0 ) return;
setFocus(focusIndex + 1);
break;
case SWT.ARROW_UP :
if ( event.stateMask != 0 ) return;
setFocus(focusIndex - 7);
break;
case SWT.ARROW_DOWN :
if ( event.stateMask != 0 ) return;
setFocus(focusIndex + 7);
break;
case SWT.PAGE_DOWN :
if ( event.stateMask != 0 || ! navigationEnabled ) return;
changeCurrentMonth(ctrl ? 12 : 1);
break;
case SWT.PAGE_UP :
if ( event.stateMask != 0 || ! navigationEnabled ) return;
changeCurrentMonth(ctrl ? -12 : -1);
break;
case ' ' :
select(focusIndex, event.stateMask);
break;
case SWT.HOME :
if ( event.stateMask != 0 ) return;
setFocusOnToday(autoSelectOnFooter);
break;
default :
return;
}
if ( hasFocus ) {
gridRedraw();
}
break;
}
case SWT.Dispose : {
Display display = getDisplay();
display.removeFilter(SWT.FocusIn, filter);
display.removeFilter(SWT.KeyDown, filter);
hasFocus = false;
break;
}
}
}
/**
* Displays a new month in the grid. The new month is specified by delta from
* the currently displayed one.
*
* @param add delta from the current month
*/
protected void changeCurrentMonth(int add) {
if ( add == 0 ) {
return;
}
currentMonthCal.add(Calendar.MONTH, add);
refreshDisplay();
}
/**
* Clears the selection.
*/
public void clearSelection() {
clearSelection(true);
}
/**
* Clears the selection. The refresh flag allows to indicate must be
* refreshed or not.
*
* @param refresh true to refresh display, else false
*/
protected void clearSelection(boolean refresh) {
selection.clear();
for (int i = 0; i < days.length; i++) {
days[i].selected = false;
}
beginInterval = null;
if ( refresh ) refreshDisplay();
}
/**
* Constructs and initializes all the GUI of the calendar.
*/
private void createContent() {
GridLayout layout = new GridLayout(1, false);
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
super.setLayout(layout);
listener = new Listener() {
public void handleEvent(Event event) {
if ( event.type == SWT.MouseDown && ! hasFocus ) {
setFocus();
return;
}
if ( DateChooser.this == event.widget && event.type != SWT.KeyDown ) {
calendarEvent(event);
} else if ( prevMonth == event.widget || nextMonth == event.widget ) {
buttonsEvent(event);
} else if ( todayLabel == event.widget ) {
footerEvent(event);
} else if ( daysPanel == event.widget || gridPanel == event.widget || event.widget instanceof Label ) {
gridEvent(event);
} else if ( monthsMenu == event.widget || event.widget instanceof MenuItem ) {
menuEvent(event);
}
}
};
filter = new Listener() {
public void handleEvent(Event event) {
switch ( event.type ) {
case SWT.FocusIn :
handleFocus(SWT.FocusOut);
break;
case SWT.KeyDown :
calendarEvent(event);
break;
}
}
};
createHeader();
createGrid();
addListener(SWT.Dispose, listener);
addListener(SWT.Traverse, listener);
addListener(SWT.KeyDown, listener);
addListener(SWT.FocusIn, listener);
}
/**
* Creates the grid of labels displaying the days numbers. The grid is composed
* of a header row, displaying days initials, and 6 row for the days numbers.
* All labels are empty (no text displayed), the content depending of both the
* locale (for first day of week) and the current displayed month.
*/
private void createGrid() {
// Master grid panel
gridPanel = new Composite(this, SWT.NONE);
gridLayout = new DateChooserLayout();
gridPanel.setLayout(gridLayout);
gridPanel.addListener(SWT.Paint, listener);
// Weeks numbers panel
weeksPanel = new Composite(gridPanel, SWT.NONE);
weeks = new Cell[6];
for (int i = 0; i < 6; i++) {
weeks[i] = new Cell(weeksPanel, i);
}
// Grid header panel
headersPanel = new Composite(gridPanel, SWT.NONE);
headers = new Label[8];
for (int i = 0; i < 8; i++) {
headers[i] = new Label(headersPanel, SWT.CENTER);
headers[i].addListener(SWT.MouseDown, listener);
}
// Grid panel
daysPanel = new Composite(gridPanel, SWT.NONE);
days = new Cell[42];
for (int i = 0; i < 42; i++) {
days[i] = new Cell(daysPanel, i);
days[i].label.addListener(SWT.MouseUp, listener);
}
daysPanel.addListener(SWT.Paint, listener);
// Footer panel
todayLabel = new Label(gridPanel, SWT.CENTER);
todayLabel.addListener(SWT.MouseDoubleClick, listener);
todayLabel.addListener(SWT.MouseDown, listener);
}
/**
* Creates the header of the calendar. The header contains the label
* displaying the current month and year, and the two buttons for navigation :
* previous and next month.
*/
private void createHeader() {
monthPanel = new Composite(this, SWT.NONE);
GridLayoutFactory.fillDefaults().numColumns(3).spacing(HEADER_SPACING, 0)
.margins(HEADER_SPACING, 2).applyTo(monthPanel);
GridDataFactory.fillDefaults().applyTo(monthPanel);
monthPanel.addListener(SWT.MouseDown, listener);
prevMonth = new Button(monthPanel, SWT.ARROW | SWT.LEFT | SWT.FLAT);
prevMonth.addListener(SWT.MouseUp, listener);
prevMonth.addListener(SWT.FocusIn, listener);
currentMonth = new Label(monthPanel, SWT.CENTER);
currentMonth.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
currentMonth.addListener(SWT.MouseDown, listener);
nextMonth = new Button(monthPanel, SWT.ARROW | SWT.RIGHT | SWT.FLAT);
nextMonth.addListener(SWT.MouseUp, listener);
nextMonth.addListener(SWT.FocusIn, listener);
monthsMenu = new Menu(getShell(), SWT.POP_UP);
currentMonth.setMenu(monthsMenu);
for (int i = 0; i < 12; i++) {
MenuItem item = new MenuItem(monthsMenu, SWT.PUSH);
item.addListener(SWT.Selection, listener);
item.setData(new Integer(i));
}
monthsMenu.addListener(SWT.Show, listener);
}
/**
* Disposes of the operating system resources associated with the receiver
* and all its descendants.
*
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
public void dispose() {
getDisplay().removeFilter(SWT.KeyDown, filter);
getDisplay().removeFilter(SWT.FocusIn, filter);
super.dispose();
}
/**
* Manages events on the footer label.
*
* @param event event
*/
protected void footerEvent(Event event) {
switch ( event.type ) {
case SWT.MouseDoubleClick :
setFocusOnToday(autoSelectOnFooter);
break;
}
}
/**
* Forces the receiver to have the keyboard focus, causing all keyboard events
* to be delivered to it.
*
* @return <code>true</code> if the control got focus, and <code>false</code> if it was unable to.
*/
public boolean forceFocus() {
checkWidget();
if ( super.forceFocus() ) {
handleFocus(SWT.FocusIn);
return true;
}
return false;
}
/**
* Returns the cell index corresponding to the given label.
*
* @param label label
* @return cell index
*/
private int getCellIndex(Label label) {
for (int i = 0; i < days.length; i++) {
if ( days[i].label == label ) return i;
}
return -1;
}
/**
* Returns the current displayed month.
*
* @return Date representing current month.
*/
public Date getCurrentMonth() {
checkWidget();
return currentMonthCal.getTime();
}
/**
* Gets what the first day of the week is.
*
* @return the first day of the week.
*/
public int getFirstDayOfWeek() {
return this.firstDayOfWeek;
}
/**
* Returns the grid visibility status.
*
* @return Returns the grid visible status.
*/
public int getGridVisible() {
checkWidget();
return gridVisible;
}
/**
* Gets what the minimal days required in the first week of the year are.
*
* @return the minimal days required in the first week of the year.
*/
public int getMinimalDaysInFirstWeek() {
return this.minimalDaysInFirstWeek;
}
/**
* Returns the selected date. If calendar is in multi selection mode, the
* first item of selection list is returned, with no guaranty of the selection
* order by the user.
* If no selection, return <code>null</code>.
*
* @return selected date
*/
public Date getSelectedDate() {
checkWidget();
return selection.isEmpty() ? null : (Date) selection.get(0);
}
/**
* Returns all the selected dates. The collection returned is a copy of the
* internal selection list.<p>
*
* If the calendar is in single selection mode, it is preferable to use
* <code>getSelectedDate</code> that returns a Date value.
*
* @return Collection of selected dates
*/
public Collection getSelectedDates() {
checkWidget();
List returnSelection = new ArrayList(selection.size());
for (Iterator it = selection.iterator(); it.hasNext();) {
Date d = (Date) it.next();
if ( ! returnSelection.contains(d) ) {
returnSelection.add(d);
}
}
return returnSelection;
}
/**
* Returns the today date.
*
* @return today date
*/
public Date getTodayDate() {
checkWidget();
return todayCal.getTime();
}
/**
* Manages events at the grid level.
*
* @param event event
*/
protected void gridEvent(Event event) {
switch ( event.type ) {
case SWT.MouseUp : {
Rectangle r = ((Control) event.widget).getBounds();
if ( event.x < 0 || event.x >= r.width
|| event.y < 0 || event.y >= r.height ) return;
setFocus(getCellIndex((Label) event.widget));
select(focusIndex, event.stateMask);
break;
}
case SWT.Paint : {
if ( ! hasFocus ) return;
if ( focusIndex < 0 ) setFocus(-1);
// Draw the focus rectangle on the grid
Rectangle r = days[focusIndex].label.getBounds();
if ( daysPanel == event.widget ) {
event.gc.setLineWidth(1);
event.gc.setForeground(theme.focusColor);
event.gc.drawRectangle(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
} else if ( gridPanel == event.widget ) {
int line = focusIndex / 7;
int col = focusIndex % 7;
if ( line == 0 || line == 5 || (col == 0 && weeksVisible) ) {
Rectangle rg = daysPanel.getBounds();
event.gc.setForeground(theme.focusColor);
if ( line == 0 ) {
event.gc.drawLine(rg.x + r.x - 1, rg.y - 1, rg.x + r.x + r.width, rg.y - 1);
} else if ( line == 5 ) {
event.gc.drawLine(rg.x + r.x - 1, rg.y + rg.height, rg.x + r.x + r.width, rg.y + rg.height);
}
if ( col == 0 && weeksVisible ) {
event.gc.drawLine(rg.x + r.x - 1, rg.y + r.y - 1, rg.x + r.x - 1, rg.y + r.y + r.height);
}
}
}
break;
}
}
}
/**
* Redraw the grid panel and all its children (day panel and labels).
*/
private void gridRedraw() {
Rectangle r = gridPanel.getBounds();
gridPanel.redraw(0, 0, r.width, r.height, true);
}
/**
* Handles the focus.
*
* @param mode SWT.FocusIn or SWT.FocusOut
*/
private void handleFocus(int mode) {
switch ( mode ) {
case SWT.FocusIn : {
if ( hasFocus ) return;
hasFocus = true;
Display display = getDisplay ();
display.removeFilter(SWT.KeyDown, filter);
display.removeFilter(SWT.FocusIn, filter);
if ( focusIndex < 0 ) {
setFocus(NOFOCUS);
}
notifyListeners(SWT.FocusIn, new Event());
display.addFilter(SWT.FocusIn, filter);
display.addFilter(SWT.KeyDown, filter);
break;
}
case SWT.FocusOut : {
if ( ! hasFocus ) return;
Control focusControl = getDisplay().getFocusControl();
if ( focusControl == DateChooser.this
|| focusControl == nextMonth
|| focusControl == prevMonth ) return;
hasFocus = false;
getDisplay().removeFilter(SWT.KeyDown, filter);
getDisplay().removeFilter(SWT.FocusIn, filter);
notifyListeners(SWT.FocusOut, new Event());
break;
}
}
gridRedraw();
}
/**
* Returns the autoChangeOnAdjacent mode.
*
* @return true / false
*/
public boolean isAutoChangeOnAdjacent() {
return this.autoChangeOnAdjacent;
}
/**
* Returns the autoSelectOnFooter mode.
*
* @return true / false
*/
public boolean isAutoSelectOnFooter() {
checkWidget();
return autoSelectOnFooter;
}
/**
* Returns <code>true</code> if the given date is selected, else returns
* <code>false</code>.
*
* @param date
* @return <code>true</code> if selected, else <code>false</code>.
*/
public boolean isDateSelected(Date date) {
checkWidget();
for (Iterator it = selection.iterator(); it.hasNext();) {
if ( ((Date) it.next()).equals(date) ) return true;
}
return false;
}
/**
* Returns <code>true</code> if the receiver has the user-interface focus,
* and <code>false</code> otherwise.
*
* @return the receiver's focus state
* @see org.eclipse.swt.widgets.Control#isFocusControl()
*/
public boolean isFocusControl() {
return hasFocus;
}
/**
* Returns true if footer is visible.
*
* @return <code>true</code> if footer visible, else <code>false</code>
*/
public boolean isFooterVisible() {
checkWidget();
return footerVisible;
}
/**
* Returns true if grid is visible in the calendar popup.
*
* @return Returns the grid visible status.
* @deprecated
*/
public boolean isGridVisible() {
checkWidget();
return gridVisible == GRID_FULL;
}
/**
* Returns true if navigation is enabled. If false, buttons are not visible.
*
* @return Returns the navigation status.
*/
public boolean isNavigationEnabled() {
checkWidget();
return navigationEnabled;
}
/**
* Returns true if weeks numbers are visible.
*
* @return Returns the weeks numbers visible status.
*/
public boolean isWeeksVisible() {
checkWidget();
return weeksVisible;
}
/**
* Manages all events of the contextual menu on the month label of the header.
*
* @param event event
*/
protected void menuEvent(Event event) {
switch ( event.type ) {
case SWT.Show :
monthsMenu.setDefaultItem(monthsMenu.getItems()[currentMonthCal.get(Calendar.MONTH)]);
break;
case SWT.Selection :
currentMonthCal.set(Calendar.MONTH, ((Integer) event.widget.getData()).intValue());
refreshDisplay();
break;
}
}
/**
* Sends selection event to the listeners.
*/
protected void notifySelection() {
Event event = new Event();
if ( ! multi ) event.data = getSelectedDate();
notifyListeners(SWT.Selection, event);
}
private void redrawDec() {
redraw--;
if ( redraw == 0 ) setRedraw(true);
}
private void redrawInc() {
if ( redraw == 0 ) setRedraw(false);
redraw++;
}
/**
* Refreshes the display of the grid and header. This can be needed because
* of a month display, a locale or color model change.
*/
private void refreshDisplay() {
if ( currentMonthCal == null || theme == null ) return;
redrawInc();
currentMonth.setText(df1.format(currentMonthCal.getTime()));
int maxDay = currentMonthCal.getActualMaximum(Calendar.DAY_OF_MONTH);
Calendar cal = (Calendar) currentMonthCal.clone();
int delta = -((cal.get(Calendar.DAY_OF_WEEK) - firstDayOfWeek + 7) % 7);
cal.add(Calendar.DAY_OF_MONTH, delta);
for (int i = 0; i < 42; i++) {
if ( i % 7 == 0 ) {
int w = cal.get(Calendar.WEEK_OF_YEAR);
weeks[i / 7].label.setText(w < 10 ? "0" + w : "" + w);
}
if ( delta == 0 ) {
firstDayIndex = i;
}
int weekDay = cal.get(Calendar.DAY_OF_WEEK);
days[i].weekend = weekDay == 1 || weekDay == 7;
days[i].adjacent = delta < 0 ? -1 : (delta >= maxDay ? 1 : 0);
days[i].today = cal.equals(todayCal);
if ( days[i].cal == null ) {
days[i].cal = (Calendar) cal.clone();
} else {
days[i].cal.setTimeInMillis(cal.getTimeInMillis());
}
days[i].label.setText("" + days[i].cal.get(Calendar.DAY_OF_MONTH)); //$NON-NLS-1$
days[i].selected = isDateSelected(cal.getTime());
setCellColors(days[i]);
cal.add(Calendar.DAY_OF_MONTH, 1);
delta++;
}
redrawDec();
}
/**
* Removes the given date from the selection.
*
* @param d date to remove
*/
public void removeSelectedDate(Date d) {
checkWidget();
removeSelectedDate(d, true);
}
/**
* Removes the given date from the selection. The refresh flag allows to
* indicate must be refreshed or not.
*
* @param d date to remove
* @param refresh true to refresh display, else false
*/
private void removeSelectedDate(Date d, boolean refresh) {
for (Iterator it = selection.iterator(); it.hasNext();) {
Date itDate = (Date) it.next();
if ( itDate.equals(d) ) {
selection.remove(itDate);
break;
}
}
if ( refresh ) refreshDisplay();
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the receiver's selection changes.
*
* @param lsnr the listener which should no longer be notified
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener(SelectionListener lsnr) {
checkWidget();
if ( lsnr == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
removeListener(SWT.Selection, lsnr);
}
/**
* Manages the selection based on the current selected cell, specified by
* index, and the keyboard mask.
*
* @param index index of selected cell
* @param stateMask keyboard state
*/
private void select(int index, int stateMask) {
Cell cell = days[index];
if ( ! navigationEnabled && cell.adjacent != 0 ) return;
boolean ctrl = (stateMask & SWT.CTRL) != 0;
boolean shift = (stateMask & SWT.SHIFT) != 0;
if ( shift && beginInterval == null ) {
ctrl = true;
shift = false;
}
if ( ! multi || (! ctrl && ! shift) ) clearSelection(false);
Date selectedDate = cell.cal.getTime();
if ( multi && ctrl && cell.selected ) {
// Remove the selection on a single cell
removeSelectedDate(selectedDate, false);
beginInterval = null;
endInterval = null;
} else {
if ( multi && shift ) {
// Interval selection
Calendar c = (Calendar) cell.cal.clone();
Date d;
int delta;
// Clear the previous interval
if ( endInterval != null ) {
delta = endInterval.after(beginInterval) ? -1 : 1;
c.setTime(endInterval);
d = c.getTime();
while ( d.compareTo(beginInterval) != 0 ) {
removeSelectedDate(d, false);
c.add(Calendar.DAY_OF_MONTH, delta);
d = c.getTime();
}
}
// Select the new interval
endInterval = selectedDate;
delta = endInterval.after(beginInterval) ? -1 : 1;
c.setTime(endInterval);
d = c.getTime();
while ( d.compareTo(beginInterval) != 0 ) {
selection.add(d);
c.add(Calendar.DAY_OF_MONTH, delta);
d = c.getTime();
}
} else {
// Single selection
selection.add(selectedDate);
beginInterval = cell.cal.getTime();
endInterval = null;
}
}
// Changes the displayed month if an adjacent day has been selected
if ( cell.adjacent != 0 && autoChangeOnAdjacent ) {
changeCurrentMonth(cell.adjacent);
} else {
refreshDisplay();
}
notifySelection();
}
/**
* Sets to <code>true</code> to enable the automatic change of current month
* when an adjacent day is clicked in the grid.<p>
* This mode is <code>true</code> by default.
*
* @param autoChangeOnAdjacent true / false
*/
public void setAutoChangeOnAdjacent(boolean autoChangeOnAdjacent) {
this.autoChangeOnAdjacent = autoChangeOnAdjacent;
}
/**
* Set the autoSelectOnFooter mode. If true, the today date is automatically
* selected on the footer selection event.
* This mode is <code>false</code> by default.
*
* @param autoselectOnFooter true /false
*/
public void setAutoSelectOnFooter(boolean autoselectOnFooter) {
checkWidget();
this.autoSelectOnFooter = autoselectOnFooter;
}
/**
* Sets the colors of a grid cell in function of its current state.
*
* @param cell grid cell
*/
private void setCellColors(Cell cell) {
if ( cell.selected ) {
cell.label.setBackground(theme.selectedBackground);
cell.label.setForeground(theme.selectedForeground);
} else if ( cell.today ) {
cell.label.setBackground(theme.todayBackground);
cell.label.setForeground(theme.todayForeground);
} else {
cell.label.setBackground(theme.dayCellBackground);
cell.label.setForeground(cell.adjacent != 0
? theme.extraMonthForeground
: (cell.weekend
? theme.weekendForeground
: theme.dayCellForeground));
}
}
/**
* Sets a new month to display.
*
* @param month New month
*/
public void setCurrentMonth(Date month) {
checkWidget();
if ( month == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
if ( currentMonthCal == null ) {
currentMonthCal = Calendar.getInstance(locale);
currentMonthCal.setFirstDayOfWeek(firstDayOfWeek);
currentMonthCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
currentMonthCal.setTime(month);
trunc(currentMonthCal);
currentMonthCal.set(Calendar.DAY_OF_MONTH, 1);
refreshDisplay();
}
/**
* Sets what the first day of the week is.<p>
* This method allows to change the default first day of the week set
* from the locale. It must be called after <code>setLocale()</code>.
*
* @param firstDayOfWeek the given first day of the week.
*/
public void setFirstDayOfWeek(int firstDayOfWeek) {
this.firstDayOfWeek = firstDayOfWeek;
currentMonthCal.setFirstDayOfWeek(firstDayOfWeek);
todayCal.setFirstDayOfWeek(firstDayOfWeek);
}
/**
* Causes the receiver to have the <em>keyboard focus</em>,
* such that all keyboard events will be delivered to it.
*
* @return <code>true</code> if the control got focus, and <code>false</code> if it was unable to.
*/
public boolean setFocus() {
checkWidget();
if ( super.setFocus() ) {
handleFocus(SWT.FocusIn);
return true;
}
return false;
}
/**
* Sets the focus on the given cell, specified by index.
*
* @param index index of cell taking the focus
*/
private void setFocus(int index) {
if ( index == NOFOCUS ) {
if ( todayCal.get(Calendar.MONTH) == currentMonthCal.get(Calendar.MONTH)
&& todayCal.get(Calendar.YEAR) == currentMonthCal.get(Calendar.YEAR) ) {
for (int i = 0; i < days.length; i++) {
if ( days[i].today ) {
focusIndex = i;
return;
}
}
}
for (int i = 0; i < days.length; i++) {
if ( days[i].cal.get(Calendar.DAY_OF_MONTH) == 1 ) {
focusIndex = i;
return;
}
}
}
if ( index < 0 ) {
if ( ! navigationEnabled ) return;
changeCurrentMonth(-1);
focusIndex = index + 42;
} else if ( index >= 42 ) {
if ( ! navigationEnabled ) return;
changeCurrentMonth(1);
focusIndex = index - 42;
} else {
focusIndex = index;
}
}
/**
* Sets the focus on the given date. The current displayed month is changed
* if necessary.
*
* @param date date to set the focus on
*/
public void setFocusOnDate(Date date) {
Calendar dateCal = (Calendar) currentMonthCal.clone();
dateCal.setTime(date);
if ( dateCal.get(Calendar.MONTH) != currentMonthCal.get(Calendar.MONTH)
|| dateCal.get(Calendar.YEAR) != currentMonthCal.get(Calendar.YEAR) ) {
setCurrentMonth(date);
}
focusIndex = firstDayIndex + dateCal.get(Calendar.DAY_OF_MONTH) - 1;
}
/**
* Sets the focus on the today date. If autoselect is true, the today date
* is selected.
*
* @param autoselect true to select automatically the today date, else false
*/
public void setFocusOnToday(boolean autoselect) {
checkWidget();
redrawInc();
if ( currentMonthCal.get(Calendar.MONTH) != todayCal.get(Calendar.MONTH)
|| currentMonthCal.get(Calendar.YEAR) != todayCal.get(Calendar.YEAR) ) {
setCurrentMonth(todayCal.getTime());
}
setFocus(NOFOCUS);
if ( autoselect ) select(focusIndex, 0);
if ( isDisposed() ) return;
refreshDisplay();
redrawDec();
}
/**
* Sets the font that the receiver will use to paint textual information to
* the font specified by the argument, or to the default font for that kind
* of control if the argument is null.<p>
*
* The new font is applied to all elements (labels) composing the calendar.
* The width of cells is adjusted.
*
* @param font the new font (or null)
*/
public void setFont(Font font) {
checkWidget();
redrawInc();
super.setFont(font);
currentMonth.setFont(font);
for (int i = 0; i < headers.length; i++) {
headers[i].setFont(font);
}
for (int i = 0; i < days.length; i++) {
days[i].label.setFont(font);
}
for (int i = 0; i < weeks.length; i++) {
weeks[i].label.setFont(font);
}
todayLabel.setFont(font);
redrawDec();
}
/**
* Sets the footer visible or not. The footer displays the today date. It is
* not visible by default.
*
* @param footerVisible <code>true</code> to set footer visible, else <code>false</code>
*/
public void setFooterVisible(boolean footerVisible) {
checkWidget();
if ( footerVisible != this.footerVisible ) {
this.footerVisible = footerVisible;
layout(true);
}
}
/**
* Sets the grid visible or not in the calendar popup. By default, the grid
* is visible.
*
* @param gridVisible <code>true</code> to set grid visible, else <code>false</code>
* @deprecated
*/
public void setGridVisible(boolean gridVisible) {
setGridVisible(gridVisible ? GRID_FULL : GRID_NONE);
}
/**
* Sets the grid visible or not. By default, the grid is visible. The
* possible values are GRID_FULL, GRID_LINES and GRID_NONE.
*
* @param gridVisible grid visibility flag
*/
public void setGridVisible(int gridVisible) {
checkWidget();
this.gridVisible = gridVisible;
switch ( this.gridVisible ) {
case GRID_FULL :
gridPanel.setBackground(theme.gridLinesColor);
headersPanel.setBackground(theme.gridLinesColor);
daysPanel.setBackground(theme.gridLinesColor);
weeksPanel.setBackground(theme.gridLinesColor);
break;
case GRID_LINES :
gridPanel.setBackground(theme.gridLinesColor);
headersPanel.setBackground(theme.gridHeaderBackground);
daysPanel.setBackground(theme.dayCellBackground);
weeksPanel.setBackground(theme.gridHeaderBackground);
break;
case GRID_NONE :
gridPanel.setBackground(theme.gridHeaderBackground);
headersPanel.setBackground(theme.gridHeaderBackground);
daysPanel.setBackground(theme.dayCellBackground);
weeksPanel.setBackground(theme.gridHeaderBackground);
break;
}
}
/**
* Sets the layout which is associated with the receiver to be
* the argument which may be null.
* <p>
* Note : No Layout can be set on this Control because it already
* manages the size and position of its children.
* </p>
*
* @param layout the receiver's new layout or null
*/
public void setLayout(Layout layout) {
checkWidget();
return;
}
/**
* Sets a new locale to use for calendar. Locale will define the names of
* months and days, and the first day of week.
*
* @param locale new locale (must not be null)
*/
public void setLocale(Locale locale) {
checkWidget();
if ( locale == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
this.locale = locale;
// Loads the resources
resources = ResourceBundle.getBundle(BUNDLE_NAME, locale);
prevMonth.setToolTipText(resources.getString("DateChooser.previousButton")); //$NON-NLS-1$
nextMonth.setToolTipText(resources.getString("DateChooser.nextButton")); //$NON-NLS-1$
// Defines formats
df1 = new SimpleDateFormat("MMMM yyyy", locale); //$NON-NLS-1$
df2 = DateFormat.getDateInstance(DateFormat.SHORT, locale);
Calendar c = Calendar.getInstance(TimeZone.getDefault(), locale);
firstDayOfWeek = c.getFirstDayOfWeek();
minimalDaysInFirstWeek = Integer.parseInt(resources.getString("minimalDaysInFirstWeek"));
if ( currentMonthCal != null ) {
currentMonthCal.setFirstDayOfWeek(firstDayOfWeek);
currentMonthCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
if ( todayCal != null ) {
todayCal.setFirstDayOfWeek(firstDayOfWeek);
todayCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
// Sets the header menu items
String[] months = df1.getDateFormatSymbols().getMonths();
MenuItem[] items = monthsMenu.getItems();
for (int i = 0; i < 12; i++) {
items[i].setText(months[i]);
}
// Sets the grid header initials
redrawInc();
DateFormatSymbols symboles = df1.getDateFormatSymbols();
String[] sn = symboles.getShortWeekdays();
String[] ln = symboles.getWeekdays();
int f = firstDayOfWeek;
for (int i = 1; i < headers.length; i++) {
headers[i].setText(sn[f].substring(0, 1).toUpperCase());
headers[i].setToolTipText(ln[f]);
f = (f % 7) + 1;
}
// Updates the footer
updateTodayLabel();
refreshDisplay();
redrawDec();
}
/**
* Sets what the minimal days required in the first week of the year are; For
* example, if the first week is defined as one that contains the first day
* of the first month of a year, call this method with value 1. If it must be
* a full week, use value 7.<p>
* This method allows to change the default value set from the locale.
* It must be called after <code>setLocale()</code>.
*
* @param minimalDaysInFirstWeek the given minimal days required in the first week of the year.
*/
public void setMinimalDaysInFirstWeek(int minimalDaysInFirstWeek) {
this.minimalDaysInFirstWeek = minimalDaysInFirstWeek;
currentMonthCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
todayCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
/**
* Sets the header's navigation buttons visible or not.
*
* @param navigationEnabled true if enabled, false else
*/
public void setNavigationEnabled(boolean navigationEnabled) {
checkWidget();
if ( navigationEnabled != this.navigationEnabled ) {
this.navigationEnabled = navigationEnabled;
prevMonth.setVisible(navigationEnabled);
nextMonth.setVisible(navigationEnabled);
if ( navigationEnabled ) {
currentMonth.setMenu(monthsMenu);
} else {
currentMonth.setMenu(null);
}
}
}
/**
* Sets the selected date. The grid is refreshed to display the corresponding
* month.
*
* @param date new selected date (must not be null)
*/
public void setSelectedDate(Date date) {
checkWidget();
if ( date == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
Calendar c = (Calendar) currentMonthCal.clone();
c.setTime(date);
trunc(c);
Date d = c.getTime();
if ( ! selection.contains(d) ) {
if ( ! multi ) clearSelection(false);
selection.add(d);
}
setCurrentMonth(d);
}
/**
* Sets the theme to apply to the calendar.
*
* @param theme new theme (must not be null)
*/
public void setTheme(DateChooserTheme theme) {
checkWidget();
if ( theme == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
this.theme = theme;
redrawInc();
// Border
this.setBackground(theme.borderBackground);
GridLayout layout = (GridLayout) this.getLayout();
layout.marginWidth = theme.borderSize;
layout.marginHeight = theme.borderSize;
setGridVisible(theme.gridVisible);
// Month header settings
monthPanel.setBackground(theme.headerBackground);
currentMonth.setBackground(theme.headerBackground);
currentMonth.setForeground(theme.headerForeground);
// Today footer settings
todayLabel.setBackground(theme.gridHeaderBackground);
todayLabel.setForeground(theme.gridHeaderForeground);
// Days headers settings
for (int i = 0; i < headers.length; i++) {
headers[i].setBackground(theme.gridHeaderBackground);
headers[i].setForeground(theme.gridHeaderForeground);
}
// Grid days cells settings
for (int i = 0; i < days.length; i++) {
days[i].label.setBackground(theme.dayCellBackground);
}
// Weeks cells settings
for (int i = 0; i < weeks.length; i++) {
weeks[i].label.setBackground(theme.gridHeaderBackground);
weeks[i].label.setForeground(theme.gridHeaderForeground);
}
// Font
setFont(theme.font);
pack(true);
layout(true);
refreshDisplay();
redrawDec();
}
/**
* Sets the today date.<p>
*
* By default the today date is initialized to the current system date. But it
* can be needed to adjust it for specifics needs.
*
* @param today today date (must not be null)
*/
public void setTodayDate(Date today) {
checkWidget();
if ( today == null ) SWT.error(SWT.ERROR_NULL_ARGUMENT);
if ( todayCal == null ) {
todayCal = Calendar.getInstance(locale);
todayCal.setFirstDayOfWeek(firstDayOfWeek);
todayCal.setMinimalDaysInFirstWeek(minimalDaysInFirstWeek);
}
todayCal.setTime(today);
trunc(todayCal);
updateTodayLabel();
refreshDisplay();
}
/**
* Sets the weeks numbers visible or not. By default, the weeks are NOT
* visible.
*
* @param weeksVisible <code>true</code> to set weeks visible, else <code>false</code>
*/
public void setWeeksVisible(boolean weeksVisible) {
checkWidget();
if ( weeksVisible != this.weeksVisible ) {
this.weeksVisible = weeksVisible;
layout(true);
}
}
/**
* Truncate a given <code>Calendar</code>. The time fields are all set to 0.
*
* @param cal Calendar
*/
private void trunc(Calendar cal) {
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
}
/**
* Updates the today label in the footer. Called when the today date or the
* locale is changed.
*/
private void updateTodayLabel() {
if ( todayCal != null ) {
todayLabel.setText(resources.getString("DateChooser.today")
+ " " + df2.format(todayCal.getTime()));
}
}
}