diff options
6 files changed, 1535 insertions, 232 deletions
diff --git a/WebContent/VAADIN/themes/base/datefield/datefield.css b/WebContent/VAADIN/themes/base/datefield/datefield.css index f1c38513e1..6f69a91f5e 100644 --- a/WebContent/VAADIN/themes/base/datefield/datefield.css +++ b/WebContent/VAADIN/themes/base/datefield/datefield.css @@ -22,6 +22,9 @@ padding: 0; margin: 0; } +.v-datefield-calendarpanel:focus{ + outline:none; +} .v-datefield-calendarpanel-header td { text-align: center; } diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java new file mode 100644 index 0000000000..a966aa8a16 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlexTable.java @@ -0,0 +1,102 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.HasBlurHandlers; +import com.google.gwt.event.dom.client.HasFocusHandlers; +import com.google.gwt.event.dom.client.HasKeyDownHandlers; +import com.google.gwt.event.dom.client.HasKeyPressHandlers; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.terminal.gwt.client.Focusable; + +/** + * Adds keyboard focus to {@link FlexPanel}. + */ +public class FocusableFlexTable extends FlexTable implements HasFocusHandlers, + HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable { + + /** + * Default constructor. + */ + public FocusableFlexTable() { + // make focusable, as we don't need access key magic we don't need to + // use FocusImpl.createFocusable + getElement().setTabIndex(0); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com. + * google.gwt.event.dom.client.FocusHandler) + */ + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return addDomHandler(handler, FocusEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google + * .gwt.event.dom.client.BlurHandler) + */ + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return addDomHandler(handler, BlurEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler( + * com.google.gwt.event.dom.client.KeyDownHandler) + */ + public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { + return addDomHandler(handler, KeyDownEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler + * (com.google.gwt.event.dom.client.KeyPressHandler) + */ + public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) { + return addDomHandler(handler, KeyPressEvent.getType()); + } + + /** + * Sets the keyboard focus to the panel + * + * @param focus + * Should the panel have keyboard focus. If true the keyboard + * focus will be moved to the + */ + public void setFocus(boolean focus) { + if (focus) { + FocusImpl.getFocusImplForPanel().focus(getElement()); + } else { + FocusImpl.getFocusImplForPanel().blur(getElement()); + } + } + + /* + * (non-Javadoc) + * + * @see com.vaadin.terminal.gwt.client.Focusable#focus() + */ + public void focus() { + setFocus(true); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java new file mode 100644 index 0000000000..bb8857c374 --- /dev/null +++ b/src/com/vaadin/terminal/gwt/client/ui/FocusableFlowPanel.java @@ -0,0 +1,97 @@ +package com.vaadin.terminal.gwt.client.ui; + +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.HasBlurHandlers; +import com.google.gwt.event.dom.client.HasFocusHandlers; +import com.google.gwt.event.dom.client.HasKeyDownHandlers; +import com.google.gwt.event.dom.client.HasKeyPressHandlers; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyPressEvent; +import com.google.gwt.event.dom.client.KeyPressHandler; +import com.google.gwt.event.shared.HandlerRegistration; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.impl.FocusImpl; +import com.vaadin.terminal.gwt.client.Focusable; + +public class FocusableFlowPanel extends FlowPanel implements HasFocusHandlers, + HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable { + + /** + * Constructor + */ + public FocusableFlowPanel() { + // make focusable, as we don't need access key magic we don't need to + // use FocusImpl.createFocusable + getElement().setTabIndex(0); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasFocusHandlers#addFocusHandler(com. + * google.gwt.event.dom.client.FocusHandler) + */ + public HandlerRegistration addFocusHandler(FocusHandler handler) { + return addDomHandler(handler, FocusEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasBlurHandlers#addBlurHandler(com.google + * .gwt.event.dom.client.BlurHandler) + */ + public HandlerRegistration addBlurHandler(BlurHandler handler) { + return addDomHandler(handler, BlurEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasKeyDownHandlers#addKeyDownHandler( + * com.google.gwt.event.dom.client.KeyDownHandler) + */ + public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { + return addDomHandler(handler, KeyDownEvent.getType()); + } + + /* + * (non-Javadoc) + * + * @see + * com.google.gwt.event.dom.client.HasKeyPressHandlers#addKeyPressHandler + * (com.google.gwt.event.dom.client.KeyPressHandler) + */ + public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) { + return addDomHandler(handler, KeyPressEvent.getType()); + } + + /** + * Sets/Removes the keyboard focus to the panel. + * + * @param focus + * If set to true then the focus is moved to the panel, if set to + * false the focus is removed + */ + public void setFocus(boolean focus) { + if (focus) { + FocusImpl.getFocusImplForPanel().focus(getElement()); + } else { + FocusImpl.getFocusImplForPanel().blur(getElement()); + } + } + + /** + * Focus the panel + */ + public void focus() { + setFocus(true); + } +} diff --git a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java b/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java index 0c2cb056a6..bf753ce530 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VCalendarPanel.java @@ -10,18 +10,177 @@ import java.util.List; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlexTable;
+import com.google.gwt.user.client.ui.InlineHTML;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.MouseListenerCollection;
import com.google.gwt.user.client.ui.SourcesMouseEvents;
import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.terminal.gwt.client.BrowserInfo;
import com.vaadin.terminal.gwt.client.DateTimeService;
import com.vaadin.terminal.gwt.client.LocaleService;
-public class VCalendarPanel extends FlexTable implements MouseListener {
+public class VCalendarPanel extends FocusableFlexTable implements
+ MouseListener, KeyDownHandler, KeyPressHandler {
+
+ /**
+ * Represents a Date button in the calendar
+ */
+ private class VEventButton extends VNativeButton implements
+ SourcesMouseEvents {
+
+ private MouseListenerCollection mouseListeners;
+
+ /**
+ * Default constructor
+ */
+ public VEventButton() {
+ super();
+ sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK
+ | Event.MOUSEEVENTS);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.FocusWidget#addMouseListener(com.google
+ * .gwt.user.client.ui.MouseListener)
+ */
+ @Override
+ public void addMouseListener(MouseListener listener) {
+ if (mouseListeners == null) {
+ mouseListeners = new MouseListenerCollection();
+ }
+ mouseListeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.FocusWidget#removeMouseListener(com
+ * .google.gwt.user.client.ui.MouseListener)
+ */
+ @Override
+ public void removeMouseListener(MouseListener listener) {
+ if (mouseListeners != null) {
+ mouseListeners.remove(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.VNativeButton#onBrowserEvent(com
+ * .google.gwt.user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+ switch (DOM.eventGetType(event)) {
+ case Event.ONMOUSEDOWN:
+ case Event.ONMOUSEUP:
+ case Event.ONMOUSEOUT:
+ if (mouseListeners != null) {
+ mouseListeners.fireMouseEvent(this, event);
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * Represents a click handler for when a user selects a date by using the
+ * mouse
+ */
+ private class DateClickHandler implements ClickHandler {
+
+ private final VCalendarPanel cal;
+
+ public DateClickHandler(VCalendarPanel panel) {
+ cal = panel;
+ }
+
+ public void selectDate(String date) {
+ try {
+ final Integer day = new Integer(date);
+ final Date newDate = cal.datefield.getShowingDate();
+ newDate.setDate(day.intValue());
+ if (!isEnabledDate(newDate)) {
+ return;
+ }
+ if (cal.datefield.getCurrentDate() == null) {
+ cal.datefield.setCurrentDate(new Date(newDate.getTime()));
+
+ // Init variables with current time
+ datefield.getClient().updateVariable(cal.datefield.getId(),
+ "hour", newDate.getHours(), false);
+ datefield.getClient().updateVariable(cal.datefield.getId(),
+ "min", newDate.getMinutes(), false);
+ datefield.getClient().updateVariable(cal.datefield.getId(),
+ "sec", newDate.getSeconds(), false);
+ datefield.getClient().updateVariable(cal.datefield.getId(),
+ "msec", datefield.getMilliseconds(), false);
+ }
+
+ cal.datefield.getCurrentDate().setTime(newDate.getTime());
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),
+ "day", cal.datefield.getCurrentDate().getDate(), false);
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),
+ "month", cal.datefield.getCurrentDate().getMonth() + 1,
+ false);
+ cal.datefield.getClient().updateVariable(cal.datefield.getId(),
+ "year",
+ cal.datefield.getCurrentDate().getYear() + 1900,
+ cal.datefield.isImmediate());
+
+ if (datefield instanceof VTextualDate) {
+ ((VOverlay) getParent()).hide();
+ } else {
+ updateCalendar();
+ }
+
+ } catch (final NumberFormatException e) {
+ // Not a number, ignore and stop here
+ return;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt
+ * .event.dom.client.ClickEvent)
+ */
+ public void onClick(ClickEvent event) {
+ Object sender = event.getSource();
+ Cell cell = cal.days.getCellForEvent(event);
+ if (sender != cal.days || cell == null || cell.getRowIndex() < 1
+ || cell.getRowIndex() > 6 || !cal.datefield.isEnabled()
+ || cal.datefield.isReadonly() || cell.getCellIndex() < 1) {
+ return;
+ }
+
+ final String text = cal.days.getText(cell.getRowIndex(), cell
+ .getCellIndex());
+ if (text.equals(" ")) {
+ return;
+ }
+
+ selectDate(text);
+ }
+ }
private final VDateField datefield;
@@ -49,18 +208,57 @@ public class VCalendarPanel extends FlexTable implements MouseListener { /* Needed to identify locale changes */
private String locale = LocaleService.getDefaultLocale();
+ private int selectedRow;
+ private int selectedColumn;
+
+ private boolean changingView = false;
+
+ private final DateClickHandler dateClickHandler;
+
+ private Timer mouseTimer;
+
public VCalendarPanel(VDateField parent) {
+ super();
+
datefield = parent;
setStyleName(VDateField.CLASSNAME + "-calendarpanel");
// buildCalendar(true);
- days.addClickHandler(new DateClickHandler(this));
+
+ dateClickHandler = new DateClickHandler(this);
+ days.addClickHandler(dateClickHandler);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+
}
public VCalendarPanel(VDateField parent, Date min, Date max) {
+ super();
+
datefield = parent;
setStyleName(VDateField.CLASSNAME + "-calendarpanel");
- days.addClickHandler(new DateClickHandler(this));
+ dateClickHandler = new DateClickHandler(this);
+ days.addClickHandler(dateClickHandler);
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
}
private void buildCalendar(boolean forceRedraw) {
@@ -118,9 +316,11 @@ public class VCalendarPanel extends FlexTable implements MouseListener { prevYear = new VEventButton();
prevYear.setHTML("«");
prevYear.setStyleName("v-button-prevyear");
+ prevYear.getElement().setTabIndex(-1);
nextYear = new VEventButton();
nextYear.setHTML("»");
nextYear.setStyleName("v-button-nextyear");
+ nextYear.getElement().setTabIndex(-1);
prevYear.addMouseListener(this);
nextYear.addMouseListener(this);
setWidget(0, 0, prevYear);
@@ -130,9 +330,11 @@ public class VCalendarPanel extends FlexTable implements MouseListener { prevMonth = new VEventButton();
prevMonth.setHTML("‹");
prevMonth.setStyleName("v-button-prevmonth");
+ prevMonth.getElement().setTabIndex(-1);
nextMonth = new VEventButton();
nextMonth.setHTML("›");
nextMonth.setStyleName("v-button-nextmonth");
+ nextMonth.getElement().setTabIndex(-1);
prevMonth.addMouseListener(this);
nextMonth.addMouseListener(this);
setWidget(0, 3, nextMonth);
@@ -217,10 +419,17 @@ public class VCalendarPanel extends FlexTable implements MouseListener { // date actually selected?
Date selectedDate = datefield.getCurrentDate();
+
// Showing is the date (year/month+year) that is currently shown in the
// panel
Date showing = datefield.getShowingDate();
+ // Always show something
+ if (showing == null) {
+ showing = new Date();
+ datefield.setShowingDate(showing);
+ }
+
// The day of month that is selected, -1 if no day of this month is
// selected (i.e, showing another month/year than selected or nothing is
// selected)
@@ -230,7 +439,9 @@ public class VCalendarPanel extends FlexTable implements MouseListener { int dayOfMonthToday = -1;
// Find out a day this month is selected
- if (selectedDate != null
+ if (showing != null) {
+ dayOfMonthSelected = showing.getDate();
+ } else if (selectedDate != null
&& selectedDate.getMonth() == showing.getMonth()
&& selectedDate.getYear() == showing.getYear()) {
dayOfMonthSelected = selectedDate.getDate();
@@ -284,28 +495,31 @@ public class VCalendarPanel extends FlexTable implements MouseListener { }
}
- // Add CSS classes according to state
- String cssClass = baseclass;
+ // Actually write the day of month
+ InlineHTML html = new InlineHTML(String.valueOf(dayOfMonth));
+ html.setStylePrimaryName(baseclass);
+ html.setTitle(title);
+ // Add CSS classes according to state
if (!isEnabledDate(curr)) {
- cssClass += " " + baseclass + "-disabled";
+ html.addStyleDependentName("disabled");
}
if (dayOfMonthSelected == dayOfMonth) {
- cssClass += " " + baseclass + "-selected";
+ html.addStyleDependentName("selected");
+ selectedRow = weekOfMonth;
+ selectedColumn = firstWeekdayColumn + dayOfWeek;
}
if (dayOfMonthToday == dayOfMonth) {
- cssClass += " " + baseclass + "-today";
+ html.addStyleDependentName("today");
}
if (title.length() > 0) {
- cssClass += " " + baseclass + "-entry";
+ html.addStyleDependentName("entry");
}
- // Actually write the day of month
- days.setHTML(weekOfMonth, firstWeekdayColumn + dayOfWeek,
- "<span title=\"" + title + "\" class=\"" + cssClass
- + "\">" + dayOfMonth + "</span>");
+ days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek,
+ html);
// ISO week numbers if requested
if (!weekNumberProcessed[weekOfMonth]) {
@@ -334,7 +548,7 @@ public class VCalendarPanel extends FlexTable implements MouseListener { private void buildTime(boolean forceRedraw) {
if (time == null) {
- time = new VTime(datefield);
+ time = new VTime(datefield, this);
setWidget(2, 0, time);
getFlexCellFormatter().setColSpan(2, 0, 5);
getFlexCellFormatter().setStyleName(2, 0,
@@ -344,19 +558,39 @@ public class VCalendarPanel extends FlexTable implements MouseListener { }
/**
- *
- * @param forceRedraw
- * Build all from scratch, in case of e.g. locale changes
+ * Updates the calendar and text field with the selected dates *
*/
public void updateCalendar() {
- // Locale and resolution changes force a complete redraw
- buildCalendar(locale != datefield.getCurrentLocale()
- || resolution != datefield.getCurrentResolution());
+ updateCalendarOnly();
if (datefield instanceof VTextualDate) {
((VTextualDate) datefield).buildDate();
}
- locale = datefield.getCurrentLocale();
- resolution = datefield.getCurrentResolution();
+ }
+
+ /**
+ * Updates the popup only, does not affect the text field
+ */
+ private void updateCalendarOnly() {
+ if (!changingView) {
+ changingView = true;
+ // Locale and resolution changes force a complete redraw
+ buildCalendar(locale != datefield.getCurrentLocale()
+ || resolution != datefield.getCurrentResolution());
+ locale = datefield.getCurrentLocale();
+ resolution = datefield.getCurrentResolution();
+
+ /*
+ * Wait a while before releasing the lock, so auto-repeated events
+ * isn't processed after the calendar is updated
+ */
+ Timer changeTimer = new Timer() {
+ @Override
+ public void run() {
+ changingView = false;
+ }
+ };
+ changeTimer.schedule(100);
+ }
}
private boolean isEnabledDate(Date date) {
@@ -367,72 +601,123 @@ public class VCalendarPanel extends FlexTable implements MouseListener { return true;
}
+ /**
+ * Selects the next month
+ */
+ private void selectNextMonth() {
+ Date showingDate = datefield.getShowingDate();
+ int currentMonth = showingDate.getMonth();
+ showingDate.setMonth(currentMonth + 1);
+ int requestedMonth = (currentMonth + 1) % 12;
+
+ /*
+ * If the selected date was e.g. 31.3 the new date would be 31.4 but
+ * this date is invalid so the new date will be 1.5. This is taken care
+ * of by decreasing the date until we have the correct month.
+ */
+ while (showingDate.getMonth() != requestedMonth) {
+ showingDate.setDate(showingDate.getDate() - 1);
+ }
+
+ updateCalendar();
+ }
+
+ /**
+ * Selects the previous month
+ */
+ private void selectPreviousMonth() {
+ Date showingDate = datefield.getShowingDate();
+ int currentMonth = showingDate.getMonth();
+ showingDate.setMonth(currentMonth - 1);
+
+ /*
+ * If the selected date was e.g. 31.12 the new date would be 31.11 but
+ * this date is invalid so the new date will be 1.12. This is taken care
+ * of by decreasing the date until we have the correct month.
+ */
+ while (showingDate.getMonth() == currentMonth) {
+ showingDate.setDate(showingDate.getDate() - 1);
+ }
+
+ updateCalendar();
+ }
+
+ /**
+ * Selects the previous year
+ */
+ private void selectPreviousYear(int years) {
+ Date showingDate = datefield.getShowingDate();
+ showingDate.setYear(showingDate.getYear() - years);
+ updateCalendar();
+ }
+
+ /**
+ * Selects the next year
+ */
+ private void selectNextYear(int years) {
+ Date showingDate = datefield.getShowingDate();
+ showingDate.setYear(showingDate.getYear() + years);
+ updateCalendar();
+ }
+
+ /**
+ * Handles a user click on the component
+ *
+ * @param sender
+ * The component that was clicked
+ * @param updateVariable
+ * Should the date field be updated
+ *
+ */
private void processClickEvent(Widget sender, boolean updateVariable) {
if (!datefield.isEnabled() || datefield.isReadonly()) {
return;
}
- Date showingDate = datefield.getShowingDate();
if (!updateVariable) {
if (sender == prevYear) {
- showingDate.setYear(showingDate.getYear() - 1);
- updateCalendar();
+ selectPreviousYear(1);
} else if (sender == nextYear) {
- showingDate.setYear(showingDate.getYear() + 1);
- updateCalendar();
+ selectNextYear(1);
} else if (sender == prevMonth) {
- int currentMonth = showingDate.getMonth();
- showingDate.setMonth(currentMonth - 1);
-
- /*
- * If the selected date was e.g. 31.12 the new date would be
- * 31.11 but this date is invalid so the new date will be 1.12.
- * This is taken care of by decreasing the date until we have
- * the correct month.
- */
- while (showingDate.getMonth() == currentMonth) {
- showingDate.setDate(showingDate.getDate() - 1);
- }
-
- updateCalendar();
+ selectPreviousMonth();
} else if (sender == nextMonth) {
- int currentMonth = showingDate.getMonth();
- showingDate.setMonth(currentMonth + 1);
- int requestedMonth = (currentMonth + 1) % 12;
-
- /*
- * If the selected date was e.g. 31.3 the new date would be 31.4
- * but this date is invalid so the new date will be 1.5. This is
- * taken care of by decreasing the date until we have the
- * correct month.
- */
- while (showingDate.getMonth() != requestedMonth) {
- showingDate.setDate(showingDate.getDate() - 1);
- }
-
- updateCalendar();
+ selectNextMonth();
}
} else {
if (datefield.getCurrentResolution() == VDateField.RESOLUTION_YEAR
|| datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {
- // Due to current UI, update variable if res=year/month
- datefield.setCurrentDate(new Date(showingDate.getTime()));
- if (datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {
- datefield.getClient().updateVariable(datefield.getId(),
- "month", datefield.getCurrentDate().getMonth() + 1,
- false);
- }
- datefield.getClient().updateVariable(datefield.getId(), "year",
- datefield.getCurrentDate().getYear() + 1900,
- datefield.isImmediate());
-
- /* Must update the value in the textfield also */
- updateCalendar();
+ notifyServerOfChanges();
}
}
}
- private Timer timer;
+ /**
+ * Send the date to the server
+ */
+ private void notifyServerOfChanges() {
+ Date showingDate = datefield.getShowingDate();
+
+ // Due to current UI, update variable if res=year/month
+ datefield.setCurrentDate(new Date(showingDate.getTime()));
+ if (datefield.getCurrentResolution() == VDateField.RESOLUTION_MONTH) {
+ datefield.getClient().updateVariable(datefield.getId(), "month",
+ datefield.getCurrentDate().getMonth() + 1, false);
+ }
+ datefield.getClient().updateVariable(datefield.getId(), "year",
+ datefield.getCurrentDate().getYear() + 1900,
+ datefield.isImmediate());
+
+ /* Must update the value in the textfield also */
+ updateCalendar();
+ }
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.MouseListener#onMouseDown(com.google.gwt
+ * .user.client.ui.Widget, int, int)
+ */
public void onMouseDown(final Widget sender, int x, int y) {
// Allow user to click-n-hold for fast-forward or fast-rewind.
// Timer is first used for a 500ms delay after mousedown. After that has
@@ -440,192 +725,740 @@ public class VCalendarPanel extends FlexTable implements MouseListener { // timers are cancelled on mouseup or mouseout.
if (sender instanceof VEventButton) {
processClickEvent(sender, false);
- timer = new Timer() {
+ mouseTimer = new Timer() {
@Override
public void run() {
- timer = new Timer() {
+ mouseTimer = new Timer() {
@Override
public void run() {
processClickEvent(sender, false);
}
};
- timer.scheduleRepeating(150);
+ mouseTimer.scheduleRepeating(150);
}
};
- timer.schedule(500);
+ mouseTimer.schedule(500);
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.MouseListener#onMouseEnter(com.google.gwt
+ * .user.client.ui.Widget)
+ */
public void onMouseEnter(Widget sender) {
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.MouseListener#onMouseLeave(com.google.gwt
+ * .user.client.ui.Widget)
+ */
public void onMouseLeave(Widget sender) {
- if (timer != null) {
- timer.cancel();
+ if (mouseTimer != null) {
+ mouseTimer.cancel();
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.MouseListener#onMouseMove(com.google.gwt
+ * .user.client.ui.Widget, int, int)
+ */
public void onMouseMove(Widget sender, int x, int y) {
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.MouseListener#onMouseUp(com.google.gwt.
+ * user.client.ui.Widget, int, int)
+ */
public void onMouseUp(Widget sender, int x, int y) {
- if (timer != null) {
- timer.cancel();
+ if (mouseTimer != null) {
+ mouseTimer.cancel();
}
processClickEvent(sender, true);
}
- private class VEventButton extends VNativeButton implements
- SourcesMouseEvents {
- private MouseListenerCollection mouseListeners;
+ public void setLimits(Date min, Date max) {
+ if (min != null) {
+ final Date d = new Date(min.getTime());
+ d.setHours(0);
+ d.setMinutes(0);
+ d.setSeconds(1);
+ minDate = d;
+ } else {
+ minDate = null;
+ }
+ if (max != null) {
+ final Date d = new Date(max.getTime());
+ d.setHours(24);
+ d.setMinutes(59);
+ d.setSeconds(59);
+ maxDate = d;
+ } else {
+ maxDate = null;
+ }
+ }
- public VEventButton() {
- super();
- sinkEvents(Event.FOCUSEVENTS | Event.KEYEVENTS | Event.ONCLICK
- | Event.MOUSEEVENTS);
+ public void setCalendarEntrySource(CalendarEntrySource entrySource) {
+ this.entrySource = entrySource;
+ }
+
+ public CalendarEntrySource getCalendarEntrySource() {
+ return entrySource;
+ }
+
+ public interface CalendarEntrySource {
+ public List getEntries(Date date, int resolution);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeKeyCode();
+
+ if (handleNavigation(keycode, event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
}
+ }
- @Override
- public void addMouseListener(MouseListener listener) {
- if (mouseListeners == null) {
- mouseListeners = new MouseListenerCollection();
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ public void onKeyPress(KeyPressEvent event) {
+ if (handleNavigation(event.getNativeEvent().getKeyCode(), event
+ .isControlKeyDown()
+ || event.isMetaKeyDown(), event.isShiftKeyDown())) {
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Get the first valid weekday column index of this month
+ *
+ * @return A column index or zero if not found
+ */
+ private int getFirstWeekdayColumn() {
+ Widget day = null;
+ for (int col = 1; col <= 7; col++) {
+ day = days.getWidget(1, col);
+ if (day != null) {
+ return col;
}
- mouseListeners.add(listener);
}
+ return 0;
+ }
- @Override
- public void removeMouseListener(MouseListener listener) {
- if (mouseListeners != null) {
- mouseListeners.remove(listener);
+ /**
+ * Get the last valid weekday column index of this month
+ *
+ * @return A column index or zero if not found
+ */
+ private int[] getLastWeekdayColumn() {
+ Widget day = null;
+ for (int row = days.getRowCount() - 1; row >= 1; row--) {
+ for (int col = 7; col >= 1; col--) {
+ day = days.getWidget(row, col);
+ if (day != null) {
+ return new int[] { row, col };
+ }
}
}
+ return new int[] { 0, 0 };
+ }
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- switch (DOM.eventGetType(event)) {
- case Event.ONMOUSEDOWN:
- case Event.ONMOUSEUP:
- case Event.ONMOUSEOUT:
- if (mouseListeners != null) {
- mouseListeners.fireMouseEvent(this, event);
+ /**
+ * Handles the keyboard navigation when the resolution is set to years.
+ *
+ * @param keycode
+ * The keycode to process
+ * @param ctrl
+ * Is ctrl pressed?
+ * @param shift
+ * is shift pressed
+ * @return Returns true if the keycode was processed, else false
+ */
+ protected boolean handleNavigationYearMode(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Ctrl and Shift selection not supported
+ if (ctrl || shift) {
+ return false;
+ }
+
+ else if (keycode == getPreviousKey()) {
+ selectNextYear(10); // Add 10 years
+ return true;
+ }
+
+ else if (keycode == getForwardKey()) {
+ selectNextYear(1); // Add 1 year
+ return true;
+ }
+
+ else if (keycode == getNextKey()) {
+ selectPreviousYear(10); // Subtract 10 years
+ return true;
+ }
+
+ else if (keycode == getBackwardKey()) {
+ selectPreviousYear(1); // Subtract 1 year
+ return true;
+
+ } else if (keycode == getSelectKey()) {
+
+ // We need to notify the clickhandler of the selection
+ dateClickHandler.selectDate(String.valueOf(datefield
+ .getShowingDate().getDay()));
+
+ // Send changes to server
+ notifyServerOfChanges();
+
+ if (datefield instanceof VTextualDate) {
+ ((VTextualDate) datefield).focus();
+ }
+ return true;
+
+ } else if (keycode == getResetKey()) {
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+ showing.setTime(current.getTime());
+ updateCalendar();
+ return true;
+
+ } else if (keycode == getCloseKey()) {
+ if (datefield instanceof VPopupCalendar) {
+
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+
+ if (current != null && current != null) {
+ showing.setTime(current.getTime());
}
- break;
+
+ // Close popup..
+ ((VPopupCalendar) datefield).closeCalendarPanel();
+
+ // ..and focus the textfield
+ ((VPopupCalendar) datefield).focus();
}
+
+ return true;
}
+ return false;
}
- private class DateClickHandler implements ClickHandler {
+ /**
+ * Handle the keyboard navigation when the resolution is set to MONTH
+ *
+ * @param keycode
+ * The keycode to handle
+ * @param ctrl
+ * Was the ctrl key pressed?
+ * @param shift
+ * Was the shift key pressed?
+ * @return
+ */
+ protected boolean handleNavigationMonthMode(int keycode, boolean ctrl,
+ boolean shift) {
- private final VCalendarPanel cal;
+ // Ctrl selection not supported
+ if (ctrl) {
+ return false;
- public DateClickHandler(VCalendarPanel panel) {
- cal = panel;
+ } else if (keycode == getPreviousKey()) {
+ selectNextYear(1); // Add 1 year
+ return true;
+
+ } else if (keycode == getForwardKey()) {
+ selectNextMonth(); // Add 1 month
+ return true;
+
+ } else if (keycode == getNextKey()) {
+ selectPreviousYear(1); // Subtract 1 year
+ return true;
+
+ } else if (keycode == getBackwardKey()) {
+ selectPreviousMonth(); // Subtract 1 month
+ return true;
+
+ } else if (keycode == getSelectKey()) {
+
+ // We need to notify the clickhandler of the selection
+ dateClickHandler.selectDate(String.valueOf(datefield
+ .getShowingDate().getDay()));
+
+ // Send changes to server
+ notifyServerOfChanges();
+
+ if (datefield instanceof VTextualDate) {
+ ((VTextualDate) datefield).focus();
+ }
+
+ return true;
+
+ } else if (keycode == getResetKey()) {
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+ showing.setTime(current.getTime());
+ updateCalendar();
+ return true;
+
+ } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
+ if (datefield instanceof VPopupCalendar) {
+
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+
+ if (current != null && current != null) {
+ showing.setTime(current.getTime());
+ }
+
+ // Close popup..
+ ((VPopupCalendar) datefield).closeCalendarPanel();
+
+ // ..and focus the textfield
+ ((VPopupCalendar) datefield).focus();
+ }
+
+ return true;
}
- public void onClick(ClickEvent event) {
- Object sender = event.getSource();
- Cell cell = cal.days.getCellForEvent(event);
- if (sender != cal.days || cell == null || cell.getRowIndex() < 1
- || cell.getRowIndex() > 6 || !cal.datefield.isEnabled()
- || cal.datefield.isReadonly() || cell.getCellIndex() < 1) {
- return;
+ return false;
+ }
+
+ /**
+ * Handle keyboard navigation what the resolution is set to DAY
+ *
+ * @param keycode
+ * The keycode to handle
+ * @param ctrl
+ * Was the ctrl key pressed?
+ * @param shift
+ * Was the shift key pressed?
+ * @return Return true if the key press was handled by the method, else
+ * return false.
+ */
+ protected boolean handleNavigationDayMode(int keycode, boolean ctrl,
+ boolean shift) {
+
+ // Ctrl key is not in use
+ if (ctrl) {
+ return false;
+ }
+
+ Date showingDate = datefield.getShowingDate();
+ Widget currentSelection = days.getWidget(selectedRow, selectedColumn);
+
+ /*
+ * Jumps to the next day.
+ */
+ if (keycode == getForwardKey() && !shift) {
+ // Calculate new showing date
+ Date newCurrentDate = new Date(showingDate.getYear(), showingDate
+ .getMonth(), showingDate.getDate(), showingDate.getHours(),
+ showingDate.getMinutes(), showingDate.getSeconds());
+ newCurrentDate.setDate(newCurrentDate.getDate() + 1);
+
+ if (newCurrentDate.getMonth() == showingDate.getMonth()) {
+ // Month did not change, only move the selection
+
+ showingDate.setDate(showingDate.getDate() + 1);
+
+ if (currentSelection != null) {
+ currentSelection.removeStyleDependentName("selected");
+
+ // Calculate new selection
+ if (selectedColumn == 7) {
+ selectedColumn = 1;
+ selectedRow++;
+ } else {
+ selectedColumn++;
+ }
+ }
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
+
+ } else {
+ // If the month changed we need to re-render the calendar
+ showingDate.setDate(showingDate.getDate() + 1);
+ updateCalendarOnly();
+
+ selectedRow = 1;
+ selectedColumn = getFirstWeekdayColumn();
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
}
- final String text = cal.days.getText(cell.getRowIndex(), cell
- .getCellIndex());
- if (text.equals(" ")) {
- return;
+ return true;
+
+ /*
+ * Jumps to the previous day
+ */
+ } else if (keycode == getBackwardKey() && !shift) {
+ // Calculate new showing date
+ Date newCurrentDate = new Date(showingDate.getYear(), showingDate
+ .getMonth(), showingDate.getDate(), showingDate.getHours(),
+ showingDate.getMinutes(), showingDate.getSeconds());
+ newCurrentDate.setDate(newCurrentDate.getDate() - 1);
+
+ if (newCurrentDate.getMonth() == showingDate.getMonth()) {
+ // Month did not change, only move the selection
+
+ showingDate.setDate(showingDate.getDate() - 1);
+
+ if (currentSelection != null) {
+ currentSelection.removeStyleDependentName("selected");
+
+ // Calculate new selection
+ if (selectedColumn == 1) {
+ selectedColumn = 7;
+ selectedRow--;
+ } else {
+ selectedColumn--;
+ }
+
+ }
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
+
+ } else {
+ // If the month changed we need to re-render the calendar
+ showingDate.setDate(showingDate.getDate() - 1);
+ updateCalendarOnly();
+
+ int[] pos = getLastWeekdayColumn();
+ selectedRow = pos[0];
+ selectedColumn = pos[1];
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
}
- try {
- final Integer day = new Integer(text);
- final Date newDate = cal.datefield.getShowingDate();
- newDate.setDate(day.intValue());
- if (!isEnabledDate(newDate)) {
- return;
+ return true;
+
+ /*
+ * Jumps one week back in the calendar
+ */
+ } else if (keycode == getPreviousKey() && !shift) {
+ // Calculate new showing date
+ Date newCurrentDate = new Date(showingDate.getYear(), showingDate
+ .getMonth(), showingDate.getDate(), showingDate.getHours(),
+ showingDate.getMinutes(), showingDate.getSeconds());
+ newCurrentDate.setDate(newCurrentDate.getDate() - 7);
+
+ if (newCurrentDate.getMonth() == showingDate.getMonth()
+ && selectedRow > 1) {
+ // Month did not change, only move the selection
+
+ showingDate.setDate(showingDate.getDate() - 7);
+
+ if (currentSelection != null) {
+ currentSelection.removeStyleDependentName("selected");
}
- if (cal.datefield.getCurrentDate() == null) {
- cal.datefield.setCurrentDate(new Date(newDate.getTime()));
- // Init variables with current time
- datefield.getClient().updateVariable(cal.datefield.getId(),
- "hour", newDate.getHours(), false);
- datefield.getClient().updateVariable(cal.datefield.getId(),
- "min", newDate.getMinutes(), false);
- datefield.getClient().updateVariable(cal.datefield.getId(),
- "sec", newDate.getSeconds(), false);
- datefield.getClient().updateVariable(cal.datefield.getId(),
- "msec", datefield.getMilliseconds(), false);
+ selectedRow--;
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
+
+ } else {
+ // If the month changed we need to re-render the calendar
+ showingDate.setDate(showingDate.getDate() - 7);
+ updateCalendarOnly();
+
+ int[] pos = getLastWeekdayColumn();
+ selectedRow = pos[0];
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ if (currentSelection == null) {
+ selectedRow--;
+ currentSelection = days.getWidget(selectedRow,
+ selectedColumn);
}
- cal.datefield.getCurrentDate().setTime(newDate.getTime());
- cal.datefield.getClient().updateVariable(cal.datefield.getId(),
- "day", cal.datefield.getCurrentDate().getDate(), false);
- cal.datefield.getClient().updateVariable(cal.datefield.getId(),
- "month", cal.datefield.getCurrentDate().getMonth() + 1,
- false);
- cal.datefield.getClient().updateVariable(cal.datefield.getId(),
- "year",
- cal.datefield.getCurrentDate().getYear() + 1900,
- cal.datefield.isImmediate());
+ currentSelection.addStyleDependentName("selected");
+ }
- if (datefield instanceof VTextualDate
- && resolution < VDateField.RESOLUTION_HOUR) {
- ((VOverlay) getParent()).hide();
- } else {
- updateCalendar();
+ return true;
+
+ /*
+ * Jumps one week forward in the calendar
+ */
+ } else if (keycode == getNextKey() && !ctrl && !shift) {
+ // Calculate new showing date
+ Date newCurrentDate = new Date(showingDate.getYear(), showingDate
+ .getMonth(), showingDate.getDate(), showingDate.getHours(),
+ showingDate.getMinutes(), showingDate.getSeconds());
+ newCurrentDate.setDate(newCurrentDate.getDate() + 7);
+
+ if (newCurrentDate.getMonth() == showingDate.getMonth()) {
+ // Month did not change, only move the selection
+
+ showingDate.setDate(showingDate.getDate() + 7);
+
+ if (currentSelection != null) {
+ currentSelection.removeStyleDependentName("selected");
}
- } catch (final NumberFormatException e) {
- // Not a number, ignore and stop here
- return;
+ selectedRow++;
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ currentSelection.addStyleDependentName("selected");
+
+ } else {
+ // If the month changed we need to re-render the calendar
+ showingDate.setDate(showingDate.getDate() + 7);
+ updateCalendarOnly();
+
+ selectedRow = 1;
+
+ // Set new selection
+ currentSelection = days.getWidget(selectedRow, selectedColumn);
+ if (currentSelection == null) {
+ selectedRow++;
+ currentSelection = days.getWidget(selectedRow,
+ selectedColumn);
+ }
+
+ currentSelection.addStyleDependentName("selected");
}
- }
+ return true;
+
+ /*
+ * Selects the date that is chosen
+ */
+ } else if (keycode == getSelectKey() && !shift) {
+ InlineHTML selection = (InlineHTML) days.getWidget(selectedRow,
+ selectedColumn);
+ dateClickHandler.selectDate(selection.getText());
+
+ if (datefield instanceof VTextualDate) {
+ ((VTextualDate) datefield).focus();
+ }
+
+ return true;
+
+ /*
+ * Closes the date popup
+ */
+ } else if (keycode == getCloseKey() || keycode == KeyCodes.KEY_TAB) {
+ if (datefield instanceof VPopupCalendar) {
+
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+
+ if (current != null && current != null) {
+ showing.setTime(current.getTime());
+ }
+
+ // Close popup..
+ ((VPopupCalendar) datefield).closeCalendarPanel();
+
+ // ..and focus the textfield
+ ((VPopupCalendar) datefield).focus();
+
+ }
+
+ return true;
+
+ /*
+ * Selects the next month
+ */
+ } else if (shift && keycode == getForwardKey()) {
+ selectNextMonth();
+ return true;
+
+ /*
+ * Selects the previous month
+ */
+ } else if (shift && keycode == getBackwardKey()) {
+ selectPreviousMonth();
+ return true;
+
+ /*
+ * Selects the next year
+ */
+ } else if (shift && keycode == getPreviousKey()) {
+ selectNextYear(1);
+ return true;
+
+ /*
+ * Selects the previous year
+ */
+ } else if (shift && keycode == getNextKey()) {
+ selectPreviousYear(1);
+ return true;
+
+ /*
+ * Resets the selection
+ */
+ } else if (keycode == getResetKey() && !shift) {
+ // Restore showing date the selected date
+ Date showing = datefield.getShowingDate();
+ Date current = datefield.getCurrentDate();
+ showing.setTime(current.getTime());
+ updateCalendar();
+ return true;
+ }
+
+ return false;
}
- public void setLimits(Date min, Date max) {
- if (min != null) {
- final Date d = new Date(min.getTime());
- d.setHours(0);
- d.setMinutes(0);
- d.setSeconds(1);
- minDate = d;
- } else {
- minDate = null;
+ /**
+ * Handles the keyboard navigation
+ *
+ * @param keycode
+ * The key code that was pressed
+ * @param ctrl
+ * Was the ctrl key pressed
+ * @param shift
+ * Was the shift key pressed
+ * @return Return true if key press was handled by the component, else
+ * return false
+ */
+ @SuppressWarnings("deprecation")
+ protected boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
+ if (!datefield.isEnabled() || datefield.isReadonly() || changingView) {
+ return false;
}
- if (max != null) {
- final Date d = new Date(max.getTime());
- d.setHours(24);
- d.setMinutes(59);
- d.setSeconds(59);
- maxDate = d;
- } else {
- maxDate = null;
+
+ else if (resolution == VDateField.RESOLUTION_YEAR) {
+ return handleNavigationYearMode(keycode, ctrl, shift);
+ }
+
+ else if (resolution == VDateField.RESOLUTION_MONTH) {
+ return handleNavigationMonthMode(keycode, ctrl, shift);
}
+
+ else if (resolution == VDateField.RESOLUTION_DAY) {
+ return handleNavigationDayMode(keycode, ctrl, shift);
+ }
+
+ else {
+ // Do not close the window on tab key but move the
+ // focus down to the time
+ if (keycode == KeyCodes.KEY_TAB) {
+ time.setFocus(true);
+ return true;
+ }
+
+ return handleNavigationDayMode(keycode, ctrl, shift);
+ }
+
}
- public void setCalendarEntrySource(CalendarEntrySource entrySource) {
- this.entrySource = entrySource;
+ /**
+ * Returns the reset key which will reset the calendar to the previous
+ * selection. By default this is backspace but it can be overriden to change
+ * the key to whatever you want.
+ *
+ * @return
+ */
+ protected int getResetKey() {
+ return KeyCodes.KEY_BACKSPACE;
}
- public CalendarEntrySource getCalendarEntrySource() {
- return entrySource;
+ /**
+ * Returns the select key which selects the date. By default this is the
+ * enter key but it can be changed to whatever you like by overriding this
+ * method.
+ *
+ * @return
+ */
+ protected int getSelectKey() {
+ return KeyCodes.KEY_ENTER;
}
- public interface CalendarEntrySource {
- public List getEntries(Date date, int resolution);
+ /**
+ * Returns the key that closes the popup window if this is a VPopopCalendar.
+ * Else this does nothing. By default this is the Escape key but you can
+ * change the key to whatever you want by overriding this method.
+ *
+ * @return
+ */
+ protected int getCloseKey() {
+ return KeyCodes.KEY_ESCAPE;
+ }
+
+ /**
+ * The key that selects the next day in the calendar. By default this is the
+ * right arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getForwardKey() {
+ return KeyCodes.KEY_RIGHT;
+ }
+
+ /**
+ * The key that selects the previous day in the calendar. By default this is
+ * the left arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getBackwardKey() {
+ return KeyCodes.KEY_LEFT;
+ }
+
+ /**
+ * The key that selects the next week in the calendar. By default this is
+ * the down arrow key but by overriding this method it can be changed to
+ * whatever you like.
+ *
+ * @return
+ */
+ protected int getNextKey() {
+ return KeyCodes.KEY_DOWN;
}
/**
- * Sets focus to Calendar panel.
+ * The key that selects the previous week in the calendar. By default this
+ * is the up arrow key but by overriding this method it can be changed to
+ * whatever you like.
*
- * @param focus
+ * @return
*/
- public void setFocus(boolean focus) {
- nextYear.setFocus(focus);
+ protected int getPreviousKey() {
+ return KeyCodes.KEY_UP;
}
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java index b8a004e699..7c87927fd3 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VPopupCalendar.java @@ -6,9 +6,11 @@ package com.vaadin.terminal.gwt.client.ui; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
@@ -18,12 +20,22 @@ import com.vaadin.terminal.gwt.client.ApplicationConnection; import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;
+/**
+ * Represents a date selection component with a text field and a popup date
+ * selector.
+ *
+ * <b>Note:</b> To change the keyboard assignments used in the popup dialog you
+ * should extend <code>com.vaadin.terminal.gwt.client.ui.VCalendarPanel</code>
+ * and then pass set it by calling the
+ * <code>setCalendarPanel(VCalendarPanel panel)</code> method.
+ *
+ */
public class VPopupCalendar extends VTextualDate implements Paintable, Field,
ClickHandler, CloseHandler<PopupPanel> {
private final Button calendarToggle;
- private final VCalendarPanel calendar;
+ private VCalendarPanel calendar;
private final VOverlay popup;
private boolean open = false;
@@ -36,19 +48,31 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, calendarToggle.setStyleName(CLASSNAME + "-button");
calendarToggle.setText("");
calendarToggle.addClickHandler(this);
+ calendarToggle.getElement().setTabIndex(-1);
add(calendarToggle);
calendar = new VCalendarPanel(this);
+
popup = new VOverlay(true, true, true);
popup.setStyleName(VDateField.CLASSNAME + "-popup");
popup.setWidget(calendar);
- popup.addCloseHandler(this);
+ popup.addCloseHandler(this);
DOM.setElementProperty(calendar.getElement(), "id",
"PID_VAADIN_POPUPCAL");
+ sinkEvents(Event.ONKEYDOWN);
+
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.VTextualDate#updateFromUIDL(com.vaadin
+ * .terminal.gwt.client.UIDL,
+ * com.vaadin.terminal.gwt.client.ApplicationConnection)
+ */
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
boolean lastReadOnlyState = readonly;
@@ -64,13 +88,6 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, }
calendarToggle.setEnabled(enabled);
- // not a FocusWidget -> needs own tabindex handling
- if (uidl.hasAttribute("tabindex")) {
- // Set the same tab index as for the textfield. Tabbing then works
- // as expected.
- calendarToggle.setTabIndex(uidl.getIntAttribute("tabindex"));
- }
-
if (readonly) {
calendarToggle.addStyleName(CLASSNAME + "-button-readonly");
} else {
@@ -84,16 +101,42 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, calendarToggle.setEnabled(true);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.UIObject#setStyleName(java.lang.String)
+ */
@Override
public void setStyleName(String style) {
// make sure the style is there before size calculation
super.setStyleName(style + " " + CLASSNAME + "-popupcalendar");
}
- public void onClick(ClickEvent event) {
- if (event.getSource() == calendarToggle && !open && !readonly) {
+ /**
+ * Set the popup panel to be displayed when clicking the calendar button.
+ * This is usually used when we want to extend the VCalendarPanel and use
+ * new keyboard bindings.
+ *
+ * @param panel
+ * The custom calendar panel
+ */
+ public void setCalendarPanel(VCalendarPanel panel) {
+ if (panel != null) {
+ calendar = panel;
+ calendar.updateCalendar();
+ }
+ }
+
+ /**
+ * Opens the calendar panel popup
+ */
+ public void openCalendarPanel() {
+
+ if (!open && !readonly) {
open = true;
calendar.updateCalendar();
+
// clear previous values
popup.setWidth("");
popup.setHeight("");
@@ -140,12 +183,43 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, popup.setPopupPosition(l, t
+ calendarToggle.getOffsetHeight() + 2);
- setFocus(true);
+ /* We have to wait a while before focusing
+ * since the popup needs to be opened before
+ * we can focus
+ */
+ Timer focusTimer = new Timer() {
+ @Override
+ public void run() {
+ setFocus(true);
+ }
+ };
+
+ focusTimer.schedule(100);
}
});
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ClickHandler#onClick(com.google.gwt.event
+ * .dom.client.ClickEvent)
+ */
+ public void onClick(ClickEvent event) {
+ if (event.getSource() == calendarToggle) {
+ openCalendarPanel();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.logical.shared.CloseHandler#onClose(com.google.gwt
+ * .event.logical.shared.CloseEvent)
+ */
public void onClose(CloseEvent<PopupPanel> event) {
if (event.getSource() == popup) {
buildDate();
@@ -169,6 +243,11 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, calendar.setFocus(focus);
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#getFieldExtraWidth()
+ */
@Override
protected int getFieldExtraWidth() {
if (fieldExtraWidth < 0) {
@@ -178,6 +257,11 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, return fieldExtraWidth;
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.VTextualDate#buildDate()
+ */
@Override
protected void buildDate() {
// Save previous value
@@ -189,4 +273,41 @@ public class VPopupCalendar extends VTextualDate implements Paintable, Field, setText(previousValue);
}
}
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.VDateField#onBrowserEvent(com.google
+ * .gwt.user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(com.google.gwt.user.client.Event event) {
+ super.onBrowserEvent(event);
+ if (DOM.eventGetType(event) == Event.ONKEYDOWN
+ && event.getKeyCode() == getOpenCalenderPanelKey()) {
+ openCalendarPanel();
+ event.preventDefault();
+ }
+ }
+
+ /**
+ * Get the key code that opens the calendar panel. By default it is the down
+ * key but you can override this to be whatever you like
+ *
+ * @return
+ */
+ protected int getOpenCalenderPanelKey() {
+ return KeyCodes.KEY_DOWN;
+ }
+
+ /**
+ * Closes the open popup panel
+ */
+ public void closeCalendarPanel() {
+ if (open) {
+ popup.hide(true);
+ }
+ }
+
}
diff --git a/src/com/vaadin/terminal/gwt/client/ui/VTime.java b/src/com/vaadin/terminal/gwt/client/ui/VTime.java index 6f60037655..99cf94b4c4 100644 --- a/src/com/vaadin/terminal/gwt/client/ui/VTime.java +++ b/src/com/vaadin/terminal/gwt/client/ui/VTime.java @@ -8,13 +8,25 @@ import java.util.Date; import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
-import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.KeyDownEvent;
+import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.ListBox;
+import com.vaadin.terminal.gwt.client.BrowserInfo;
-public class VTime extends FlowPanel implements ChangeHandler {
+public class VTime extends FocusableFlowPanel implements ChangeHandler,
+ KeyPressHandler, KeyDownHandler, FocusHandler {
private final VDateField datefield;
+ private final VCalendarPanel calendar;
+
private ListBox hours;
private ListBox mins;
@@ -23,18 +35,85 @@ public class VTime extends FlowPanel implements ChangeHandler { private ListBox msec;
- private ListBox ampm;
+ private AMPMListBox ampm;
private int resolution = VDateField.RESOLUTION_HOUR;
private boolean readonly;
- public VTime(VDateField parent) {
+ /**
+ * The AM/PM Listbox yields the keyboard focus to the calendar
+ */
+ private class AMPMListBox extends ListBox {
+ public AMPMListBox() {
+ super();
+ sinkEvents(Event.ONKEYDOWN);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt
+ * .user.client.Event)
+ */
+ @Override
+ public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
+ if (event.getKeyCode() == KeyCodes.KEY_TAB) {
+ event.preventDefault();
+
+ /*
+ * Wait until the current event has been processed. If the timer
+ * is left out the focus will move to the VTime-class not the
+ * panel itself. Weird.
+ */
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ calendar.setFocus(true);
+ }
+ };
+ t.schedule(1);
+ }
+ }
+ }
+
+ /**
+ * Constructor
+ *
+ * @param parent
+ * The DateField related to this instance
+ * @param panel
+ * The panel where this this instance is embedded
+ */
+ public VTime(VDateField parent, VCalendarPanel panel) {
super();
datefield = parent;
+ calendar = panel;
setStyleName(VDateField.CLASSNAME + "-time");
+
+ /*
+ * Firefox auto-repeat works correctly only if we use a key press
+ * handler, other browsers handle it correctly when using a key down
+ * handler
+ */
+ if (BrowserInfo.get().isGecko()) {
+ addKeyPressHandler(this);
+ } else {
+ addKeyDownHandler(this);
+ }
+
+ addFocusHandler(this);
}
+ /**
+ * Constructs the ListBoxes and updates their value
+ *
+ * @param redraw
+ * Should new instances of the listboxes be created
+ */
private void buildTime(boolean redraw) {
final boolean thc = datefield.getDateTimeService().isTwelveHourClock();
if (redraw) {
@@ -47,10 +126,11 @@ public class VTime extends FlowPanel implements ChangeHandler { }
hours.addChangeHandler(this);
if (thc) {
- ampm = new ListBox();
+ ampm = new AMPMListBox();
ampm.setStyleName(VNativeSelect.CLASSNAME);
final String[] ampmText = datefield.getDateTimeService()
.getAmPmStrings();
+ calendar.setFocus(true);
ampm.addItem(ampmText[0]);
ampm.addItem(ampmText[1]);
ampm.addChangeHandler(this);
@@ -151,7 +231,7 @@ public class VTime extends FlowPanel implements ChangeHandler { }
// Update times
- Date cdate = datefield.getCurrentDate();
+ Date cdate = datefield.getShowingDate();
boolean selected = true;
if (cdate == null) {
cdate = new Date();
@@ -236,6 +316,12 @@ public class VTime extends FlowPanel implements ChangeHandler { }
+ /**
+ * Update the time ListBoxes
+ *
+ * @param redraw
+ * Should new instances of the listboxes be created
+ */
public void updateTime(boolean redraw) {
buildTime(redraw || resolution != datefield.getCurrentResolution()
|| readonly != datefield.isReadonly());
@@ -246,6 +332,13 @@ public class VTime extends FlowPanel implements ChangeHandler { readonly = datefield.isReadonly();
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.ChangeHandler#onChange(com.google.gwt
+ * .event.dom.client.ChangeEvent)
+ */
public void onChange(ChangeEvent event) {
if (datefield.getCurrentDate() == null) {
// was null on server, need to set
@@ -257,61 +350,115 @@ public class VTime extends FlowPanel implements ChangeHandler { datefield.setCurrentDate(new Date(now.getTime()));
// Init variables with current time
- datefield.getClient().updateVariable(datefield.getId(), "year",
- now.getYear() + 1900, false);
- datefield.getClient().updateVariable(datefield.getId(), "month",
- now.getMonth() + 1, false);
- datefield.getClient().updateVariable(datefield.getId(), "day",
- now.getDate(), false);
- datefield.getClient().updateVariable(datefield.getId(), "hour",
- now.getHours(), false);
- datefield.getClient().updateVariable(datefield.getId(), "min",
- now.getMinutes(), false);
- datefield.getClient().updateVariable(datefield.getId(), "sec",
- now.getSeconds(), false);
- datefield.getClient().updateVariable(datefield.getId(), "msec",
- datefield.getMilliseconds(), false);
+ notifyServerOfChanges();
}
if (event.getSource() == hours) {
int h = hours.getSelectedIndex();
if (datefield.getDateTimeService().isTwelveHourClock()) {
h = h + ampm.getSelectedIndex() * 12;
}
- datefield.getCurrentDate().setHours(h);
datefield.getShowingDate().setHours(h);
- datefield.getClient().updateVariable(datefield.getId(), "hour", h,
- datefield.isImmediate());
updateTime(false);
} else if (event.getSource() == mins) {
final int m = mins.getSelectedIndex();
- datefield.getCurrentDate().setMinutes(m);
datefield.getShowingDate().setMinutes(m);
- datefield.getClient().updateVariable(datefield.getId(), "min", m,
- datefield.isImmediate());
updateTime(false);
} else if (event.getSource() == sec) {
final int s = sec.getSelectedIndex();
- datefield.getCurrentDate().setSeconds(s);
datefield.getShowingDate().setSeconds(s);
- datefield.getClient().updateVariable(datefield.getId(), "sec", s,
- datefield.isImmediate());
updateTime(false);
} else if (event.getSource() == msec) {
final int ms = msec.getSelectedIndex();
- datefield.setMilliseconds(ms);
datefield.setShowingMilliseconds(ms);
- datefield.getClient().updateVariable(datefield.getId(), "msec", ms,
- datefield.isImmediate());
updateTime(false);
} else if (event.getSource() == ampm) {
- final int h = hours.getSelectedIndex() + ampm.getSelectedIndex()
- * 12;
- datefield.getCurrentDate().setHours(h);
+ final int h = hours.getSelectedIndex()
+ + (ampm.getSelectedIndex() * 12);
datefield.getShowingDate().setHours(h);
- datefield.getClient().updateVariable(datefield.getId(), "hour", h,
- datefield.isImmediate());
updateTime(false);
}
}
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
+ * .gwt.event.dom.client.KeyPressEvent)
+ */
+ public void onKeyPress(KeyPressEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+
+ if (calendar != null) {
+ if (keycode == calendar.getSelectKey()
+ || keycode == calendar.getCloseKey()) {
+ if (keycode == calendar.getSelectKey()) {
+ notifyServerOfChanges();
+ }
+
+ calendar.handleNavigation(keycode, event.getNativeEvent()
+ .getCtrlKey()
+ || event.getNativeEvent().getMetaKey(), event
+ .getNativeEvent().getShiftKey());
+ return;
+ }
+ }
+
+ event.stopPropagation();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
+ * .event.dom.client.KeyDownEvent)
+ */
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode != calendar.getCloseKey()
+ && keycode != calendar.getSelectKey()) {
+ event.stopPropagation();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
+ * .dom.client.FocusEvent)
+ */
+ public void onFocus(FocusEvent event) {
+ event.preventDefault();
+
+ // Delegate focus to the hour select
+ hours.setFocus(true);
+ }
+
+ /**
+ * Update the variables server side
+ */
+ public void notifyServerOfChanges() {
+ /*
+ * Just update the variables, don't send any thing. The calendar panel
+ * will make the request when the panel is closed.
+ */
+ Date now = datefield.getCurrentDate();
+ datefield.getClient().updateVariable(datefield.getId(), "year",
+ now.getYear() + 1900, false);
+ datefield.getClient().updateVariable(datefield.getId(), "month",
+ now.getMonth() + 1, false);
+ datefield.getClient().updateVariable(datefield.getId(), "day",
+ now.getDate(), false);
+ datefield.getClient().updateVariable(datefield.getId(), "hour",
+ now.getHours(), false);
+ datefield.getClient().updateVariable(datefield.getId(), "min",
+ now.getMinutes(), false);
+ datefield.getClient().updateVariable(datefield.getId(), "sec",
+ now.getSeconds(), false);
+ datefield.getClient().updateVariable(datefield.getId(), "msec",
+ datefield.getShowingMilliseconds(), false);
+ }
+
}
|