/****************************************************************************
* Copyright (c) 2006-2008 Jeremy Dowdall
* 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:
* Jeremy Dowdall <jeremyd@aspencloud.com> - initial API and implementation
* Wim Jongman - https://bugs.eclipse.org/bugs/show_bug.cgi?id=362181
* Scott Klein - https://bugs.eclipse.org/bugs/show_bug.cgi?id=370605
* Baruch Youssin - https://bugs.eclipse.org/bugs/show_bug.cgi?id=261414
* Doug Showell - https://bugs.eclipse.org/bugs/show_bug.cgi?id=383589
*****************************************************************************/
package org.eclipse.nebula.widgets.cdatetime;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.text.DateFormat.Field;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.nebula.cwt.base.BaseCombo;
import org.eclipse.nebula.cwt.v.VButton;
import org.eclipse.nebula.cwt.v.VCanvas;
import org.eclipse.nebula.cwt.v.VGridLayout;
import org.eclipse.nebula.cwt.v.VLabel;
import org.eclipse.nebula.cwt.v.VLayout;
import org.eclipse.nebula.cwt.v.VNative;
import org.eclipse.nebula.cwt.v.VPanel;
import org.eclipse.nebula.cwt.v.VTracker;
import org.eclipse.nebula.widgets.cdatetime.CDT.PickerPart;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
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.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Spinner;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.TypedListener;
/**
* The CDateTime provides both textual and graphical means selecting a date.<br/>
* As with other combo type widgets, there are three basic styles:
* <ul>
* <li>Text only (default)</li>
* <li>Graphical only (CDT.SIMPLE)</li>
* <li>Combo - a text selector with a drop-down graphical selector
* (CDT.DROP_DOWN)</li>
* </ul>
* <p>
* Styles are set using the constants provided in the CDT class.
* </p>
*
* @see CDT
*/
public class CDateTime extends BaseCombo {
/**
* A simple class used for editing a field numerically.
*/
private class EditField {
private String buffer;
private int digits;
private int count = 0;
EditField(int digits, int initialValue) {
this.digits = digits;
buffer = Integer.toString(initialValue);
}
/**
* Adds a character if it is a digit; in case the field exceeds its capacity, the oldest character is
* dropped from the buffer. Non-digits are dropped.
* @param c
* @return true if the new character is a digit and with its addition the active field
* reaches or exceeds its capacity, false otherwise
*/
boolean addChar(char c) {
if (Character.isDigit(c)) {
buffer = (count > 0) ? buffer : ""; //$NON-NLS-1$
buffer += String.valueOf(c);
if (buffer.length() > digits) {
buffer = buffer.substring(buffer.length() - digits,
buffer.length());
}
}
return (++count > (digits - 1));
}
int getValue() {
return Integer.parseInt(buffer);
}
void removeLastCharacter() {
if (buffer.length() > 0) {
buffer = buffer.substring(0, buffer.length() - 1);
count--;
}
}
void reset() {
count = 0;
}
public String toString() {
if (buffer.length() < digits) {
char[] ca = new char[digits - buffer.length()];
Arrays.fill(ca, '0');
buffer = String.valueOf(ca).concat(buffer);
}
return buffer;
}
}
/**
* The layout used for a "basic" CDateTime - when it is neither of style
* SIMPLE or DROP_DOWN - with style of SPINNER.<br>
* Note that there is a spinner, but no button for this style.
*/
class SpinnerLayout extends VLayout {
protected Point computeSize(VPanel panel, int wHint, int hHint,
boolean flushCache) {
Point size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT);
Rectangle sRect = spinner.getControl().computeTrim(0, 0, 0, 0);
int sWidth = sRect.x + sRect.width
- (2 * spinner.getControl().getBorderWidth()) + 1;
size.x += sWidth;
size.x++;
size.y += textMarginHeight;
if (wHint != SWT.DEFAULT) {
size.x = Math.min(size.x, wHint);
}
if (hHint != SWT.DEFAULT) {
size.y = Math.min(size.y, hHint);
}
return size;
}
protected void layout(VPanel panel, boolean flushCache) {
Rectangle cRect = panel.getClientArea();
if (cRect.isEmpty())
return;
Point tSize = text.getControl().computeSize(SWT.DEFAULT,
SWT.DEFAULT);
tSize.y += textMarginHeight;
spinner.setBounds(cRect.x, cRect.y, cRect.width, tSize.y);
Rectangle sRect = spinner.getControl().computeTrim(0, 0, 0, 0);
int sWidth = sRect.x + sRect.width
- (2 * spinner.getControl().getBorderWidth()) + 1;
tSize.x = cRect.width - sWidth;
text.setBounds(cRect.x, cRect.y + getBorderWidth(), tSize.x,
tSize.y);
}
}
private static final int FIELD_NONE = -1;
private static final int DISCARD = 0;
private static final int WRAP = 1;
private static final int BLOCK = 2;
private static int convertStyle(int style) {
int rstyle = SWT.NONE;
if ((style & CDT.DROP_DOWN) != 0) {
rstyle |= SWT.DROP_DOWN;
}
if ((style & CDT.SIMPLE) != 0) {
rstyle |= SWT.SIMPLE;
}
if ((style & CDT.READ_ONLY) != 0) {
rstyle |= SWT.READ_ONLY;
}
if ((style & CDT.BUTTON_LEFT) != 0) {
rstyle |= SWT.LEFT;
}
if ((style & CDT.TEXT_LEAD) != 0) {
rstyle |= SWT.LEAD;
}
if ((style & CDT.BORDER) != 0) {
rstyle |= SWT.BORDER;
}
if (win32) {
rstyle |= SWT.DOUBLE_BUFFERED;
}
return rstyle;
}
VPanel picker;
VNative<Spinner> spinner;
boolean internalFocusShift = false;
boolean rightClick = false;
private Date cancelDate;
private Calendar calendar;
private DateFormat df;
Locale locale;
TimeZone timezone;
Field[] field;
int activeField;
private boolean tabStops = false;
// Store these values so that the style can be reset automatically
// to update everything if/when the locale is changed
int style;
String pattern = null;
int format = -1;
private CDateTimePainter painter;
/**
* Delegates events to their appropriate handler
*/
Listener textListener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.FocusIn:
rightClick = false;
if (internalFocusShift) {
if (activeField < 0) {
fieldFirst();
updateText();
}
} else {
if (VTracker.getLastTraverse() == SWT.TRAVERSE_TAB_PREVIOUS) {
fieldLast();
} else {
fieldFirst();
}
updateText();
}
break;
case SWT.FocusOut:
if (!rightClick && !internalFocusShift) {
commitEditField();
updateText();
}
break;
case SWT.KeyDown:
handleKey(event);
break;
case SWT.MouseDown:
if (event.button == 1) {
fieldFromTextSelection();
} else if (event.button == 2) {
fieldNext();
} else if (event.button == 3) {
rightClick = true;
}
break;
case SWT.MouseWheel:
if (event.count > 0) {
fieldAdjust(1);
} else {
fieldAdjust(-1);
}
event.doit = false;
break;
case SWT.MouseUp:
if (event.button == 1) {
fieldFromTextSelection();
}
break;
case SWT.Traverse:
handleTraverse(event);
break;
case SWT.Verify:
verify(event);
break;
}
}
};
private Point textSelectionOffset = new Point(0, 0); // x = selOffset start,
// y = selOffset
// amount
private EditField editField;
private String[] separator;
private int[] calendarFields;
private boolean isTime;
private boolean isDate;
// private boolean isNull = true;
private String nullText = null;
private boolean defaultNullText = true;
private boolean singleSelection;
// private boolean dragSelection;
private Date[] selection = new Date[0];
private boolean scrollable = true;
CDateTimeBuilder builder;
VPanel pickerPanel;
private TimeZone[] allowedTimezones;
/**
* Constructs a new instance of this class given its parent and a style
* value describing its behavior and appearance. The current date and the
* system's default locale are used.
*
* @param parent
* a widget which will be the parent of the new instance (cannot
* be null)
* @param style
* the style of widget to construct
*/
public CDateTime(Composite parent, int style) {
super(parent, convertStyle(style));
init(style);
}
/**
* 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 selection (date/time)
* changes. <code>widgetDefaultSelected</code> is when ENTER is pressed the
* text box.
* </p>
* The event's data field will contain the newly selected Date object.<br>
* The event's detail field will contain which Calendar Field was changed
*
* @param listener
* the listener which should be notified
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener(SelectionListener listener) {
if (listener != null) {
TypedListener typedListener = new TypedListener(listener);
addListener(SWT.Selection, typedListener);
addListener(SWT.DefaultSelection, typedListener);
}
}
/**
* Adds the textListener for the appropriate SWT events to handle
* incrementing fields.
*/
protected void addTextListener() {
removeTextListener();
Text control = text.getControl();
control.addListener(SWT.FocusIn, textListener);
control.addListener(SWT.FocusOut, textListener);
control.addListener(SWT.KeyDown, textListener);
control.addListener(SWT.MouseDown, textListener);
control.addListener(SWT.MouseWheel, textListener);
control.addListener(SWT.MouseUp, textListener);
control.addListener(SWT.Verify, textListener);
text.addListener(SWT.Traverse, textListener);
}
/**
* If a field is being edited (via keyboard), set the edit value to the
* active field of the calendar. Reset the count of the EditField so that a
* subsequent key press will overwrite its contents;
*
* @return true if the commit was successfull (the value was valid for the
* field) or there was no commit to be made (editField is null),
* false otherwise
*/
private boolean commitEditField() {
if (editField != null) {
int cf = getCalendarField();
int val = editField.getValue();
editField.reset();
if (cf == Calendar.MONTH) {
val--;
}
return fieldSet(cf, val, DISCARD);
}
return true;
}
/**
* If style is neither SIMPLE or DROP_DOWN, then this method simply returns,
* otherwise it creates the picker.
*/
private void createPicker() {
if (isSimple()) {
pickerPanel = panel;
setContent(panel.getComposite());
} else if (isDropDown()) {
disposePicker();
Shell shell = getContentShell();
int style = (isSimple() ? SWT.NONE : SWT.BORDER)
| SWT.DOUBLE_BUFFERED;
VCanvas canvas = new VCanvas(shell, style);
pickerPanel = canvas.getPanel();
pickerPanel.setWidget(canvas);
VGridLayout layout = new VGridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.verticalSpacing = 1;
pickerPanel.setLayout(layout);
setContent(pickerPanel.getComposite());
canvas.addListener(SWT.KeyDown, new Listener() {
public void handleEvent(Event event) {
if (SWT.ESC == event.keyCode) {
event.doit = false;
if (selection.length > 0 && selection[0] != cancelDate) {
setSelection(cancelDate);
fireSelectionChanged();
}
setOpen(false);
}
}
});
if (field.length > 1 || isTime) {
createPickerToolbar(pickerPanel);
}
}
if (isDate) {
DatePicker dp = new DatePicker(this);
dp.setScrollable(scrollable);
dp.setFields(calendarFields);
dp.updateView();
picker = dp;
} else if (isTime) {
if ((style & CDT.CLOCK_DISCRETE) != 0) {
DiscreteTimePicker dtp = new DiscreteTimePicker(this);
dtp.setFields(calendarFields);
dtp.updateView();
picker = dtp;
} else {
AnalogTimePicker atp = new AnalogTimePicker(this);
atp.setFields(calendarFields);
atp.updateView();
picker = atp;
}
}
if (isDropDown()) {
picker.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
}
}
private void createPickerToolbar(VPanel parent) {
VPanel tb = new VPanel(parent, SWT.NONE);
VGridLayout layout = new VGridLayout(3, false);
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.horizontalSpacing = 2;
tb.setLayout(layout);
tb.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false));
tb.setData(CDT.PickerPart, PickerPart.Toolbar);
VButton b = new VButton(tb, SWT.OK | SWT.NO_FOCUS);
b.setData(CDT.PickerPart, PickerPart.OkButton);
b.setToolTipText(Resources.getString("accept.text", locale)); //$NON-NLS-1$
b.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
b.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
setOpen(false);
}
});
b = new VButton(tb, SWT.CANCEL | SWT.NO_FOCUS);
b.setData(CDT.PickerPart, PickerPart.CancelButton);
b.setToolTipText(Resources.getString("cancel.text", locale)); //$NON-NLS-1$
b.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
b.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
setSelection(cancelDate);
fireSelectionChanged();
setOpen(false);
}
});
b = new VButton(tb, SWT.NO_FOCUS);
b.setData(CDT.PickerPart, PickerPart.ClearButton);
b.setText(Resources.getString("clear.text", locale)); //$NON-NLS-1$
b.setToolTipText(Resources.getString("clear.text", locale)); //$NON-NLS-1$
b.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
b.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
setOpen(false);
setSelection(null);
fireSelectionChanged();
}
});
VLabel sep = new VLabel(parent, SWT.SEPARATOR | SWT.HORIZONTAL);
sep.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
}
// void deselect(Date date) {
// if(date != null && isSelected(date)) {
// Date[] tmp = new Date[selection.length - 1];
// for(int i = 0, j = 0; i < selection.length; i++) {
// if(!selection[i].equals(date)) {
// tmp[j++] = selection[i];
// }
// }
// setSelection(tmp);
// }
// }
//
// void deselectAll() {
// setSelectedDates((Date[]) null);
// }
private void disposePicker() {
if (content != null) {
if (picker != null) {
picker.dispose();
picker = null;
}
if (isDropDown()) {
Control c = content;
setContent(null);
c.dispose();
if (contentShell != null) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
if (contentShell != null
&& !contentShell.isDisposed()) {
contentShell.dispose();
contentShell = null;
}
}
});
}
}
}
}
/**
* Adds the given amount to the active field, if there is one
*/
void fieldAdjust(int amount) {
if (!hasSelection()) {
setSelection(calendar.getTime());
fireSelectionChanged();
} else {
int cf = getCalendarField();
if (cf >= 0) {
fieldRoll(cf, amount, WRAP);
}
}
}
void fieldFirst() {
// If the user has opted to have the user be able to
// change time zones then allow the next field to be the
// time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[0])) {
setActiveField(1);
} else {
setActiveField(0);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(0);
}
}
/**
* Sets the active field from the select of the text box
*/
void fieldFromTextSelection() {
if (!hasSelection()) {
// setActiveField(FIELD_ALL);
fieldNext();
} else {
Point sel = text.getControl().getSelection();
AttributedCharacterIterator aci = df
.formatToCharacterIterator(calendar.getTime());
if (sel.x > textSelectionOffset.x)
sel.x += textSelectionOffset.y;
aci.setIndex(sel.x);
Object[] oa = aci.getAttributes().keySet().toArray();
if (oa.length == 0 && sel.x > 0) {
sel.x -= 1;
aci.setIndex(sel.x);
oa = aci.getAttributes().keySet().toArray();
}
if (oa.length > 0) {
for (int i = 0; i < field.length; i++) {
if (oa[0].equals(field[i])) {
// If the user has opted to have the user be able to
// change time zones then allow the next field to be the
// time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET != getCalendarField(field[i])) {
setActiveField(i);
}
} else {
// allowed time zones have been set, so let the user
// edit it
setActiveField(i);
}
break;
}
}
updateText();
}
}
}
void fieldLast() {
// If the user has opted to have the user be able to change
// time zones then allow the next field to be the time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[field.length - 1])) {
setActiveField(field.length - 2);
} else {
setActiveField(field.length - 1);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(field.length - 1);
}
}
/**
* Sets the active field to the next field; wraps if necessary and sets to
* last field if there is no current active field
*/
void fieldNext() {
fieldNext(false);
}
/**
* Sets the active field to the next field; wraps if necessary and sets to
* last field if there is no current active field
*
* @param If
* true, the text update will be asynchronous (for changes to
* text selection)
*/
void fieldNext(boolean async) {
if (activeField >= 0 && activeField < field.length - 1) {
// If the user has opted to have the user be able to change
// time zones then allow the next field to be the time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[activeField + 1])) {
if (activeField < field.length - 2) {
setActiveField(activeField + 2);
} else {
setActiveField(0);
}
} else {
setActiveField(activeField + 1);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(activeField + 1);
}
} else {
// If the user has opted to have the user be able to change
// time zones then allow the next field to be the time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[0])) {
setActiveField(1);
} else {
setActiveField(0);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(0);
}
}
updateText(async);
}
/**
* Sets the active field to the previous field; wraps if necessary and sets
* to first field if there is no current active field
*/
private void fieldPrev() {
fieldPrev(false);
}
/**
* Sets the active field to the previous field; wraps if necessary and sets
* to first field if there is no current active field
*
* @param If
* true, the text update will be asynchronous (for changes to
* text selection)
*/
void fieldPrev(boolean async) {
if (activeField > 0 && activeField < field.length) {
// If the user has opted to have the user be able to change
// time zones then allow the next field to be the time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[activeField - 1])) {
if (activeField > 1) {
setActiveField(activeField - 2);
} else {
setActiveField(field.length - 1);
}
} else {
setActiveField(activeField - 1);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(activeField - 1);
}
} else {
// If the user has opted to have the user be able to change
// time zones then allow the next field to be the time zone field
if (this.allowedTimezones == null) {
if (Calendar.ZONE_OFFSET == getCalendarField(field[field.length - 1])) {
setActiveField(field.length - 2);
} else {
setActiveField(field.length - 1);
}
} else {
// allowed time zones have been set, so let the user edit it
setActiveField(field.length - 1);
}
}
updateText(async);
}
private boolean fieldRoll(final int calendarField, final int rollAmount,
final int style) {
if (!getEditable()) {
return false;
}
if (calendarField == Calendar.ZONE_OFFSET
&& this.allowedTimezones != null) {
boolean timeZoneSet = false;
for (int idx = 0; idx < this.allowedTimezones.length; idx++) {
TimeZone activeTimeZone = this.getTimeZone();
if (activeTimeZone.getID() == this.allowedTimezones[idx]
.getID()) {
if (rollAmount < 0) {
if (idx == 0) {
this.setTimeZone(this.allowedTimezones[this.allowedTimezones.length - 1]);
} else {
this.setTimeZone(this.allowedTimezones[idx - 1]);
}
} else if (rollAmount > 0) {
if (idx == this.allowedTimezones.length - 1) {
this.setTimeZone(this.allowedTimezones[0]);
} else {
this.setTimeZone(this.allowedTimezones[idx + 1]);
}
}
timeZoneSet = true;
break;
}
}
if (!timeZoneSet) {
this.setTimeZone(this.allowedTimezones[0]);
}
} else {
calendar.roll(calendarField, rollAmount);
}
if (selection.length > 0) {
selection[0] = calendar.getTime();
}
updateText();
updatePicker();
fireSelectionChanged(calendarField);
return true;
}
/**
* Sets the given calendar field to the given value.<br>
* <b>NOTE:</b> This is NOT the active field but a field in the "calendar"
* variable.
*
* @param calendarField
* the field of calendar to set
* @param value
* the value to set it to
* @param style
* the of set to perform; if the value is valid for the given
* calendarField then this has no affect, otherwise it will take
* an action according to this style int:
* <ul>
* <li>DISCARD: the value will be discarded and the method
* returns without performing and action</li>
* <li>WRAP: if value is higher than its maximum it will be set
* to its minimum, and visa versa</li>
* <li>BLOCK: if value is higher than its maximum it will be set
* to its maximum, and visa versa</li>
* </ul>
* @return true if the field was set, false otherwise (as is possible with a
* DISCARD style)
*/
private boolean fieldSet(int calendarField, int value, int style) {
if (!getEditable()) {
return false;
}
if (calendarField >= 0) {
if (value > calendar.getActualMaximum(calendarField)) {
if (style == DISCARD) {
return false;
} else if (style == WRAP) {
value = calendar.getActualMinimum(calendarField);
} else if (style == BLOCK) {
value = calendar.getActualMaximum(calendarField);
}
} else if (value < calendar.getActualMinimum(calendarField)) {
if (style == DISCARD) {
return false;
} else if (style == WRAP) {
value = calendar.getActualMaximum(calendarField);
} else if (style == BLOCK) {
value = calendar.getActualMinimum(calendarField);
}
}
calendar.set(calendarField, value);
if (selection.length > 0) {
selection[0] = calendar.getTime();
}
updateText();
updatePicker();
fireSelectionChanged(calendarField);
}
return true;
}
/**
* <p>
* Notifies listeners that the selection for this CDateTime has changed
* </p>
* <p>
* This will fire both a regular selection event, and a default selection
* event.
* </p>
* <p>
* The data field is populated by {@link #getSelectedDates()}.
* </p>
*/
void fireSelectionChanged() {
fireSelectionChanged(false);
}
void fireSelectionChanged(boolean defaultSelection) {
if (defaultSelection && isOpen()) {
setOpen(false);
}
Event event = new Event();
event.data = getSelection();
notifyListeners(SWT.Selection, event);
if (defaultSelection) {
notifyListeners(SWT.DefaultSelection, event);
}
}
/**
* <p>
* Notifies listeners that a field of the selected date for this CDateTime
* has changed
* </p>
* <p>
* Note that this is only valid when {@link #singleSelection} is true, and
* will only fire a regular selection event (not a default selection event)
* </p>
* <p>
* The data field is populated by {@link #getSelection()} and the detail
* field holds the field which was changed.
* </p>
*
* @param field
* the Calendar Field which caused the change, or -1 if
* <code>setTime</code> was called (thus setting all Calendar
* Fields)
*/
void fireSelectionChanged(int field) {
Event event = new Event();
event.data = getSelection();
event.detail = field;
if (this.field.length == 1) {
if (isOpen()) {
setOpen(false);
}
notifyListeners(SWT.Selection, event);
notifyListeners(SWT.DefaultSelection, event);
} else {
notifyListeners(SWT.Selection, event);
}
}
VButton getButtonWidget() {
return button;
}
/**
* Gets the calendar field corresponding to the active field, if there is
* one.
*
* @return an int representing the calendar field, -1 if there isn't one.
*/
int getCalendarField() {
return hasField(activeField) ? getCalendarField(field[activeField])
: -1;
}
int getCalendarField(Field field) {
int cf = field.getCalendarField();
if (cf < 0) {
if (field.toString().indexOf("hour 1") > -1) { //$NON-NLS-1$
cf = Calendar.HOUR;
} else if (field.toString().contains("zone")) { //$NON-NLS-1$
cf = Calendar.ZONE_OFFSET;
}
}
return cf;
}
Calendar getCalendarInstance() {
return getCalendarInstance(calendar.getTimeInMillis());
}
/**
* <p>
* <b>WARNING: Experimental API - this method may be removed in future
* versions</b>
* </p>
* Get a new instance of Calendar that is initialized with the timezone and
* locale of this CDateTime, and set to the given date.
*
* @param date
* the date that the Calendar will be set to, or null for the
* current system time
* @return a new instance of Calendar
*/
public Calendar getCalendarInstance(Date date) {
if (date == null) {
return getCalendarInstance(System.currentTimeMillis());
} else {
return getCalendarInstance(date.getTime());
}
}
/**
* <p>
* <b>WARNING: Experimental API - this method may be removed in future
* versions</b>
* </p>
* Get a new instance of Calendar that is initialized with the timezone and
* locale of this CDateTime, and set to the given date.
*
* @param date
* the date, in millis, that the Calendar will be set to
* @return a new instance of Calendar
*/
public Calendar getCalendarInstance(long date) {
Calendar cal = Calendar.getInstance(timezone, locale);
cal.setTimeInMillis(date);
return cal;
}
Date getCalendarTime() {
return calendar.getTime();
}
long getCalendarTimeInMillis() {
return calendar.getTimeInMillis();
}
public boolean getEditable() {
return !panel.hasStyle(SWT.READ_ONLY);
}
/**
* The locale currently in use by this CDateTime.
*
* @return the locale
* @see #setLocale(Locale)
*/
public Locale getLocale() {
return locale;
}
/**
* Get the text which will be shown when the selection is set to null. Note
* that this will be equal to the default null text for the given locale
* unless the null text has been explicitly set using
* {@link #setNullText(String)}
*
* @return the text shown when the selection is null
* @see #setNullText(String)
*/
public String getNullText() {
if (nullText == null) {
if (isDate) {
return Resources.getString("null_text.date", locale); //$NON-NLS-1$
} else {
return Resources.getString("null_text.time", locale); //$NON-NLS-1$
}
}
return nullText;
}
CDateTimePainter getPainter() {
if (painter == null) {
setPainter(new CDateTimePainter());
}
return painter;
}
/**
* Get the pattern of this CDateTime as used to set its format. If the
* format was NOT set using <code>setFormat(String)</code> this will return
* <code>null</code>.
*
* @return the pattern, null if there isn't one
* @see SimpleDateFormat
* @see #setFormat(int)
* @see #setPattern(String)
*/
public String getPattern() {
return pattern;
}
/**
* Get the current selection of this CDateTime widget, or null if there is
* no selection.
*
* @return the current selection
*/
public Date getSelection() {
return hasSelection() ? selection[0] : null;
}
public int getStyle() {
return style;
}
public String getText() {
return checkText() ? text.getText() : null;
}
VNative<Text> getTextWidget() {
return text;
}
/**
* The timezone currently in use by this CDateTime.
*
* @return the timezone
* @see #setTimeZone(String)
* @see #setTimeZone(TimeZone)
*/
public TimeZone getTimeZone() {
return timezone;
}
/**
* The Key event handler
*
* @param event
* the event
*/
void handleKey(Event event) {
if (event.stateMask != 0) {
return;
}
if ('\r' == event.keyCode || SWT.KEYPAD_CR == event.keyCode) {
fireSelectionChanged(true);
} else if (SWT.BS == event.keyCode || SWT.DEL == event.keyCode) {
event.doit = false;
setSelection((Date) null);
fireSelectionChanged();
} else if (!hasField(activeField) && !hasSelection()) {
event.doit = false;
} else {
switch (event.keyCode) {
case '-':
case SWT.KEYPAD_SUBTRACT:
fieldAdjust(-1);
break;
case '=':
case '+':
case SWT.KEYPAD_ADD:
fieldAdjust(1);
break;
case SWT.BS:
if (editField != null)
editField.removeLastCharacter();
break;
case SWT.ARROW_DOWN:
fieldAdjust(-1);
updateText(true);
break;
case SWT.ARROW_UP:
fieldAdjust(1);
updateText(true);
break;
case SWT.ARROW_LEFT:
fieldPrev(true);
break;
case SWT.ARROW_RIGHT:
fieldNext(true);
break;
default:
if (hasField(activeField)
&& activeField + 1 < separator.length
&& String.valueOf(event.character).equals(
separator[activeField + 1])) {
fieldNext();
} else if (!hasSelection()
&& String.valueOf(event.character).matches("[0-9]")) {
fieldAdjust(0);
fieldFirst();
}
}
}
}
/**
* The Travers event handler. Note that ARROW_UP and ARROW_DOWN are handled
* in the <code>handleKey</code> method.
*
* @param event
* the event
*/
void handleTraverse(Event event) {
boolean allowTimeZoneEdit = this.allowedTimezones != null;
switch (event.detail) {
case SWT.TRAVERSE_ARROW_NEXT:
if (event.keyCode == SWT.ARROW_RIGHT) {
fieldNext();
} else if (event.keyCode == SWT.ARROW_DOWN) {
fieldAdjust(-1);
}
break;
case SWT.TRAVERSE_ARROW_PREVIOUS:
if (event.keyCode == SWT.ARROW_LEFT) {
fieldPrev();
} else if (event.keyCode == SWT.ARROW_UP) {
fieldAdjust(1);
}
break;
case SWT.CR:
fieldNext();
fireSelectionChanged();
break;
case SWT.TRAVERSE_TAB_NEXT:
if (tabStops && hasSelection()) {
// if we are at the last field, allow the tab out of the control
// the last field is also considered to be the 2nd to last if
// the last is a time zone
// we now check if the control allows time zone editing
if (activeField == field.length - 1
|| ((activeField == field.length - 2)
&& (Calendar.ZONE_OFFSET == getCalendarField(field[field.length - 1])) && (!allowTimeZoneEdit))) {
event.doit = true;
} else {
event.doit = false;
if (activeField < 0) {
fieldPrev();
} else {
fieldNext();
}
}
}
break;
case SWT.TRAVERSE_TAB_PREVIOUS:
if (tabStops && hasSelection()) {
// if we are at the 1st field, allow the tab out of the control
// the 1st field is also considered to the the 2nd if the 1st
// is a time zone
if (activeField == 0
|| ((activeField == 1)
&& (Calendar.ZONE_OFFSET == getCalendarField(field[0])) && (!allowTimeZoneEdit))) {
event.doit = true;
} else {
event.doit = false;
if (activeField < 0) {
fieldNext();
} else {
fieldPrev();
}
}
}
break;
default:
}
}
/**
* Determines if the given field number is backed by a real field.
*
* @param field
* the field number to check
* @return true if the given field number corresponds to a field in the
* field array
*/
private boolean hasField(int field) {
return field >= 0 && field <= this.field.length;
}
/**
* Return true if this CDateTime has one or more dates selected;
*
* @return true if a date is selected, false otherwise
*/
public boolean hasSelection() {
return selection.length > 0;
}
private void init(int style) {
this.style = style;
locale = Locale.getDefault();
try {
timezone = TimeZone.getDefault();
} catch (Exception e) {
timezone = TimeZone.getTimeZone("GMT"); //$NON-NLS-1$
}
calendar = Calendar.getInstance(this.timezone, this.locale);
calendar.setTime(new Date());
tabStops = (style & CDT.TAB_FIELDS) != 0;
singleSelection = ((style & CDT.SIMPLE) == 0)
|| ((style & CDT.MULTI) == 0);
setFormat(style);
if (!isSimple()) {
if (isDropDown()) {
if ((style & CDT.BUTTON_AUTO) != 0) {
setButtonVisibility(BaseCombo.BUTTON_AUTO);
} else {
setButtonVisibility(BaseCombo.BUTTON_ALWAYS);
}
} else {
setButtonVisibility(BaseCombo.BUTTON_NEVER);
if ((style & CDT.SPINNER) != 0) {
int sStyle = SWT.VERTICAL;
if (gtk && ((style & CDT.BORDER) != 0)) {
sStyle |= SWT.BORDER;
}
spinner = VNative.create(Spinner.class, panel, sStyle);
if (win32) {
spinner.setBackground(text.getControl().getBackground());
}
spinner.getControl().setMinimum(0);
spinner.getControl().setMaximum(50);
spinner.getControl().setDigits(1);
spinner.getControl().setIncrement(1);
spinner.getControl().setPageIncrement(1);
spinner.getControl().setSelection(25);
spinner.getControl().addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent e) {
internalFocusShift = true;
setFocus();
internalFocusShift = false;
}
});
spinner.getControl().addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
if (e.button == 2) {
fieldNext();
}
}
});
spinner.getControl().addSelectionListener(
new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
if (VTracker.getMouseDownButton() != 2) {
if (spinner.getControl().getSelection() > 25) {
fieldAdjust(1);
} else {
fieldAdjust(-1);
}
spinner.getControl().setSelection(25);
}
}
});
panel.setLayout(new SpinnerLayout());
}
}
updateText();
activeField = -5;
setActiveField(FIELD_NONE);
if (checkText()) {
addTextListener();
}
}
}
boolean isSelected(Date date) {
for (Date d : selection) {
if (d.equals(date)) {
return true;
}
}
return false;
}
boolean isSingleSelection() {
return singleSelection;
}
/**
* Determine whether the provided field is the most
* <b>precise</b> field. According to the used pattern, e.g.
* <ul>
* <li>dd.mm.yyyy
* <li>MMMM yyyy
* <li>yyyy
* </ul>
* the date picker provides the panels for selecting a day, month or year
* respectively. The panel should close itself and set the selection in
* the CDateTime field when the user selects the most precise field.
* The constants from the {@link Calendar} class may be used to determine
* the most precise field:
* <ul>
* <li>{@link Calendar#YEAR} -> 1
* <li>{@link Calendar#MONTH} -> 2
* <li>{@link Calendar#DATE} -> 5
* </ul>
* e.g. the <i>highest</i> constant is the closing field.
* @param calendarField The calendar field identifying a pattern field
* @return true if the highest pattern field
*/
boolean isClosingField(int calendarField) {
// find the "highest" constant in the pattern fields
int i = Integer.MIN_VALUE;
for (Field f : field) {
i = Math.max(i, f.getCalendarField());
}
// compare the highest constant with the field
if ( i == calendarField) {
return true;
}
return false;
}
@Override
protected void postClose(Shell popup) {
disposePicker();
}
/**
* Removes the listener from the collection of listeners who will be
* notified when the receiver's selection changes.
*
* @param listener
* the listener which should no longer be notified
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
* @see SelectionListener
* @see #addSelectionListener
*/
public void removeSelectionListener(SelectionListener listener) {
if (listener != null) {
TypedListener l = new TypedListener(listener);
removeListener(SWT.Selection, l);
removeListener(SWT.DefaultSelection, l);
}
}
/**
* Removes the textListener for the appropriate SWT events to handle
* incrementing fields.
*/
protected void removeTextListener() {
Text control = text.getControl();
control.removeListener(SWT.KeyDown, textListener);
control.removeListener(SWT.MouseDown, textListener);
control.removeListener(SWT.MouseWheel, textListener);
control.removeListener(SWT.MouseUp, textListener);
control.removeListener(SWT.Verify, textListener);
text.removeListener(SWT.Traverse, textListener);
}
// void select(Date date) {
// if(date != null) {
// Date[] tmp = new Date[selection.length + 1];
// System.arraycopy(selection, 0, tmp, 1, selection.length);
// tmp[0] = date;
// setSelectedDates(tmp);
// }
// }
//
// void select(Date date1, Date date2, int field, int increment) {
// if(date1 != null && date2 != null) {
// Date start = date1.before(date2) ? date1 : date2;
// Date end = date1.before(date2) ? date2 : date1;
// List<Date> tmp = new ArrayList<Date>();
// Calendar cal = getCalendarInstance(start);
// while(cal.getTime().before(end)) {
// tmp.add(cal.getTime());
// cal.add(field, increment);
// }
// tmp.add(cal.getTime());
// if(start == date2) {
// Collections.reverse(tmp);
// }
// setSelectedDates(tmp.toArray(new Date[tmp.size()]));
// }
// }
/**
* Sets the active field, which may or may not be a real field (it may also
* be <code>FIELD_NONE</code>)
*
* @param field
* the field to be set active
* @see CDateTime#hasField(int)
*/
private void setActiveField(int field) {
if (activeField != field) {
commitEditField();
editField = null;
activeField = field;
}
}
/**
* <p>
* <b>WARNING: Experimental API - this method may be removed in future
* versions</b>
* </p>
* Sets the builder that this CDateTime widget will use to build its
* graphical selector to the given builder, or to a default builder if the
* given builder is null.
*
* @param builder
* the builder to use, or null to use a default builder
*/
public void setBuilder(CDateTimeBuilder builder) {
this.builder = builder;
if (picker != null) {
disposePicker();
createPicker();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.nebula.cwt.base.BaseCombo#setButtonImage(org.eclipse.swt.
* graphics.Image)
*/
public void setButtonImage(Image image) {
super.setButtonImage(image);
}
@Override
protected boolean setContentFocus() {
if (checkPicker()) {
internalFocusShift = true;
boolean result = picker.setFocus();
internalFocusShift = false;
return result;
}
return false;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.nebula.cwt.base.BaseCombo#setEditable(boolean)
*/
public void setEditable(boolean editable) {
super.setEditable(editable);
if (checkPicker()) {
if (picker instanceof DatePicker) {
((DatePicker) picker).setEditable(editable);
} else {
picker.setActivatable(editable);
}
}
}
private boolean checkPicker() {
return picker != null && !picker.isDisposed();
}
/**
* Set the date and time format of this CDateTime uses style constants which
* correspond to the various forms of DateFormat.getXxxInstance(int). <dt>
* <b>Valid Styles:</b></dt> <dd>DATE_SHORT, DATE_MEDIUM, DATE_LONG,
* TIME_SHORT, TIME_MEDIUM</dd>
* <p>
* Styles are bitwise OR'ed together, but only one "DATE" and one "TIME" may
* be set at a time.
* </p>
* Examples:<br>
* </code>setFormat(CDT.DATE_LONG);</code><br />
* </code>setFormat(CDT.DATE_SHORT | CDT.TIME_MEDIUM);</code><br />
*
* @param format
* the bitwise OR'ed Date and Time format to be set
* @throws IllegalArgumentException
* @see #getPattern()
* @see #setPattern(String)
*/
public void setFormat(int format) throws IllegalArgumentException {
int dateStyle = (format & CDT.DATE_SHORT) != 0 ? DateFormat.SHORT
: (format & CDT.DATE_MEDIUM) != 0 ? DateFormat.MEDIUM
: (format & CDT.DATE_LONG) != 0 ? DateFormat.LONG : -1;
int timeStyle = (format & CDT.TIME_SHORT) != 0 ? DateFormat.SHORT
: (format & CDT.TIME_MEDIUM) != 0 ? DateFormat.MEDIUM : -1;
String str = null;
if (dateStyle != -1 && timeStyle != -1) {
str = ((SimpleDateFormat) DateFormat.getDateTimeInstance(dateStyle,
timeStyle, locale)).toPattern();
} else if (dateStyle != -1) {
str = ((SimpleDateFormat) DateFormat.getDateInstance(dateStyle,
locale)).toPattern();
} else if (timeStyle != -1) {
str = ((SimpleDateFormat) DateFormat.getTimeInstance(timeStyle,
locale)).toPattern();
} else if (pattern == null) { // first call, so set to default
format = CDT.DATE_SHORT;
str = ((SimpleDateFormat) DateFormat.getDateInstance(
DateFormat.SHORT, locale)).toPattern();
}
if (str != null) {
this.format = format;
setPattern(str);
}
}
/**
* Sets the Locale to be used by this CDateTime and causes all affected
* attributes to be updated<br>
* If the provided locale is the same as the current locale then this method
* simply returns. If the provided Locale is null then this CDateTime will
* use the system's default locale.<br>
* If this <code>CDateTime</code> is of style <code>DROP_DOWN</code> then
* the associated <code>CDateTime</code> will be set to the same locale.
*
* @param locale
* the Locale, or null to use the system's default
* @see #getLocale()
*/
public void setLocale(Locale locale) {
if (locale == null)
locale = Locale.getDefault();
if (!this.locale.equals(locale)) {
this.locale = locale;
if (format > 0) {
setFormat(format);
} else {
setPattern(pattern);
}
updateNullText();
}
}
protected void setModifyEventProperties(Event e) {
e.data = calendar.getTime();
}
/**
* Set the text to be shown when the selection is null. Passing null into
* this method will cause the CDateTime widget to use a default null text
* for the given locale.
*
* @param text
*/
public void setNullText(String text) {
defaultNullText = false;
nullText = text;
updateText();
}
public void setOpen(boolean open) {
setOpen(open, null);
}
public void setOpen(boolean open, Runnable callback) {
if (open) {
cancelDate = getSelection();
createPicker();
} else {
cancelDate = null;
}
super.setOpen(open, callback);
if (hasSelection()) {
show(getSelection());
}
}
/**
* <p>
* <b>WARNING: Experimental API - this method may be removed in future
* versions</b>
* </p>
* Sets the painter that this CDateTime widget will use to paint its
* graphical selector to the given painter, or to a default painter if the
* given painter is null.
*
* @param painter
* the painter to use, or null to use a default painter
*/
public void setPainter(CDateTimePainter painter) {
if (painter != null) {
painter.setCDateTime(this);
}
this.painter = painter;
}
/**
* Set the style of this CDateTime to work with dates and / or times as
* determined by the given pattern. This will set the fields shown in the
* text box and, if <code>DROP_DOWN</code> style is set, the fields of the
* drop down component.<br>
* This method is backed by an implementation of SimpleDateFormat, and as
* such, any string pattern which is valid for SimpleDateFormat may be used.
* Examples (US Locale):<br>
* </code>setPattern("MM/dd/yyyy h:mm a");</code><br />
* </code>setPattern("'Meeting @' h:mm a 'on' EEEE, MMM dd, yyyy");</code><br />
*
* @param pattern
* the pattern to use, if it is invalid, the original is restored
* @throws IllegalArgumentException
* @see SimpleDateFormat
* @see #getPattern()
* @see #setFormat(int)
*/
public void setPattern(String pattern) throws IllegalArgumentException {
this.allowedTimezones = null;
if (isOpen()) {
setOpen(false);
}
df = new SimpleDateFormat(pattern, locale);
df.setTimeZone(timezone);
if (updateFields()) {
this.pattern = pattern;
this.format = -1;
boolean wasDate = isDate;
boolean wasTime = isTime;
isDate = isTime = false;
calendarFields = new int[field.length];
for (int i = 0; i < calendarFields.length; i++) {
calendarFields[i] = getCalendarField(field[i]);
switch (calendarFields[i]) {
case Calendar.AM_PM:
case Calendar.HOUR:
case Calendar.HOUR_OF_DAY:
case Calendar.MILLISECOND:
case Calendar.MINUTE:
case Calendar.SECOND:
case Calendar.ZONE_OFFSET:
isTime = true;
break;
case Calendar.DAY_OF_MONTH:
case Calendar.DAY_OF_WEEK:
case Calendar.DAY_OF_WEEK_IN_MONTH:
case Calendar.DAY_OF_YEAR:
case Calendar.ERA:
case Calendar.MONTH:
case Calendar.WEEK_OF_MONTH:
case Calendar.WEEK_OF_YEAR:
case Calendar.YEAR:
isDate = true;
break;
default:
break;
}
}
if (checkButton() && ((isDate != wasDate) || (isTime != wasTime))) {
if (defaultButtonImage) {
if (isDate && isTime) {
doSetButtonImage(Resources.getIconCalendarClock());
} else if (isDate) {
doSetButtonImage(Resources.getIconCalendar());
} else {
doSetButtonImage(Resources.getIconClock());
}
}
updateNullText();
}
if (checkText()) {
updateText();
}
if (isSimple()) {
disposePicker();
createPicker();
}
} else {
throw new IllegalArgumentException(
"Problem setting pattern: \"" + pattern + "\""); //$NON-NLS-1$ //$NON-NLS-2$
}
}
void setScrollable(boolean scrollable) {
this.scrollable = scrollable;
if (isSimple() && !scrollable) {
if (picker != null && picker instanceof DatePicker) {
updatePicker();
}
}
}
/**
* Set the selection for this CDateTime to that of the provided
* <code>Date</code> object.<br>
*
* @param selection
* the new selection, or null to clear the selection
*/
public void setSelection(Date selection) {
if (getEditable()) {
if (selection == null) {
this.selection = new Date[0];
} else {
this.selection = new Date[] { selection };
}
}
if (singleSelection && this.selection.length > 0) {
show(selection);
} else {
updateText();
updatePicker();
}
}
/**
* Sets the timezone to the timezone specified by the given zoneID, or to
* the system default if the given zoneID is null. If the give zoneID cannot
* be understood, then the timezone will be set to GMT.
*
* @param zoneID
* the id of the timezone to use, or null to use the system
* default
* @see #setTimeZone(TimeZone)
*/
public void setTimeZone(String zoneID) {
if (zoneID == null) {
setTimeZone((TimeZone) null);
} else {
setTimeZone(TimeZone.getTimeZone(zoneID));
}
}
/**
* Sets the timezone to the given timezone, or to the system's default
* timezone if the given timezone is null.
*
* @param zone
* the timezone to use, or null to use the system default
* @see #setTimeZone(String)
*/
public void setTimeZone(TimeZone zone) {
if (zone == null) {
timezone = TimeZone.getDefault();
}
if (!this.timezone.equals(zone)) {
this.timezone = zone;
calendar.setTimeZone(this.timezone);
df.setTimeZone(this.timezone);
updateText();
}
}
/**
* Shows the given date if it can be shown by the selector. In other words,
* for graphical selectors such as a calendar, the visible range of time is
* moved so that the given date is visible.
*
* @param date
* the date to show
*/
public void show(Date date) {
if (date == null) {
calendar.setTime(new Date());
} else {
calendar.setTime(date);
}
updateText();
updatePicker();
}
/**
* Show the selection if it can be shown by the selector. Has no affect if
* there is no selection.
*
* @see #show(Date)
*/
public void showSelection() {
if (selection.length > 0) {
show(selection[0]);
}
}
@Override
public String toString() {
return getClass().getSimpleName() + " {" + getCalendarTime() + "}"; //$NON-NLS-1$ //$NON-NLS-2$
}
/**
* inspects all of the calendar fields in the <code>field</code> array to
* determine what style is appropriate and then sets that style to the
* picker using the setPickerStyle method.<br>
*/
private boolean updateFields() {
Field[] bak = new Field[(field == null) ? 0 : field.length];
if (bak.length > 0)
System.arraycopy(field, 0, bak, 0, field.length);
AttributedCharacterIterator aci = df.formatToCharacterIterator(calendar
.getTime());
field = new Field[aci.getAllAttributeKeys().size()];
separator = new String[field.length + 1]; // there can be a separator
// before and after
int i = 0;
Object last = null;
for (char c = aci.first(); c != CharacterIterator.DONE; c = aci.next()) {
Object[] oa = aci.getAttributes().keySet().toArray();
if (oa.length > 0) {
if (oa[0] != last && i < field.length) {
if (getCalendarField((Field) oa[0]) < 0) {
if (bak.length > 0) {
field = new Field[bak.length];
System.arraycopy(bak, 0, field, 0, bak.length);
}
return false;
} else {
field[i] = (Field) oa[0];
last = oa[0];
}
i++;
}
} else {
if (separator[i] == null)
separator[i] = String.valueOf(c);
}
}
df.setLenient(false);
setActiveField(FIELD_NONE);
return true;
}
private void updateNullText() {
if (defaultNullText) {
if (isDate) {
nullText = Resources.getString("null_text.date", locale); //$NON-NLS-1$
} else {
nullText = Resources.getString("null_text.time", locale); //$NON-NLS-1$
}
if (!hasSelection()) {
updateText();
}
}
}
/**
* tell the picker to update its view of the selection and reference time
*/
private void updatePicker() {
if (picker != null) {
if (picker instanceof DatePicker) {
((DatePicker) picker).updateView();
} else if (picker instanceof AnalogTimePicker) {
((AnalogTimePicker) picker).updateView();
} else if (picker instanceof DiscreteTimePicker) {
((DiscreteTimePicker) picker).updateView();
}
}
}
/**
* This is the only way that text is set to the text box.<br>
* The selection of the text in the text box is also set here (the active field is selected) as
* well as if a field is being edited, it's "edit text" is inserted for
* display.
* The <code>getSelection</code> property of CDateTime remains unchanged.
*/
private void updateText() {
updateText(false);
}
/**
* This is the only way that text is set to the text box.<br>
* The selection of the text in the text box is also set here (the active field is selected) as
* well as if a field is being edited, it's "edit text" is inserted for
* display.
* The <code>getSelection</code> property of CDateTime remains unchanged.
*
* @param async
* If true, this operation will be performed asynchronously (for
* changes to text selection)
*/
private void updateText(boolean async) {
// TODO: save previous state and only update on changes...?
String buffer = hasSelection() ? df.format(getSelection())
: getNullText();
int s0 = 0;
int s1 = 0;
if (!hasSelection()) {
s0 = 0;
s1 = buffer.length();
} else if (activeField >= 0 && activeField < field.length) {
AttributedCharacterIterator aci = df
.formatToCharacterIterator(getSelection());
for (char c = aci.first(); c != CharacterIterator.DONE; c = aci
.next()) {
if (aci.getAttribute(field[activeField]) != null) {
s0 = aci.getRunStart();
s1 = aci.getRunLimit();
if (editField != null) {
String str = editField.toString();
buffer = buffer.substring(0, s0) + str
+ buffer.substring(s1);
int oldS1 = s1;
s1 = s0 + str.length();
textSelectionOffset.x = Math.min(oldS1, s1);
textSelectionOffset.y = (oldS1 - s0) - str.length();
} else {
textSelectionOffset.x = buffer.length() + 1;
textSelectionOffset.y = 0;
}
break;
}
}
} else {
setActiveField(FIELD_NONE);
}
final String string = buffer;
final int selStart = s0;
final int selEnd = s1;
Runnable runnable = new Runnable() {
public void run() {
if ((text != null) && (!text.isDisposed())) {
if (!string.equals(text.getText())) {
text.getControl().removeListener(SWT.Verify,
textListener);
text.setText(string);
text.getControl().addListener(SWT.Verify, textListener);
}
text.getControl().setSelection(selStart, selEnd);
}
}
};
if (async) {
getDisplay().asyncExec(runnable);
} else {
getDisplay().syncExec(runnable);
}
}
/**
* The Verify Event handler.<br>
* <b>EVERYTHING</b> is blocked via this handler (Event.doit is set to
* false). Depending upon the input, a course of action is determined and
* the displayed text is updated via the <code>updateText()</code> method.
* <br>
* This method implements the following logic:
* If the event is a paste, the pasted text is parsed for the entire date/time selection;
* if this parse is successful, the result is committed to the selection property and is displayed;
* otherwise, it is discarded. One-character pastes are discarded without parsing.
* When user types characters one by one, all non-digits are discarded (if they have effects, they
* have already been processed by other event handlers) while digits are added to
* <code>this.editField</code> without affecting the selection.
* Once <code>this.editField</code> reaches its capacity for
* the active field, its contents are attempted to be committed.
* If the commit is successful, focus switches to the next field of CDateTime.
* Otherwise, the contents of <code>this.editField</code>
* are discarded and the previous value of the selection (before user started typing
* in this field) is restored; focus stays in the same field.
* <b>Example:</b> if the seconds field contains "23", and user types 8 in the seconds field,
* "08" is shown on screen while getSelection still returns 23. If user types 9 after that,
* the field reaches its capacity, the attempt to commit 89 seconds fails, and 23 gets restored
* on screen.
*
* @param e
* the event
* @see CDateTime#updateText()
*/
void verify(Event e) {
// we don't want to reprocess the event
if(e.doit == false){
return;
}
e.doit = false;
if (field.length == 0 || activeField == FIELD_NONE)
return;
char c = e.character;
if (((e.text.length() == 1) && String.valueOf(c).equals(e.text) && Character
.isDigit(c)) || (e.text.length() > 1)) {
if (e.text.length() == 1) {
if (editField == null) {
int cf = getCalendarField();
if (cf >= 0) {
int digits;
switch (cf) {
case Calendar.YEAR:
digits = 4;
break;
case Calendar.DAY_OF_YEAR:
digits = 3;
break;
case Calendar.AM_PM:
case Calendar.DAY_OF_WEEK:
case Calendar.ERA:
digits = 1;
break;
case Calendar.MILLISECOND:
digits = 3;
break;
default:
digits = 2;
}
editField = new EditField(digits, calendar.get(cf));
} else {
return;
}
}
if (editField.addChar(c)) {
if (commitEditField()) {
fieldNext();
} else {
editField = null;
if (selection.length > 0) {
selection[0] = calendar.getTime();
}
updateText();
}
}
if (selection.length > 0) {
selection[0] = calendar.getTime();
}
updatePicker();
} else {
try {
setSelection(df.parse(e.text));
fireSelectionChanged();
} catch (ParseException pe) {
// do nothing
}
}
}
updateText();
}
/**
* @param pattern
* @param allowedTimeZones
* @throws IllegalArgumentException
*/
public void setPattern(final String pattern,
final TimeZone[] allowedTimeZones) throws IllegalArgumentException {
this.setPattern(pattern);
if (pattern.indexOf('z') != -1) {
this.allowedTimezones = allowedTimeZones;
}
}
}