summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/build.xml6
-rw-r--r--client/ivy.xml8
-rw-r--r--client/src/com/vaadin/Vaadin.gwt.xml16
-rw-r--r--client/src/com/vaadin/client/ApplicationConfiguration.java90
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java515
-rw-r--r--client/src/com/vaadin/client/BrowserInfo.java10
-rw-r--r--client/src/com/vaadin/client/ComponentConnector.java19
-rw-r--r--client/src/com/vaadin/client/ComponentLocator.java13
-rw-r--r--client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java10
-rw-r--r--client/src/com/vaadin/client/Console.java44
-rw-r--r--client/src/com/vaadin/client/HasComponentsConnector.java1
-rw-r--r--client/src/com/vaadin/client/NullConsole.java74
-rw-r--r--client/src/com/vaadin/client/Profiler.java27
-rw-r--r--client/src/com/vaadin/client/SimpleTree.java36
-rw-r--r--client/src/com/vaadin/client/Util.java29
-rw-r--r--client/src/com/vaadin/client/VCaption.java32
-rw-r--r--client/src/com/vaadin/client/VConsole.java77
-rw-r--r--client/src/com/vaadin/client/VDebugConsole.java1041
-rw-r--r--client/src/com/vaadin/client/VErrorMessage.java3
-rw-r--r--client/src/com/vaadin/client/VLoadingIndicator.java292
-rw-r--r--client/src/com/vaadin/client/VTooltip.java268
-rw-r--r--client/src/com/vaadin/client/VUIDLBrowser.java6
-rw-r--r--client/src/com/vaadin/client/ValueMap.java4
-rw-r--r--client/src/com/vaadin/client/communication/AtmospherePushConnection.java453
-rw-r--r--client/src/com/vaadin/client/communication/PushConnection.java91
-rw-r--r--client/src/com/vaadin/client/debug/internal/DebugButton.java109
-rw-r--r--client/src/com/vaadin/client/debug/internal/ErrorNotificationHandler.java86
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchySection.java676
-rw-r--r--client/src/com/vaadin/client/debug/internal/Highlight.java210
-rw-r--r--client/src/com/vaadin/client/debug/internal/Icon.java62
-rw-r--r--client/src/com/vaadin/client/debug/internal/LogSection.java355
-rw-r--r--client/src/com/vaadin/client/debug/internal/NetworkSection.java97
-rw-r--r--client/src/com/vaadin/client/debug/internal/Section.java76
-rw-r--r--client/src/com/vaadin/client/debug/internal/VDebugWindow.java1062
-rw-r--r--client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java2
-rw-r--r--client/src/com/vaadin/client/metadata/Property.java25
-rw-r--r--client/src/com/vaadin/client/metadata/TypeDataStore.java19
-rw-r--r--client/src/com/vaadin/client/ui/AbstractClickEventHandler.java5
-rw-r--r--client/src/com/vaadin/client/ui/AbstractComponentConnector.java42
-rw-r--r--client/src/com/vaadin/client/ui/AbstractConnector.java1
-rw-r--r--client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java2
-rw-r--r--client/src/com/vaadin/client/ui/VAbsoluteLayout.java104
-rw-r--r--client/src/com/vaadin/client/ui/VButton.java12
-rw-r--r--client/src/com/vaadin/client/ui/VCalendar.java1446
-rw-r--r--client/src/com/vaadin/client/ui/VCalendarPanel.java501
-rw-r--r--client/src/com/vaadin/client/ui/VCheckBox.java24
-rw-r--r--client/src/com/vaadin/client/ui/VColorPickerArea.java1
-rw-r--r--client/src/com/vaadin/client/ui/VContextMenu.java38
-rw-r--r--client/src/com/vaadin/client/ui/VFilterSelect.java35
-rw-r--r--client/src/com/vaadin/client/ui/VFormLayout.java22
-rw-r--r--client/src/com/vaadin/client/ui/VLabel.java3
-rw-r--r--client/src/com/vaadin/client/ui/VNativeButton.java6
-rw-r--r--client/src/com/vaadin/client/ui/VOptionGroup.java8
-rw-r--r--client/src/com/vaadin/client/ui/VOptionGroupBase.java2
-rw-r--r--client/src/com/vaadin/client/ui/VPopupCalendar.java189
-rw-r--r--client/src/com/vaadin/client/ui/VPopupView.java29
-rw-r--r--client/src/com/vaadin/client/ui/VTextualDate.java35
-rw-r--r--client/src/com/vaadin/client/ui/VTree.java61
-rw-r--r--client/src/com/vaadin/client/ui/VWindow.java98
-rw-r--r--client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java5
-rw-r--r--client/src/com/vaadin/client/ui/aria/AriaHelper.java188
-rw-r--r--client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java39
-rw-r--r--client/src/com/vaadin/client/ui/aria/HandlesAriaInvalid.java (renamed from client/src/com/vaadin/client/SynchronousXHR.java)33
-rw-r--r--client/src/com/vaadin/client/ui/aria/HandlesAriaRequired.java32
-rw-r--r--client/src/com/vaadin/client/ui/button/ButtonConnector.java7
-rw-r--r--client/src/com/vaadin/client/ui/calendar/CalendarConnector.java662
-rw-r--r--client/src/com/vaadin/client/ui/calendar/VCalendarAction.java138
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java55
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java313
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java810
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java117
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java639
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java59
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java70
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java181
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java122
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java134
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java124
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java33
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java142
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java216
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java701
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java97
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java109
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java678
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java62
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java51
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java185
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java67
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java65
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java167
-rw-r--r--client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java182
-rw-r--r--client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java5
-rw-r--r--client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java2
-rw-r--r--client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java4
-rw-r--r--client/src/com/vaadin/client/ui/form/FormConnector.java5
-rw-r--r--client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java9
-rw-r--r--client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java10
-rw-r--r--client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java5
-rw-r--r--client/src/com/vaadin/client/ui/orderedlayout/Slot.java6
-rw-r--r--client/src/com/vaadin/client/ui/table/TableConnector.java10
-rw-r--r--client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java10
-rw-r--r--client/src/com/vaadin/client/ui/tree/TreeConnector.java52
-rw-r--r--client/src/com/vaadin/client/ui/ui/UIConnector.java178
-rw-r--r--client/src/com/vaadin/client/ui/window/WindowConnector.java288
105 files changed, 13701 insertions, 1974 deletions
diff --git a/client/build.xml b/client/build.xml
index d0dae91dfa..a2262eed7d 100644
--- a/client/build.xml
+++ b/client/build.xml
@@ -20,7 +20,7 @@
-->
<fileset file="${gwt.user.jar}" />
</path>
- <path id="classpath.tests.custom" />
+ <path id="classpath.test.custom" />
<target name="jar">
<property name="jar.file" location="${result.dir}/lib/${module.name}-${vaadin.version}.jar" />
@@ -69,8 +69,8 @@
</antcall>
</target>
- <target name="tests" depends="checkstyle">
- <antcall target="common.tests.run" />
+ <target name="test" depends="checkstyle">
+ <antcall target="common.test.run" />
</target>
</project> \ No newline at end of file
diff --git a/client/ivy.xml b/client/ivy.xml
index 4b56338c24..5d079537b9 100644
--- a/client/ivy.xml
+++ b/client/ivy.xml
@@ -11,7 +11,7 @@
<conf name="build" />
<conf name="build-provided" />
<conf name="ide" visibility="private" />
- <conf name="tests" />
+ <conf name="test" />
</configurations>
<publications>
<artifact type="jar" ext="jar" />
@@ -25,15 +25,15 @@
<!-- LIBRARY DEPENDENCIES (compile time) -->
<!-- Project modules -->
<dependency org="com.vaadin" name="vaadin-shared"
- rev="${vaadin.version}" conf="build,tests->build"></dependency>
+ rev="${vaadin.version}" conf="build,test->build"></dependency>
<dependency org="com.vaadin" name="vaadin-server"
- rev="${vaadin.version}" conf="build->build"></dependency>
+ rev="${vaadin.version}" conf="build,test->build"></dependency>
<!-- gwt-user dependencies -->
<dependency org="org.w3c.css" name="sac" rev="1.3" />
<dependency org="junit" name="junit" rev="4.5"
- conf="tests->default" />
+ conf="test->default" />
<dependency org="javax.validation" name="validation-api"
rev="1.0.0.GA" conf="build->default,sources" />
diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml
index dcc5b0d294..a4eb88d9b4 100644
--- a/client/src/com/vaadin/Vaadin.gwt.xml
+++ b/client/src/com/vaadin/Vaadin.gwt.xml
@@ -12,7 +12,13 @@
<inherits name="com.google.gwt.http.HTTP" />
<inherits name="com.google.gwt.json.JSON" />
-
+
+ <inherits name="com.google.gwt.logging.Logging" />
+ <!-- Firebug handler is deprecated but for some reason still enabled by default -->
+ <set-property name="gwt.logging.firebugHandler" value="DISABLED" />
+ <!-- Disable popup logging as we have our own popup logger -->
+ <set-property name="gwt.logging.popupHandler" value="DISABLED" />
+
<inherits name="com.vaadin.VaadinBrowserSpecificOverrides" />
<source path="client" />
@@ -24,10 +30,6 @@
<when-type-is class="com.google.gwt.core.client.impl.SchedulerImpl" />
</replace-with>
- <replace-with class="com.vaadin.client.VDebugConsole">
- <when-type-is class="com.vaadin.client.Console" />
- </replace-with>
-
<generate-with
class="com.vaadin.server.widgetsetutils.AcceptCriteriaFactoryGenerator">
<when-type-is class="com.vaadin.client.ui.dd.VAcceptCriterionFactory" />
@@ -39,6 +41,10 @@
class="com.vaadin.client.metadata.ConnectorBundleLoader" />
</generate-with>
+ <replace-with class="com.vaadin.client.communication.AtmospherePushConnection">
+ <when-type-is class="com.vaadin.client.communication.PushConnection" />
+ </replace-with>
+
<!-- Set vaadin.profiler to true to include profiling support in the module -->
<define-property name="vaadin.profiler" values="true,false" />
<set-property name="vaadin.profiler" value="false" />
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java
index 2291f21361..adf5e1de9d 100644
--- a/client/src/com/vaadin/client/ApplicationConfiguration.java
+++ b/client/src/com/vaadin/client/ApplicationConfiguration.java
@@ -20,6 +20,9 @@ import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
@@ -28,8 +31,15 @@ import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window;
+import com.vaadin.client.debug.internal.ErrorNotificationHandler;
+import com.vaadin.client.debug.internal.HierarchySection;
+import com.vaadin.client.debug.internal.LogSection;
+import com.vaadin.client.debug.internal.NetworkSection;
+import com.vaadin.client.debug.internal.Section;
+import com.vaadin.client.debug.internal.VDebugWindow;
import com.vaadin.client.metadata.BundleLoadCallback;
import com.vaadin.client.metadata.ConnectorBundleLoader;
import com.vaadin.client.metadata.NoDataException;
@@ -259,7 +269,16 @@ public class ApplicationConfiguration implements EntryPoint {
}
public String getThemeUri() {
- return vaadinDirUrl + "themes/" + getThemeName();
+ return getVaadinDirUrl() + "themes/" + getThemeName();
+ }
+
+ /**
+ * Gets the URL of the VAADIN directory on the server.
+ *
+ * @return the URL of the VAADIN directory
+ */
+ public String getVaadinDirUrl() {
+ return vaadinDirUrl;
}
public void setAppId(String appId) {
@@ -365,7 +384,6 @@ public class ApplicationConfiguration implements EntryPoint {
if (jsoConfiguration.getConfigBoolean("initPending") == Boolean.FALSE) {
setBrowserDetailsSent();
}
-
}
/**
@@ -546,32 +564,50 @@ public class ApplicationConfiguration implements EntryPoint {
enableIOS6castFix();
}
- // Prepare VConsole for debugging
+ // Prepare the debugging window
if (isDebugMode()) {
- Console console = GWT.create(Console.class);
- console.setQuietMode(isQuietDebugMode());
- console.init();
- VConsole.setImplementation(console);
- } else {
- VConsole.setImplementation((Console) GWT.create(NullConsole.class));
- }
- /*
- * Display some sort of error of exceptions in web mode to debug
- * console. After this, exceptions are reported to VConsole and possible
- * GWT hosted mode.
- */
- GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+ /*
+ * XXX Lots of implementation details here right now. This should be
+ * cleared up when an API for extending the debug window is
+ * implemented.
+ */
+ VDebugWindow window = GWT.create(VDebugWindow.class);
- @Override
- public void onUncaughtException(Throwable e) {
- /*
- * Note in case of null console (without ?debug) we eat
- * exceptions. "a1 is not an object" style errors helps nobody,
- * especially end user. It does not work tells just as much.
- */
- VConsole.getImplementation().error(e);
+ if (LogConfiguration.loggingIsEnabled()) {
+ window.addSection((Section) GWT.create(LogSection.class));
}
- });
+ window.addSection((Section) GWT.create(HierarchySection.class));
+ window.addSection((Section) GWT.create(NetworkSection.class));
+
+ if (isQuietDebugMode()) {
+ window.close();
+ } else {
+ window.init();
+ }
+
+ // Connect to the legacy API
+ VConsole.setImplementation(window);
+
+ Handler errorNotificationHandler = GWT
+ .create(ErrorNotificationHandler.class);
+ Logger.getLogger("").addHandler(errorNotificationHandler);
+ }
+
+ if (LogConfiguration.loggingIsEnabled()) {
+ GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
+
+ @Override
+ public void onUncaughtException(Throwable e) {
+ /*
+ * If the debug window is not enabled (?debug), this will
+ * not show anything to normal users. "a1 is not an object"
+ * style errors helps nobody, especially end user. It does
+ * not work tells just as much.
+ */
+ getLogger().log(Level.SEVERE, e.getMessage(), e);
+ }
+ });
+ }
Profiler.leave("ApplicationConfiguration.onModuleLoad");
if (SuperDevMode.enableBasedOnParameter()) {
@@ -679,4 +715,8 @@ public class ApplicationConfiguration implements EntryPoint {
widgetsetVersionSent = true;
}
+ private static final Logger getLogger() {
+ return Logger.getLogger(ApplicationConfiguration.class.getName());
+ }
+
}
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index 4ddbd7c39b..4141f9f2dc 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -27,6 +27,9 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import com.google.gwt.aria.client.LiveValue;
+import com.google.gwt.aria.client.RelevantValue;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
@@ -66,6 +69,7 @@ import com.vaadin.client.communication.HasJavaScriptConnectorHelper;
import com.vaadin.client.communication.JavaScriptMethodInvocation;
import com.vaadin.client.communication.JsonDecoder;
import com.vaadin.client.communication.JsonEncoder;
+import com.vaadin.client.communication.PushConnection;
import com.vaadin.client.communication.RpcManager;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.extensions.AbstractExtensionConnector;
@@ -80,6 +84,7 @@ import com.vaadin.client.ui.AbstractConnector;
import com.vaadin.client.ui.VContextMenu;
import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VNotification.HideEvent;
+import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.dd.VDragAndDropManager;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.client.ui.window.WindowConnector;
@@ -94,7 +99,7 @@ import com.vaadin.shared.ui.ui.UIConstants;
/**
* This is the client side communication "engine", managing client-server
* communication with its server side counterpart
- * com.vaadin.server.AbstractCommunicationManager.
+ * com.vaadin.server.VaadinService.
*
* Client-side connectors receive updates from the corresponding server-side
* connector (typically component) as state updates or RPC calls. The connector
@@ -155,8 +160,8 @@ public class ApplicationConnection {
*/
public static final String UIDL_REFRESH_TOKEN = "Vaadin-Refresh";
- // will hold the UIDL security key (for XSS protection) once received
- private String uidlSecurityKey = "init";
+ // will hold the CSRF token once received
+ private String csrfToken = "init";
private final HashMap<String, String> resourcesMap = new HashMap<String, String>();
@@ -177,11 +182,6 @@ public class ApplicationConnection {
private VContextMenu contextMenu = null;
- private Timer loadTimer;
- private Timer loadTimer2;
- private Timer loadTimer3;
- private Element loadElement;
-
private final UIConnector uIConnector;
protected boolean applicationRunning = false;
@@ -227,6 +227,8 @@ public class ApplicationConnection {
private final RpcManager rpcManager;
+ private PushConnection push;
+
/**
* If responseHandlingLocks contains any objects, response handling is
* suspended until the collection is empty or a timeout has occurred.
@@ -378,6 +380,8 @@ public class ApplicationConnection {
private CommunicationErrorHandler communicationErrorDelegate = null;
+ private VLoadingIndicator loadingIndicator;
+
public static class MultiStepDuration extends Duration {
private int previousStep = elapsedMillis();
@@ -404,6 +408,8 @@ public class ApplicationConnection {
layoutManager = GWT.create(LayoutManager.class);
layoutManager.setConnection(this);
tooltip = GWT.create(VTooltip.class);
+ loadingIndicator = GWT.create(VLoadingIndicator.class);
+ loadingIndicator.setConnection(this);
}
public void init(WidgetSet widgetSet, ApplicationConfiguration cnf) {
@@ -436,7 +442,7 @@ public class ApplicationConnection {
tooltip.setOwner(uIConnector.getWidget());
- showLoadingIndicator();
+ getLoadingIndicator().show();
scheduleHeartbeat();
@@ -454,6 +460,15 @@ public class ApplicationConnection {
webkitMaybeIgnoringRequests = true;
}
});
+
+ // Ensure the overlay container is added to the dom and set as a live
+ // area for assistive devices
+ Element overlayContainer = VOverlay.getOverlayContainer(this);
+ Roles.getAlertRole().setAriaLiveProperty(overlayContainer,
+ LiveValue.ASSERTIVE);
+ setOverlayContainerLabel(getUIConnector().getState().overlayContainerLabel);
+ Roles.getAlertRole().setAriaRelevantProperty(overlayContainer,
+ RelevantValue.ADDITIONS);
}
/**
@@ -656,8 +671,7 @@ public class ApplicationConnection {
}-*/;
protected void repaintAll() {
- String repainAllParameters = getRepaintAllParameters();
- makeUidlRequest("", repainAllParameters, false);
+ makeUidlRequest("", getRepaintAllParameters());
}
/**
@@ -667,7 +681,7 @@ public class ApplicationConnection {
public void analyzeLayouts() {
String params = getRepaintAllParameters() + "&"
+ ApplicationConstants.PARAM_ANALYZE_LAYOUTS + "=1";
- makeUidlRequest("", params, false);
+ makeUidlRequest("", params);
}
/**
@@ -681,7 +695,7 @@ public class ApplicationConnection {
String params = getRepaintAllParameters() + "&"
+ ApplicationConstants.PARAM_HIGHLIGHT_CONNECTOR + "="
+ serverConnector.getConnectorId();
- makeUidlRequest("", params, false);
+ makeUidlRequest("", params);
}
/**
@@ -694,14 +708,12 @@ public class ApplicationConnection {
* Contains key=value pairs joined by & characters or is empty if
* no parameters should be added. Should not start with any
* special character.
- * @param forceSync
- * true if the request should be synchronous, false otherwise
*/
protected void makeUidlRequest(final String requestData,
- final String extraParams, final boolean forceSync) {
+ final String extraParams) {
startRequest();
// Security: double cookie submission pattern
- final String payload = uidlSecurityKey + VAR_BURST_SEPARATOR
+ final String payload = getCsrfToken() + VAR_BURST_SEPARATOR
+ requestData;
VConsole.log("Making UIDL Request with params: " + payload);
String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
@@ -713,7 +725,7 @@ public class ApplicationConnection {
uri = addGetParameters(uri, UIConstants.UI_ID_PARAMETER + "="
+ configuration.getUIId());
- doUidlRequest(uri, payload, forceSync);
+ doUidlRequest(uri, payload);
}
@@ -725,143 +737,127 @@ public class ApplicationConnection {
* The URI to use for the request. May includes GET parameters
* @param payload
* The contents of the request to send
- * @param synchronous
- * true if the request should be synchronous, false otherwise
*/
- protected void doUidlRequest(final String uri, final String payload,
- final boolean synchronous) {
- if (!synchronous) {
- RequestCallback requestCallback = new RequestCallback() {
- @Override
- public void onError(Request request, Throwable exception) {
- handleCommunicationError(exception.getMessage(), -1);
- }
+ protected void doUidlRequest(final String uri, final String payload) {
+ RequestCallback requestCallback = new RequestCallback() {
+ @Override
+ public void onError(Request request, Throwable exception) {
+ handleCommunicationError(exception.getMessage(), -1);
+ }
- private void handleCommunicationError(String details,
- int statusCode) {
- if (!handleErrorInDelegate(details, statusCode)) {
- showCommunicationError(details, statusCode);
- }
- endRequest();
+ private void handleCommunicationError(String details, int statusCode) {
+ if (!handleErrorInDelegate(details, statusCode)) {
+ showCommunicationError(details, statusCode);
}
+ endRequest();
+ }
- @Override
- public void onResponseReceived(Request request,
- Response response) {
- VConsole.log("Server visit took "
- + String.valueOf((new Date()).getTime()
- - requestStartTime.getTime()) + "ms");
-
- int statusCode = response.getStatusCode();
-
- switch (statusCode) {
- case 0:
- if (retryCanceledActiveRequest) {
- /*
- * Request was most likely canceled because the
- * browser is maybe navigating away from the page.
- * Just send the request again without displaying
- * any error in case the navigation isn't carried
- * through.
- */
- retryCanceledActiveRequest = false;
- doUidlRequest(uri, payload, synchronous);
- } else {
- handleCommunicationError(
- "Invalid status code 0 (server down?)",
- statusCode);
- }
- return;
+ @Override
+ public void onResponseReceived(Request request, Response response) {
+ VConsole.log("Server visit took "
+ + String.valueOf((new Date()).getTime()
+ - requestStartTime.getTime()) + "ms");
- case 401:
- /*
- * Authorization has failed. Could be that the session
- * has timed out and the container is redirecting to a
- * login page.
- */
- showAuthenticationError("");
- endRequest();
- return;
+ int statusCode = response.getStatusCode();
- case 503:
+ switch (statusCode) {
+ case 0:
+ if (retryCanceledActiveRequest) {
/*
- * We'll assume msec instead of the usual seconds. If
- * there's no Retry-After header, handle the error like
- * a 500, as per RFC 2616 section 10.5.4.
+ * Request was most likely canceled because the browser
+ * is maybe navigating away from the page. Just send the
+ * request again without displaying any error in case
+ * the navigation isn't carried through.
*/
- String delay = response.getHeader("Retry-After");
- if (delay != null) {
- VConsole.log("503, retrying in " + delay + "msec");
- (new Timer() {
- @Override
- public void run() {
- doUidlRequest(uri, payload, synchronous);
- }
- }).schedule(Integer.parseInt(delay));
- return;
- }
+ retryCanceledActiveRequest = false;
+ doUidlRequest(uri, payload);
+ } else {
+ handleCommunicationError(
+ "Invalid status code 0 (server down?)",
+ statusCode);
}
+ return;
- if ((statusCode / 100) == 4) {
- // Handle all 4xx errors the same way as (they are
- // all permanent errors)
- showCommunicationError(
- "UIDL could not be read from server. Check servlets mappings. Error code: "
- + statusCode, statusCode);
- endRequest();
- return;
- } else if ((statusCode / 100) == 5) {
- // Something's wrong on the server, there's nothing the
- // client can do except maybe try again.
- handleCommunicationError("Server error. Error code: "
- + statusCode, statusCode);
+ case 401:
+ /*
+ * Authorization has failed. Could be that the session has
+ * timed out and the container is redirecting to a login
+ * page.
+ */
+ showAuthenticationError("");
+ endRequest();
+ return;
+
+ case 503:
+ /*
+ * We'll assume msec instead of the usual seconds. If
+ * there's no Retry-After header, handle the error like a
+ * 500, as per RFC 2616 section 10.5.4.
+ */
+ String delay = response.getHeader("Retry-After");
+ if (delay != null) {
+ VConsole.log("503, retrying in " + delay + "msec");
+ (new Timer() {
+ @Override
+ public void run() {
+ doUidlRequest(uri, payload);
+ }
+ }).schedule(Integer.parseInt(delay));
return;
}
+ }
- String contentType = response.getHeader("Content-Type");
- if (contentType == null
- || !contentType.startsWith("application/json")) {
- /*
- * A servlet filter or equivalent may have intercepted
- * the request and served non-UIDL content (for
- * instance, a login page if the session has expired.)
- * If the response contains a magic substring, do a
- * synchronous refresh. See #8241.
- */
- MatchResult refreshToken = RegExp.compile(
- UIDL_REFRESH_TOKEN + "(:\\s*(.*?))?(\\s|$)")
- .exec(response.getText());
- if (refreshToken != null) {
- redirect(refreshToken.getGroup(2));
- return;
- }
- }
+ if ((statusCode / 100) == 4) {
+ // Handle all 4xx errors the same way as (they are
+ // all permanent errors)
+ showCommunicationError(
+ "UIDL could not be read from server. Check servlets mappings. Error code: "
+ + statusCode, statusCode);
+ endRequest();
+ return;
+ } else if ((statusCode / 100) == 5) {
+ // Something's wrong on the server, there's nothing the
+ // client can do except maybe try again.
+ handleCommunicationError("Server error. Error code: "
+ + statusCode, statusCode);
+ return;
+ }
- // for(;;);[realjson]
- final String jsonText = response.getText().substring(9,
- response.getText().length() - 1);
- handleJSONText(jsonText, statusCode);
+ String contentType = response.getHeader("Content-Type");
+ if (contentType == null
+ || !contentType.startsWith("application/json")) {
+ /*
+ * A servlet filter or equivalent may have intercepted the
+ * request and served non-UIDL content (for instance, a
+ * login page if the session has expired.) If the response
+ * contains a magic substring, do a synchronous refresh. See
+ * #8241.
+ */
+ MatchResult refreshToken = RegExp.compile(
+ UIDL_REFRESH_TOKEN + "(:\\s*(.*?))?(\\s|$)").exec(
+ response.getText());
+ if (refreshToken != null) {
+ redirect(refreshToken.getGroup(2));
+ return;
+ }
}
- };
+ // for(;;);[realjson]
+ final String jsonText = response.getText().substring(9,
+ response.getText().length() - 1);
+ handleJSONText(jsonText, statusCode);
+ }
+ };
+ if (push != null) {
+ push.push(payload);
+ } else {
try {
- doAsyncUIDLRequest(uri, payload, requestCallback);
+ doAjaxRequest(uri, payload, requestCallback);
} catch (RequestException e) {
VConsole.error(e);
endRequest();
}
- } else {
- // Synchronized call, discarded response (leaving the page)
- SynchronousXHR syncXHR = (SynchronousXHR) SynchronousXHR.create();
- syncXHR.synchronousPost(uri + "&"
- + ApplicationConstants.PARAM_UNLOADBURST + "=1", payload);
- /*
- * Although we are in theory leaving the page, the page may still
- * stay open. End request properly here too. See #3289
- */
- endRequest();
}
-
}
/**
@@ -905,11 +901,12 @@ public class ApplicationConnection {
* @throws RequestException
* if the request could not be sent
*/
- protected void doAsyncUIDLRequest(String uri, String payload,
+ protected void doAjaxRequest(String uri, String payload,
RequestCallback requestCallback) throws RequestException {
RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri);
// TODO enable timeout
// rb.setTimeoutMillis(timeoutMillis);
+ // TODO this should be configurable
rb.setHeader("Content-Type", "text/plain;charset=utf-8");
rb.setRequestData(payload);
rb.setCallback(requestCallback);
@@ -1008,7 +1005,7 @@ public class ApplicationConnection {
*/
protected boolean isCSSLoaded() {
return cssLoaded
- || DOM.getElementPropertyInt(loadElement, "offsetHeight") != 0;
+ || getLoadingIndicator().getElement().getOffsetHeight() != 0;
}
/**
@@ -1042,7 +1039,7 @@ public class ApplicationConnection {
* @param details
* Optional details for debugging.
*/
- protected void showSessionExpiredError(String details) {
+ public void showSessionExpiredError(String details) {
VConsole.error("Session expired: " + details);
showError(details, configuration.getSessionExpiredError());
}
@@ -1106,25 +1103,7 @@ public class ApplicationConnection {
}
hasActiveRequest = true;
requestStartTime = new Date();
- // show initial throbber
- if (loadTimer == null) {
- loadTimer = new Timer() {
- @Override
- public void run() {
- /*
- * IE7 does not properly cancel the event with
- * loadTimer.cancel() so we have to check that we really
- * should make it visible
- */
- if (loadTimer != null) {
- showLoadingIndicator();
- }
-
- }
- };
- // First one kicks in at 300ms
- }
- loadTimer.schedule(300);
+ loadingIndicator.trigger();
eventBus.fireEvent(new RequestStartingEvent(this));
}
@@ -1145,12 +1124,13 @@ public class ApplicationConnection {
checkForPendingVariableBursts();
runPostRequestHooks(configuration.getRootPanelId());
}
+
// deferring to avoid flickering
Scheduler.get().scheduleDeferred(new Command() {
@Override
public void execute() {
if (!hasActiveRequest()) {
- hideLoadingIndicator();
+ getLoadingIndicator().hide();
// If on Liferay and session expiration management is in
// use, extend session duration on each request.
@@ -1179,7 +1159,7 @@ public class ApplicationConnection {
}
LinkedHashMap<String, MethodInvocation> nextBurst = pendingBursts
.remove(0);
- buildAndSendVariableBurst(nextBurst, false);
+ buildAndSendVariableBurst(nextBurst);
}
}
@@ -1203,54 +1183,6 @@ public class ApplicationConnection {
}
}
- private void showLoadingIndicator() {
- // show initial throbber
- if (loadElement == null) {
- loadElement = DOM.createDiv();
- DOM.setStyleAttribute(loadElement, "position", "absolute");
- DOM.appendChild(uIConnector.getWidget().getElement(), loadElement);
- VConsole.log("inserting load indicator");
- }
- DOM.setElementProperty(loadElement, "className", "v-loading-indicator");
- DOM.setStyleAttribute(loadElement, "display", "block");
- // Initialize other timers
- loadTimer2 = new Timer() {
- @Override
- public void run() {
- DOM.setElementProperty(loadElement, "className",
- "v-loading-indicator-delay");
- }
- };
- // Second one kicks in at 1500ms from request start
- loadTimer2.schedule(1200);
-
- loadTimer3 = new Timer() {
- @Override
- public void run() {
- DOM.setElementProperty(loadElement, "className",
- "v-loading-indicator-wait");
- }
- };
- // Third one kicks in at 5000ms from request start
- loadTimer3.schedule(4700);
- }
-
- private void hideLoadingIndicator() {
- if (loadTimer != null) {
- loadTimer.cancel();
- loadTimer = null;
- }
- if (loadTimer2 != null) {
- loadTimer2.cancel();
- loadTimer3.cancel();
- loadTimer2 = null;
- loadTimer3 = null;
- }
- if (loadElement != null) {
- DOM.setStyleAttribute(loadElement, "display", "none");
- }
- }
-
/**
* Checks if deferred commands are (potentially) still being executed as a
* result of an update from the server. Returns true if a deferred command
@@ -1273,19 +1205,24 @@ public class ApplicationConnection {
}
/**
+ * Returns the loading indicator used by this ApplicationConnection
+ *
+ * @return The loading indicator for this ApplicationConnection
+ */
+ public VLoadingIndicator getLoadingIndicator() {
+ return loadingIndicator;
+ }
+
+ /**
* Determines whether or not the loading indicator is showing.
*
* @return true if the loading indicator is visible
+ * @deprecated As of 7.1. Use {@link #getLoadingIndicator()} and
+ * {@link VLoadingIndicator#isVisible()}.isVisible() instead.
*/
+ @Deprecated
public boolean isLoadingIndicatorVisible() {
- if (loadElement == null) {
- return false;
- }
- if (loadElement.getStyle().getProperty("display").equals("none")) {
- return false;
- }
-
- return true;
+ return getLoadingIndicator().isVisible();
}
private static native ValueMap parseJSONResponse(String jsonText)
@@ -1332,6 +1269,14 @@ public class ApplicationConnection {
return;
}
+ /*
+ * Lock response handling to avoid a situation where something pushed
+ * from the server gets processed while waiting for e.g. lazily loaded
+ * connectors that are needed for processing the current message.
+ */
+ final Object lock = new Object();
+ suspendReponseHandling(lock);
+
VConsole.log("Handling message from server");
eventBus.fireEvent(new ResponseHandlingStartedEvent(this));
@@ -1349,7 +1294,7 @@ public class ApplicationConnection {
// Get security key
if (json.containsKey(ApplicationConstants.UIDL_SECURITY_TOKEN_ID)) {
- uidlSecurityKey = json
+ csrfToken = json
.getString(ApplicationConstants.UIDL_SECURITY_TOKEN_ID);
}
VConsole.log(" * Handling resources from server");
@@ -1545,7 +1490,12 @@ public class ApplicationConnection {
+ jsonText.length() + " characters of JSON");
VConsole.log("Referenced paintables: " + connectorMap.size());
- endRequest();
+ if (meta == null || !meta.containsKey("async")) {
+ // End the request if the received message was a response,
+ // not sent asynchronously
+ endRequest();
+ }
+ resumeResponseHandling(lock);
if (Profiler.isEnabled()) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@@ -1556,7 +1506,6 @@ public class ApplicationConnection {
}
});
}
-
}
/**
@@ -2417,6 +2366,23 @@ public class ApplicationConnection {
}
/**
+ * Removes any pending invocation of the given method from the queue
+ *
+ * @param invocation
+ * The invocation to remove
+ */
+ public void removePendingInvocations(MethodInvocation invocation) {
+ Iterator<MethodInvocation> iter = pendingInvocations.values()
+ .iterator();
+ while (iter.hasNext()) {
+ MethodInvocation mi = iter.next();
+ if (mi.equals(invocation)) {
+ iter.remove();
+ }
+ }
+ }
+
+ /**
* This method sends currently queued variable changes to server. It is
* called when immediate variable update must happen.
*
@@ -2429,7 +2395,7 @@ public class ApplicationConnection {
public void sendPendingVariableChanges() {
if (!deferedSendPending) {
deferedSendPending = true;
- Scheduler.get().scheduleDeferred(sendPendingCommand);
+ Scheduler.get().scheduleFinally(sendPendingCommand);
}
}
@@ -2444,7 +2410,7 @@ public class ApplicationConnection {
private void doSendPendingVariableChanges() {
if (applicationRunning) {
- if (hasActiveRequest()) {
+ if (hasActiveRequest() || (push != null && !push.isActive())) {
// skip empty queues if there are pending bursts to be sent
if (pendingInvocations.size() > 0 || pendingBursts.size() == 0) {
pendingBursts.add(pendingInvocations);
@@ -2453,7 +2419,7 @@ public class ApplicationConnection {
lastInvocationTag = 0;
}
} else {
- buildAndSendVariableBurst(pendingInvocations, false);
+ buildAndSendVariableBurst(pendingInvocations);
}
}
}
@@ -2467,12 +2433,9 @@ public class ApplicationConnection {
*
* @param pendingInvocations
* List of RPC method invocations to send
- * @param forceSync
- * Should we use synchronous request?
*/
private void buildAndSendVariableBurst(
- LinkedHashMap<String, MethodInvocation> pendingInvocations,
- boolean forceSync) {
+ LinkedHashMap<String, MethodInvocation> pendingInvocations) {
final StringBuffer req = new StringBuffer();
while (!pendingInvocations.isEmpty()) {
@@ -2526,12 +2489,6 @@ public class ApplicationConnection {
pendingInvocations.clear();
// Keep tag string short
lastInvocationTag = 0;
- // Append all the bursts to this synchronous request
- if (forceSync && !pendingBursts.isEmpty()) {
- pendingInvocations = pendingBursts.get(0);
- pendingBursts.remove(0);
- req.append(VAR_BURST_SEPARATOR);
- }
}
// Include the browser detail parameters if they aren't already sent
@@ -2552,7 +2509,7 @@ public class ApplicationConnection {
getConfiguration().setWidgetsetVersionSent();
}
- makeUidlRequest(req.toString(), extraParams, forceSync);
+ makeUidlRequest(req.toString(), extraParams);
}
private boolean isJavascriptRpc(MethodInvocation invocation) {
@@ -3056,7 +3013,17 @@ public class ApplicationConnection {
private ConnectorMap connectorMap = GWT.create(ConnectorMap.class);
protected String getUidlSecurityKey() {
- return uidlSecurityKey;
+ return getCsrfToken();
+ }
+
+ /**
+ * Gets the token (aka double submit cookie) that the server uses to protect
+ * against Cross Site Request Forgery attacks.
+ *
+ * @return the CSRF token string
+ */
+ public String getCsrfToken() {
+ return csrfToken;
}
/**
@@ -3309,6 +3276,14 @@ public class ApplicationConnection {
Timer forceHandleMessage = new Timer() {
@Override
public void run() {
+ if (responseHandlingLocks.isEmpty()) {
+ /*
+ * Timer fired but there's nothing to clear. This can happen
+ * with IE8 as Timer.cancel is not always effective (see GWT
+ * issue 8101).
+ */
+ return;
+ }
VConsole.log("WARNING: reponse handling was never resumed, forcibly removing locks...");
responseHandlingLocks.clear();
handlePendingMessages();
@@ -3333,9 +3308,13 @@ public class ApplicationConnection {
public void resumeResponseHandling(Object lock) {
responseHandlingLocks.remove(lock);
if (responseHandlingLocks.isEmpty()) {
- VConsole.log("No more response handling locks, handling pending requests.");
+ // Cancel timer that breaks the lock
forceHandleMessage.cancel();
- handlePendingMessages();
+
+ if (!pendingUIDLMessages.isEmpty()) {
+ VConsole.log("No more response handling locks, handling pending requests.");
+ handlePendingMessages();
+ }
}
}
@@ -3344,11 +3323,19 @@ public class ApplicationConnection {
* suspended.
*/
private void handlePendingMessages() {
- for (PendingUIDLMessage pending : pendingUIDLMessages) {
- handleUIDLMessage(pending.getStart(), pending.getJsonText(),
- pending.getJson());
+ if (!pendingUIDLMessages.isEmpty()) {
+ /*
+ * Clear the list before processing enqueued messages to support
+ * reentrancy
+ */
+ List<PendingUIDLMessage> pendingMessages = pendingUIDLMessages;
+ pendingUIDLMessages = new ArrayList<PendingUIDLMessage>();
+
+ for (PendingUIDLMessage pending : pendingMessages) {
+ handleReceivedJSONMessage(pending.getStart(),
+ pending.getJsonText(), pending.getJson());
+ }
}
- pendingUIDLMessages.clear();
}
private boolean handleErrorInDelegate(String details, int statusCode) {
@@ -3406,4 +3393,58 @@ public class ApplicationConnection {
return Util.getConnectorForElement(this, getUIConnector().getWidget(),
focusedElement);
}
+
+ /**
+ * Sets the status for the push connection.
+ *
+ * @param enabled
+ * <code>true</code> to enable the push connection;
+ * <code>false</code> to disable the push connection.
+ */
+ public void setPushEnabled(boolean enabled) {
+ if (enabled && push == null) {
+ push = GWT.create(PushConnection.class);
+ push.init(this);
+ } else if (!enabled && push != null && push.isActive()) {
+ push.disconnect(new Command() {
+ @Override
+ public void execute() {
+ push = null;
+ /*
+ * If push has been enabled again while we were waiting for
+ * the old connection to disconnect, now is the right time
+ * to open a new connection
+ */
+ if (uIConnector.getState().pushMode.isEnabled()) {
+ setPushEnabled(true);
+ }
+
+ /*
+ * Send anything that was enqueued while we waited for the
+ * connection to close
+ */
+ if (pendingInvocations.size() > 0) {
+ sendPendingVariableChanges();
+ }
+ }
+ });
+ }
+ }
+
+ public void handlePushMessage(String message) {
+ handleJSONText(message, 200);
+ }
+
+ /**
+ * Set the label of the container element, where tooltip, notification and
+ * dialgs are added to.
+ *
+ * @param overlayContainerLabel
+ * label for the container
+ */
+ public void setOverlayContainerLabel(String overlayContainerLabel) {
+ Roles.getAlertRole().setAriaLabelProperty(
+ VOverlay.getOverlayContainer(this),
+ getUIConnector().getState().overlayContainerLabel);
+ }
}
diff --git a/client/src/com/vaadin/client/BrowserInfo.java b/client/src/com/vaadin/client/BrowserInfo.java
index 5185f829fe..b3490c3ca4 100644
--- a/client/src/com/vaadin/client/BrowserInfo.java
+++ b/client/src/com/vaadin/client/BrowserInfo.java
@@ -85,6 +85,8 @@ public class BrowserInfo {
if (browserDetails.isChrome()) {
touchDevice = detectChromeTouchDevice();
+ } else if (browserDetails.isIE()) {
+ touchDevice = detectIETouchDevice();
} else {
touchDevice = detectTouchDevice();
}
@@ -100,6 +102,11 @@ public class BrowserInfo {
return ("ontouchstart" in window);
}-*/;
+ private native boolean detectIETouchDevice()
+ /*-{
+ return !!navigator.msMaxTouchPoints;
+ }-*/;
+
private native int getIEDocumentMode()
/*-{
var mode = $wnd.document.documentMode;
@@ -331,7 +338,8 @@ public class BrowserInfo {
* otherwise <code>false</code>
*/
public boolean requiresOverflowAutoFix() {
- return (getWebkitVersion() > 0 || getOperaVersion() >= 11 || isFirefox())
+ return (getWebkitVersion() > 0 || getOperaVersion() >= 11
+ || getIEVersion() >= 10 || isFirefox())
&& Util.getNativeScrollbarSize() > 0;
}
diff --git a/client/src/com/vaadin/client/ComponentConnector.java b/client/src/com/vaadin/client/ComponentConnector.java
index eecc3fda0c..f923a9dade 100644
--- a/client/src/com/vaadin/client/ComponentConnector.java
+++ b/client/src/com/vaadin/client/ComponentConnector.java
@@ -119,6 +119,11 @@ public interface ComponentConnector extends ServerConnector {
/**
* Gets the tooltip info for the given element.
+ * <p>
+ * When overriding this method, {@link #hasTooltip()} should also be
+ * overridden to return <code>true</code> in all situations where this
+ * method might return a non-empty result.
+ * </p>
*
* @param element
* The element to lookup a tooltip for
@@ -128,6 +133,20 @@ public interface ComponentConnector extends ServerConnector {
public TooltipInfo getTooltipInfo(Element element);
/**
+ * Check whether there might be a tooltip for this component. The framework
+ * will only add event listeners for automatically handling tooltips (using
+ * {@link #getTooltipInfo(Element)}) if this method returns true.
+ * <p>
+ * This is only done to optimize performance, so in cases where the status
+ * is not known, it's safer to return <code>true</code> so that there will
+ * be a tooltip handler even though it might not be needed in all cases.
+ *
+ * @return <code>true</code> if some part of the component might have a
+ * tooltip, otherwise <code>false</code>
+ */
+ public boolean hasTooltip();
+
+ /**
* Called for the active (focused) connector when a situation occurs that
* the focused connector might have buffered changes which need to be
* processed before other activity takes place.
diff --git a/client/src/com/vaadin/client/ComponentLocator.java b/client/src/com/vaadin/client/ComponentLocator.java
index 05603e8abe..af934470c2 100644
--- a/client/src/com/vaadin/client/ComponentLocator.java
+++ b/client/src/com/vaadin/client/ComponentLocator.java
@@ -28,6 +28,7 @@ import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.SubPartAware;
import com.vaadin.client.ui.VCssLayout;
import com.vaadin.client.ui.VGridLayout;
+import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.VTabsheetPanel;
import com.vaadin.client.ui.VUI;
import com.vaadin.client.ui.VWindow;
@@ -446,7 +447,10 @@ public class ComponentLocator {
return null;
}
String elementId = w.getElement().getId();
- if (elementId != null && !elementId.isEmpty()) {
+ if (elementId != null && !elementId.isEmpty()
+ && !elementId.startsWith("gwt-uid-")) {
+ // Use PID_S+id if the user has set an id but do not use it for auto
+ // generated id:s as these might not be consistent
return "PID_S" + elementId;
} else if (w instanceof VUI) {
return "";
@@ -575,7 +579,12 @@ public class ComponentLocator {
// is always 0 which indicates the widget in the active tab
widgetPosition = 0;
}
-
+ if (w instanceof VOverlay
+ && "VCalendarPanel".equals(widgetClassName)) {
+ // Vaadin 7.1 adds a wrapper for datefield popups
+ parent = (Iterable<?>) ((Iterable) parent).iterator()
+ .next();
+ }
/*
* The new grid and ordered layotus do not contain
* ChildComponentContainer widgets. This is instead simulated by
diff --git a/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java b/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java
index 56ae7c44ac..2896386933 100644
--- a/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java
+++ b/client/src/com/vaadin/client/ConnectorHierarchyChangeEvent.java
@@ -67,19 +67,17 @@ public class ConnectorHierarchyChangeEvent extends
}
/**
- * Returns the {@link HasComponentsConnector} for which this event
- * occurred.
+ * Returns the {@link HasComponentsConnector} for which this event occurred.
*
- * @return The {@link HasComponentsConnector} whose child collection
- * has changed. Never returns null.
+ * @return The {@link HasComponentsConnector} whose child collection has
+ * changed. Never returns null.
*/
public HasComponentsConnector getParent() {
return parent;
}
/**
- * Sets the {@link HasComponentsConnector} for which this event
- * occurred.
+ * Sets the {@link HasComponentsConnector} for which this event occurred.
*
* @param The
* {@link HasComponentsConnector} whose child collection has
diff --git a/client/src/com/vaadin/client/Console.java b/client/src/com/vaadin/client/Console.java
deleted file mode 100644
index aa8ef2adc5..0000000000
--- a/client/src/com/vaadin/client/Console.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client;
-
-import java.util.Set;
-
-public interface Console {
-
- public abstract void log(String msg);
-
- public abstract void log(Throwable e);
-
- public abstract void error(Throwable e);
-
- public abstract void error(String msg);
-
- public abstract void printObject(Object msg);
-
- public abstract void dirUIDL(ValueMap u, ApplicationConnection client);
-
- public abstract void printLayoutProblems(ValueMap meta,
- ApplicationConnection applicationConnection,
- Set<ComponentConnector> zeroHeightComponents,
- Set<ComponentConnector> zeroWidthComponents);
-
- public abstract void setQuietMode(boolean quietDebugMode);
-
- public abstract void init();
-
-} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/HasComponentsConnector.java b/client/src/com/vaadin/client/HasComponentsConnector.java
index 0a1a7be97b..ebc6dbcd2a 100644
--- a/client/src/com/vaadin/client/HasComponentsConnector.java
+++ b/client/src/com/vaadin/client/HasComponentsConnector.java
@@ -21,6 +21,7 @@ import java.util.List;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.HasWidgets;
import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
+import com.vaadin.ui.HasComponents;
/**
* An interface used by client-side connectors whose widget is a component
diff --git a/client/src/com/vaadin/client/NullConsole.java b/client/src/com/vaadin/client/NullConsole.java
deleted file mode 100644
index 2b70454b9d..0000000000
--- a/client/src/com/vaadin/client/NullConsole.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client;
-
-import java.util.Set;
-
-import com.google.gwt.core.client.GWT;
-
-/**
- * Client side console implementation for non-debug mode that discards all
- * messages.
- *
- */
-public class NullConsole implements Console {
-
- @Override
- public void dirUIDL(ValueMap u, ApplicationConnection conn) {
- }
-
- @Override
- public void error(String msg) {
- GWT.log(msg);
- }
-
- @Override
- public void log(String msg) {
- GWT.log(msg);
- }
-
- @Override
- public void printObject(Object msg) {
- GWT.log(msg.toString());
- }
-
- @Override
- public void printLayoutProblems(ValueMap meta,
- ApplicationConnection applicationConnection,
- Set<ComponentConnector> zeroHeightComponents,
- Set<ComponentConnector> zeroWidthComponents) {
- }
-
- @Override
- public void log(Throwable e) {
- GWT.log(e.getMessage(), e);
- }
-
- @Override
- public void error(Throwable e) {
- GWT.log(e.getMessage(), e);
- }
-
- @Override
- public void setQuietMode(boolean quietDebugMode) {
- }
-
- @Override
- public void init() {
- }
-
-}
diff --git a/client/src/com/vaadin/client/Profiler.java b/client/src/com/vaadin/client/Profiler.java
index 6e8f2f5f9a..95b3232723 100644
--- a/client/src/com/vaadin/client/Profiler.java
+++ b/client/src/com/vaadin/client/Profiler.java
@@ -387,17 +387,12 @@ public class Profiler {
StringBuilder stringBuilder = new StringBuilder();
rootNode.buildRecursiveString(stringBuilder, "");
- Console implementation = VConsole.getImplementation();
- if (implementation instanceof VDebugConsole) {
- VDebugConsole console = (VDebugConsole) implementation;
- SimpleTree tree = (SimpleTree) stack.getFirst().buildTree();
- tree.setText("Profiler data");
-
- console.showTree(tree, stringBuilder.toString());
- } else {
- VConsole.log(stringBuilder.toString());
- }
+ /*
+ * Should really output to a separate section in the debug window, but
+ * just dump it to the log for now.
+ */
+ VConsole.log(stringBuilder.toString());
Map<String, Node> totals = new HashMap<String, Node>();
rootNode.sumUpTotals(totals);
@@ -482,13 +477,11 @@ public class Profiler {
return;
}
- Console implementation = VConsole.getImplementation();
- if (implementation instanceof VDebugConsole) {
- VDebugConsole console = (VDebugConsole) implementation;
- console.showTree(tree, stringBuilder.toString());
- } else {
- VConsole.log(stringBuilder.toString());
- }
+ /*
+ * Should really output to a separate section in the debug window,
+ * but just dump it to the log for now.
+ */
+ VConsole.log(stringBuilder.toString());
}
}
diff --git a/client/src/com/vaadin/client/SimpleTree.java b/client/src/com/vaadin/client/SimpleTree.java
index d4aef4e4f7..23bdc4828f 100644
--- a/client/src/com/vaadin/client/SimpleTree.java
+++ b/client/src/com/vaadin/client/SimpleTree.java
@@ -25,16 +25,30 @@ import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
-public class SimpleTree extends ComplexPanel {
+/**
+ * @author Vaadin Ltd
+ *
+ * @deprecated as of 7.1. This class was mainly used by the old debug console
+ * but is retained for now for backwards compatibility.
+ */
+@Deprecated
+public class SimpleTree extends ComplexPanel implements HasDoubleClickHandlers {
private Element children = Document.get().createDivElement().cast();
private SpanElement handle = Document.get().createSpanElement();
private SpanElement text = Document.get().createSpanElement();
+ private HandlerManager textDoubleClickHandlerManager;
+
public SimpleTree() {
setElement(Document.get().createDivElement());
Style style = getElement().getStyle();
@@ -126,4 +140,24 @@ public class SimpleTree extends ComplexPanel {
getElement().getStyle().setPaddingLeft(3, Unit.PX);
}
+ /**
+ * {@inheritDoc} Events are not fired when double clicking child widgets.
+ */
+ @Override
+ public HandlerRegistration addDoubleClickHandler(DoubleClickHandler handler) {
+ if (textDoubleClickHandlerManager == null) {
+ textDoubleClickHandlerManager = new HandlerManager(this);
+ addDomHandler(new DoubleClickHandler() {
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ if (event.getNativeEvent().getEventTarget().cast() == text) {
+ textDoubleClickHandlerManager.fireEvent(event);
+ }
+ }
+ }, DoubleClickEvent.getType());
+ }
+ return textDoubleClickHandlerManager.addHandler(
+ DoubleClickEvent.getType(), handler);
+ }
+
}
diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java
index 2cd01b2dd8..8972670232 100644
--- a/client/src/com/vaadin/client/Util.java
+++ b/client/src/com/vaadin/client/Util.java
@@ -48,6 +48,7 @@ import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.communication.MethodInvocation;
import com.vaadin.shared.ui.ComponentStateUtil;
+import com.vaadin.shared.util.SharedUtil;
public class Util {
@@ -550,12 +551,21 @@ public class Util {
}
}
+ /**
+ * Checks if a and b are equals using {@link #equals(Object)}. Handles null
+ * values as well. Does not ensure that objects are of the same type.
+ * Assumes that the first object's equals method handle equals properly.
+ *
+ * @param a
+ * The first value to compare
+ * @param b
+ * The second value to compare
+ * @return
+ * @deprecated As of 7.1 use {@link SharedUtil#equals(Object)} instead
+ */
+ @Deprecated
public static boolean equals(Object a, Object b) {
- if (a == null) {
- return b == null;
- }
-
- return a.equals(b);
+ return SharedUtil.equals(a, b);
}
public static void updateRelativeChildrenAndSendSizeUpdateEvent(
@@ -630,6 +640,10 @@ public class Util {
/*-{
var cs = element.ownerDocument.defaultView.getComputedStyle(element);
var heightPx = cs.height;
+ if(heightPx == 'auto'){
+ // Fallback for when IE reports auto
+ heightPx = @com.vaadin.client.Util::getRequiredHeightBoundingClientRect(Lcom/google/gwt/dom/client/Element;)(element) + 'px';
+ }
var borderTopPx = cs.borderTop;
var borderBottomPx = cs.borderBottom;
var paddingTopPx = cs.paddingTop;
@@ -646,6 +660,10 @@ public class Util {
/*-{
var cs = element.ownerDocument.defaultView.getComputedStyle(element);
var widthPx = cs.width;
+ if(widthPx == 'auto'){
+ // Fallback for when IE reports auto
+ widthPx = @com.vaadin.client.Util::getRequiredWidthBoundingClientRect(Lcom/google/gwt/dom/client/Element;)(element) + 'px';
+ }
var borderLeftPx = cs.borderLeft;
var borderRightPx = cs.borderRight;
var paddingLeftPx = cs.paddingLeft;
@@ -1339,5 +1357,4 @@ public class Util {
}
}
-
}
diff --git a/client/src/com/vaadin/client/VCaption.java b/client/src/com/vaadin/client/VCaption.java
index 47287636c4..d033c2b4fe 100644
--- a/client/src/com/vaadin/client/VCaption.java
+++ b/client/src/com/vaadin/client/VCaption.java
@@ -16,6 +16,7 @@
package com.vaadin.client;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
@@ -23,6 +24,7 @@ import com.google.gwt.user.client.ui.HTML;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.Icon;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.AbstractFieldState;
import com.vaadin.shared.ComponentConstants;
@@ -95,6 +97,24 @@ public class VCaption extends HTML {
setStyleName(CLASSNAME);
}
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+
+ if (null != owner) {
+ AriaHelper.bindCaption(owner.getWidget(), getElement());
+ }
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+
+ if (null != owner) {
+ AriaHelper.bindCaption(owner.getWidget(), null);
+ }
+ }
+
/**
* Updates the caption from UIDL.
*
@@ -200,6 +220,8 @@ public class VCaption extends HTML {
removeStyleDependentName("hasdescription");
}
+ AriaHelper.handleInputRequired(owner.getWidget(), showRequired);
+
if (showRequired) {
if (requiredFieldIndicator == null) {
requiredFieldIndicator = DOM.createDiv();
@@ -209,6 +231,10 @@ public class VCaption extends HTML {
DOM.insertChild(getElement(), requiredFieldIndicator,
getInsertPosition(InsertPosition.REQUIRED));
+
+ // Hide the required indicator from assistive device
+ Roles.getTextboxRole().setAriaHiddenState(
+ requiredFieldIndicator, true);
}
} else if (requiredFieldIndicator != null) {
// Remove existing
@@ -216,6 +242,8 @@ public class VCaption extends HTML {
requiredFieldIndicator = null;
}
+ AriaHelper.handleInputInvalid(owner.getWidget(), showError);
+
if (showError) {
if (errorIndicatorElement == null) {
errorIndicatorElement = DOM.createDiv();
@@ -225,6 +253,10 @@ public class VCaption extends HTML {
DOM.insertChild(getElement(), errorIndicatorElement,
getInsertPosition(InsertPosition.ERROR));
+
+ // Hide error indicator from assistive devices
+ Roles.getTextboxRole().setAriaHiddenState(
+ errorIndicatorElement, true);
}
} else if (errorIndicatorElement != null) {
// Remove existing
diff --git a/client/src/com/vaadin/client/VConsole.java b/client/src/com/vaadin/client/VConsole.java
index db19d1a9fd..f7a7554e34 100644
--- a/client/src/com/vaadin/client/VConsole.java
+++ b/client/src/com/vaadin/client/VConsole.java
@@ -16,91 +16,67 @@
package com.vaadin.client;
import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
-import com.google.gwt.core.client.GWT;
+import com.google.gwt.logging.client.LogConfiguration;
+import com.vaadin.client.debug.internal.VDebugWindow;
/**
* A helper class to do some client side logging.
- * <p>
- * This class replaces previously used loggin style:
- * ApplicationConnection.getConsole().log("foo").
- * <p>
- * The default widgetset provides three modes for debugging:
- * <ul>
- * <li>NullConsole (Default, displays no errors at all)
- * <li>VDebugConsole ( Enabled by appending ?debug to url. Displays a floating
- * console in the browser and also prints to browsers internal console (builtin
- * or Firebug) and GWT's development mode console if available.)
- * <li>VDebugConsole in quiet mode (Enabled by appending ?debug=quiet. Same as
- * previous but without the console floating over application).
- * </ul>
- * <p>
- * Implementations can be customized with GWT deferred binding by overriding
- * NullConsole.class or VDebugConsole.class. This way developer can for example
- * build mechanism to send client side logging data to a server.
- * <p>
- * Note that logging in client side is not fully optimized away even in
- * production mode. Use logging moderately in production code to keep the size
- * of client side engine small. An exception is {@link GWT#log(String)} style
- * logging, which is available only in GWT development mode, but optimized away
- * when compiled to web mode.
- *
- *
- * TODO improve javadocs of individual methods
*
+ * @deprecated as of 7.1, use {@link Logger} from java.util.logging instead.
*/
+@Deprecated
public class VConsole {
- private static Console impl;
+ private static VDebugWindow impl;
/**
* Used by ApplicationConfiguration to initialize VConsole.
*
* @param console
*/
- static void setImplementation(Console console) {
+ static void setImplementation(VDebugWindow console) {
impl = console;
}
- /**
- * Used by ApplicationConnection to support deprecated getConsole() api.
- */
- static Console getImplementation() {
- return impl;
- }
-
public static void log(String msg) {
- if (impl != null) {
- impl.log(msg);
+ if (LogConfiguration.loggingIsEnabled(Level.INFO)) {
+ getLogger().log(Level.INFO, msg);
}
}
public static void log(Throwable e) {
- if (impl != null) {
- impl.log(e);
+ if (LogConfiguration.loggingIsEnabled(Level.INFO)) {
+ getLogger().log(Level.INFO, e.getMessage(), e);
}
}
public static void error(Throwable e) {
- if (impl != null) {
- impl.error(e);
+ if (LogConfiguration.loggingIsEnabled(Level.SEVERE)) {
+ getLogger().log(Level.SEVERE, e.getMessage(), e);
}
}
public static void error(String msg) {
- if (impl != null) {
- impl.error(msg);
+ if (LogConfiguration.loggingIsEnabled(Level.SEVERE)) {
+ getLogger().log(Level.SEVERE, msg);
}
}
public static void printObject(Object msg) {
- if (impl != null) {
- impl.printObject(msg);
+ String str;
+ if (msg == null) {
+ str = "null";
+ } else {
+ str = msg.toString();
}
+ log(str);
}
public static void dirUIDL(ValueMap u, ApplicationConnection client) {
if (impl != null) {
- impl.dirUIDL(u, client);
+ impl.uidl(client, u);
}
}
@@ -109,9 +85,12 @@ public class VConsole {
Set<ComponentConnector> zeroHeightComponents,
Set<ComponentConnector> zeroWidthComponents) {
if (impl != null) {
- impl.printLayoutProblems(meta, applicationConnection,
- zeroHeightComponents, zeroWidthComponents);
+ impl.meta(applicationConnection, meta);
}
}
+ private static Logger getLogger() {
+ return Logger.getLogger(VConsole.class.getName());
+ }
+
}
diff --git a/client/src/com/vaadin/client/VDebugConsole.java b/client/src/com/vaadin/client/VDebugConsole.java
deleted file mode 100644
index ee7505876d..0000000000
--- a/client/src/com/vaadin/client/VDebugConsole.java
+++ /dev/null
@@ -1,1041 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.client;
-
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.FontWeight;
-import com.google.gwt.dom.client.Style.Overflow;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-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.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.logical.shared.ValueChangeEvent;
-import com.google.gwt.event.logical.shared.ValueChangeHandler;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.UmbrellaException;
-import com.google.gwt.http.client.Request;
-import com.google.gwt.http.client.RequestBuilder;
-import com.google.gwt.http.client.RequestCallback;
-import com.google.gwt.http.client.RequestException;
-import com.google.gwt.http.client.Response;
-import com.google.gwt.http.client.UrlBuilder;
-import com.google.gwt.i18n.client.DateTimeFormat;
-import com.google.gwt.storage.client.Storage;
-import com.google.gwt.user.client.Cookies;
-import com.google.gwt.user.client.DOM;
-import com.google.gwt.user.client.Element;
-import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.Event.NativePreviewEvent;
-import com.google.gwt.user.client.Event.NativePreviewHandler;
-import com.google.gwt.user.client.EventPreview;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.Window.Location;
-import com.google.gwt.user.client.ui.Button;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.HTML;
-import com.google.gwt.user.client.ui.HorizontalPanel;
-import com.google.gwt.user.client.ui.Label;
-import com.google.gwt.user.client.ui.Panel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.VerticalPanel;
-import com.google.gwt.user.client.ui.Widget;
-import com.vaadin.client.ui.VLazyExecutor;
-import com.vaadin.client.ui.VNotification;
-import com.vaadin.client.ui.VOverlay;
-import com.vaadin.client.ui.ui.UIConnector;
-import com.vaadin.client.ui.window.WindowConnector;
-import com.vaadin.shared.Version;
-
-/**
- * A helper console for client side development. The debug console can also be
- * used to resolve layout issues, inspect the communication between browser and
- * the server, start GWT dev mode and restart application.
- *
- * <p>
- * This implementation is used vaadin is in debug mode (see manual) and
- * developer appends "?debug" query parameter to url. Debug information can also
- * be shown on browsers internal console only, by appending "?debug=quiet" query
- * parameter.
- * <p>
- * This implementation can be overridden with GWT deferred binding.
- *
- */
-public class VDebugConsole extends VOverlay implements Console {
-
- private final class HighlightModeHandler implements NativePreviewHandler {
- private final Label label;
-
- private HighlightModeHandler(Label label) {
- this.label = label;
- }
-
- @Override
- public void onPreviewNativeEvent(NativePreviewEvent event) {
- if (event.getTypeInt() == Event.ONKEYDOWN
- && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
- highlightModeRegistration.removeHandler();
- VUIDLBrowser.deHiglight();
- return;
- }
- if (event.getTypeInt() == Event.ONMOUSEMOVE) {
- VUIDLBrowser.deHiglight();
- Element eventTarget = Util.getElementFromPoint(event
- .getNativeEvent().getClientX(), event.getNativeEvent()
- .getClientY());
- if (getElement().isOrHasChild(eventTarget)) {
- return;
- }
-
- for (ApplicationConnection a : ApplicationConfiguration
- .getRunningApplications()) {
- ComponentConnector connector = Util.getConnectorForElement(
- a, a.getUIConnector().getWidget(), eventTarget);
- if (connector == null) {
- connector = Util.getConnectorForElement(a,
- RootPanel.get(), eventTarget);
- }
- if (connector != null) {
- String pid = connector.getConnectorId();
- VUIDLBrowser.highlight(connector);
- label.setText("Currently focused :"
- + connector.getClass() + " ID:" + pid);
- event.cancel();
- event.consume();
- event.getNativeEvent().stopPropagation();
- return;
- }
- }
- }
- if (event.getTypeInt() == Event.ONCLICK) {
- VUIDLBrowser.deHiglight();
- event.cancel();
- event.consume();
- event.getNativeEvent().stopPropagation();
- highlightModeRegistration.removeHandler();
- Element eventTarget = Util.getElementFromPoint(event
- .getNativeEvent().getClientX(), event.getNativeEvent()
- .getClientY());
- for (ApplicationConnection a : ApplicationConfiguration
- .getRunningApplications()) {
- ComponentConnector paintable = Util.getConnectorForElement(
- a, a.getUIConnector().getWidget(), eventTarget);
- if (paintable == null) {
- paintable = Util.getConnectorForElement(a,
- RootPanel.get(), eventTarget);
- }
-
- if (paintable != null) {
- a.highlightConnector(paintable);
- return;
- }
- }
- }
- event.cancel();
- }
- }
-
- private static final String POS_COOKIE_NAME = "VDebugConsolePos";
-
- private HandlerRegistration highlightModeRegistration;
-
- Element caption = DOM.createDiv();
-
- private Panel panel;
-
- private Button clear = new Button("C");
- private Button restart = new Button("R");
- private Button forceLayout = new Button("FL");
- private Button analyzeLayout = new Button("AL");
- private Button savePosition = new Button("S");
- private Button highlight = new Button("H");
- private Button connectorStats = new Button("CS");
- private CheckBox devMode = new CheckBox("Dev");
- private CheckBox superDevMode = new CheckBox("SDev");
- private CheckBox autoScroll = new CheckBox("Autoscroll ");
- private HorizontalPanel actions;
- private boolean collapsed = false;
-
- private boolean resizing;
- private int startX;
- private int startY;
- private int initialW;
- private int initialH;
-
- private boolean moving = false;
-
- private int origTop;
-
- private int origLeft;
-
- private static final String help = "Drag title=move, shift-drag=resize, doubleclick title=min/max."
- + "Use debug=quiet to log only to browser console.";
-
- private static final int DEFAULT_WIDTH = 650;
- private static final int DEFAULT_HEIGHT = 400;
-
- public VDebugConsole() {
- super(false, false);
- getElement().getStyle().setOverflow(Overflow.HIDDEN);
- clear.setTitle("Clear console");
- restart.setTitle("Restart app");
- forceLayout.setTitle("Force layout");
- analyzeLayout.setTitle("Analyze layouts");
- savePosition.setTitle("Save pos");
- }
-
- private EventPreview dragpreview = new EventPreview() {
-
- @Override
- public boolean onEventPreview(Event event) {
- onBrowserEvent(event);
- return false;
- }
- };
-
- private boolean quietMode;
-
- @Override
- public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
- switch (DOM.eventGetType(event)) {
- case Event.ONMOUSEDOWN:
- if (DOM.eventGetShiftKey(event)) {
- resizing = true;
- DOM.setCapture(getElement());
- startX = DOM.eventGetScreenX(event);
- startY = DOM.eventGetScreenY(event);
- initialW = VDebugConsole.this.getOffsetWidth();
- initialH = VDebugConsole.this.getOffsetHeight();
- DOM.eventCancelBubble(event, true);
- DOM.eventPreventDefault(event);
- DOM.addEventPreview(dragpreview);
- } else if (DOM.eventGetTarget(event) == caption) {
- moving = true;
- startX = DOM.eventGetScreenX(event);
- startY = DOM.eventGetScreenY(event);
- origTop = getAbsoluteTop();
- origLeft = getAbsoluteLeft();
- DOM.eventCancelBubble(event, true);
- DOM.eventPreventDefault(event);
- DOM.addEventPreview(dragpreview);
- }
-
- break;
- case Event.ONMOUSEMOVE:
- if (resizing) {
- int deltaX = startX - DOM.eventGetScreenX(event);
- int detalY = startY - DOM.eventGetScreenY(event);
- int w = initialW - deltaX;
- if (w < 30) {
- w = 30;
- }
- int h = initialH - detalY;
- if (h < 40) {
- h = 40;
- }
- VDebugConsole.this.setPixelSize(w, h);
- DOM.eventCancelBubble(event, true);
- DOM.eventPreventDefault(event);
- } else if (moving) {
- int deltaX = startX - DOM.eventGetScreenX(event);
- int detalY = startY - DOM.eventGetScreenY(event);
- int left = origLeft - deltaX;
- if (left < 0) {
- left = 0;
- }
- int top = origTop - detalY;
- if (top < 0) {
- top = 0;
- }
- VDebugConsole.this.setPopupPosition(left, top);
- DOM.eventCancelBubble(event, true);
- DOM.eventPreventDefault(event);
- }
- break;
- case Event.ONLOSECAPTURE:
- case Event.ONMOUSEUP:
- if (resizing) {
- DOM.releaseCapture(getElement());
- resizing = false;
- } else if (moving) {
- DOM.releaseCapture(getElement());
- moving = false;
- }
- DOM.removeEventPreview(dragpreview);
- break;
- case Event.ONDBLCLICK:
- if (DOM.eventGetTarget(event) == caption) {
- if (collapsed) {
- panel.setVisible(true);
- setToDefaultSizeAndPos();
- } else {
- panel.setVisible(false);
- setPixelSize(120, 20);
- setPopupPosition(Window.getClientWidth() - 125,
- Window.getClientHeight() - 25);
- }
- collapsed = !collapsed;
- }
- break;
- default:
- break;
- }
-
- }
-
- private void setToDefaultSizeAndPos() {
- String cookie = Cookies.getCookie(POS_COOKIE_NAME);
- int width, height, top, left;
- boolean autoScrollValue = false;
- if (cookie != null) {
- String[] split = cookie.split(",");
- left = Integer.parseInt(split[0]);
- top = Integer.parseInt(split[1]);
- width = Integer.parseInt(split[2]);
- height = Integer.parseInt(split[3]);
- autoScrollValue = Boolean.valueOf(split[4]);
- } else {
- int windowHeight = Window.getClientHeight();
- int windowWidth = Window.getClientWidth();
- width = DEFAULT_WIDTH;
- height = DEFAULT_HEIGHT;
-
- if (height > windowHeight / 2) {
- height = windowHeight / 2;
- }
- if (width > windowWidth / 2) {
- width = windowWidth / 2;
- }
-
- top = windowHeight - (height + 10);
- left = windowWidth - (width + 10);
- }
- setPixelSize(width, height);
- setPopupPosition(left, top);
- autoScroll.setValue(autoScrollValue);
- }
-
- @Override
- public void setPixelSize(int width, int height) {
- if (height < 20) {
- height = 20;
- }
- if (width < 2) {
- width = 2;
- }
- panel.setHeight((height - 20) + "px");
- panel.setWidth((width - 2) + "px");
- getElement().getStyle().setWidth(width, Unit.PX);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Console#log(java.lang.String)
- */
- @Override
- public void log(String msg) {
- if (msg == null) {
- msg = "null";
- }
- msg = addTimestamp(msg);
- // remoteLog(msg);
-
- logToDebugWindow(msg, false);
- GWT.log(msg);
- consoleLog(msg);
- System.out.println(msg);
- }
-
- private List<String> msgQueue = new LinkedList<String>();
-
- private ScheduledCommand doSend = new ScheduledCommand() {
- @Override
- public void execute() {
- if (!msgQueue.isEmpty()) {
- RequestBuilder requestBuilder = new RequestBuilder(
- RequestBuilder.POST, getRemoteLogUrl());
- try {
- String requestData = "";
- for (String str : msgQueue) {
- requestData += str;
- requestData += "\n";
- }
- requestBuilder.sendRequest(requestData,
- new RequestCallback() {
-
- @Override
- public void onResponseReceived(Request request,
- Response response) {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void onError(Request request,
- Throwable exception) {
- // TODO Auto-generated method stub
-
- }
- });
- } catch (RequestException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- msgQueue.clear();
- }
- }
-
- };
- private VLazyExecutor sendToRemoteLog = new VLazyExecutor(350, doSend);
-
- protected String getRemoteLogUrl() {
- return "http://sun-vehje.local:8080/remotelog/";
- }
-
- protected void remoteLog(String msg) {
- msgQueue.add(msg);
- sendToRemoteLog.trigger();
- }
-
- /**
- * Logs the given message to the debug window.
- *
- * @param msg
- * The message to log. Must not be null.
- */
- private void logToDebugWindow(String msg, boolean error) {
- Widget row;
- if (error) {
- row = createErrorHtml(msg);
- } else {
- row = new HTML(msg);
- }
- panel.add(row);
- if (autoScroll.getValue()) {
- row.getElement().scrollIntoView();
- }
- }
-
- private HTML createErrorHtml(String msg) {
- HTML html = new HTML(msg);
- html.getElement().getStyle().setColor("#f00");
- html.getElement().getStyle().setFontWeight(FontWeight.BOLD);
- return html;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Console#error(java.lang.String)
- */
- @Override
- public void error(String msg) {
- if (msg == null) {
- msg = "null";
- }
- msg = addTimestamp(msg);
- logToDebugWindow(msg, true);
-
- GWT.log(msg);
- consoleErr(msg);
- System.out.println(msg);
-
- }
-
- DateTimeFormat timestampFormat = DateTimeFormat.getFormat("HH:mm:ss:SSS");
-
- @SuppressWarnings("deprecation")
- private String addTimestamp(String msg) {
- Date date = new Date();
- String timestamp = timestampFormat.format(date);
- return timestamp + " " + msg;
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Console#printObject(java.lang. Object)
- */
- @Override
- public void printObject(Object msg) {
- String str;
- if (msg == null) {
- str = "null";
- } else {
- str = msg.toString();
- }
- panel.add((new Label(str)));
- consoleLog(str);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.client.Console#dirUIDL(com.vaadin.client.UIDL)
- */
- @Override
- public void dirUIDL(ValueMap u, ApplicationConnection client) {
- if (panel.isAttached()) {
- VUIDLBrowser vuidlBrowser = new VUIDLBrowser(u, client);
- vuidlBrowser.setText("Response:");
- panel.add(vuidlBrowser);
- }
- consoleDir(u);
- // consoleLog(u.getChildrenAsXML());
- }
-
- /**
- * Adds a {@link SimpleTree} to the console and prints a string
- * representation of the tree structure to the text console.
- *
- * @param tree
- * the simple tree to display in the console window
- * @param stringRepresentation
- * the string representation of the tree to output to the text
- * console
- */
- public void showTree(SimpleTree tree, String stringRepresentation) {
- if (panel.isAttached()) {
- panel.add(tree);
- }
- consoleLog(stringRepresentation);
- }
-
- private static native void consoleDir(ValueMap u)
- /*-{
- if($wnd.console && $wnd.console.log) {
- if($wnd.console.dir) {
- $wnd.console.dir(u);
- } else {
- $wnd.console.log(u);
- }
- }
-
- }-*/;
-
- private static native void consoleLog(String msg)
- /*-{
- if($wnd.console && $wnd.console.log) {
- $wnd.console.log(msg);
- }
- }-*/;
-
- private static native void consoleErr(String msg)
- /*-{
- if($wnd.console) {
- if ($wnd.console.error)
- $wnd.console.error(msg);
- else if ($wnd.console.log)
- $wnd.console.log(msg);
- }
- }-*/;
-
- @Override
- public void printLayoutProblems(ValueMap meta, ApplicationConnection ac,
- Set<ComponentConnector> zeroHeightComponents,
- Set<ComponentConnector> zeroWidthComponents) {
- JsArray<ValueMap> valueMapArray = meta
- .getJSValueMapArray("invalidLayouts");
- int size = valueMapArray.length();
- panel.add(new HTML("<div>************************</di>"
- + "<h4>Layouts analyzed on server, total top level problems: "
- + size + " </h4>"));
- if (size > 0) {
- SimpleTree root = new SimpleTree("Root problems");
-
- for (int i = 0; i < size; i++) {
- printLayoutError(valueMapArray.get(i), root, ac);
- }
- panel.add(root);
-
- }
- if (zeroHeightComponents.size() > 0 || zeroWidthComponents.size() > 0) {
- panel.add(new HTML("<h4> Client side notifications</h4>"
- + " <em>The following relative sized components were "
- + "rendered to a zero size container on the client side."
- + " Note that these are not necessarily invalid "
- + "states, but reported here as they might be.</em>"));
- if (zeroHeightComponents.size() > 0) {
- panel.add(new HTML(
- "<p><strong>Vertically zero size:</strong><p>"));
- printClientSideDetectedIssues(zeroHeightComponents, ac);
- }
- if (zeroWidthComponents.size() > 0) {
- panel.add(new HTML(
- "<p><strong>Horizontally zero size:</strong><p>"));
- printClientSideDetectedIssues(zeroWidthComponents, ac);
- }
- }
- log("************************");
- }
-
- private void printClientSideDetectedIssues(
- Set<ComponentConnector> zeroHeightComponents,
- ApplicationConnection ac) {
- for (final ComponentConnector paintable : zeroHeightComponents) {
- final ServerConnector parent = paintable.getParent();
-
- VerticalPanel errorDetails = new VerticalPanel();
- errorDetails.add(new Label("" + Util.getSimpleName(paintable)
- + " inside " + Util.getSimpleName(parent)));
- if (parent instanceof ComponentConnector) {
- ComponentConnector parentComponent = (ComponentConnector) parent;
- final Widget layout = parentComponent.getWidget();
-
- final CheckBox emphasisInUi = new CheckBox(
- "Emphasize components parent in UI (the actual component is not visible)");
- emphasisInUi.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- Element element2 = layout.getElement();
- Widget.setStyleName(element2, "invalidlayout",
- emphasisInUi.getValue().booleanValue());
- }
- });
-
- errorDetails.add(emphasisInUi);
- }
- panel.add(errorDetails);
- }
- }
-
- private void printLayoutError(ValueMap valueMap, SimpleTree root,
- final ApplicationConnection ac) {
- final String pid = valueMap.getString("id");
- final ComponentConnector paintable = (ComponentConnector) ConnectorMap
- .get(ac).getConnector(pid);
-
- SimpleTree errorNode = new SimpleTree();
- VerticalPanel errorDetails = new VerticalPanel();
- errorDetails.add(new Label(Util.getSimpleName(paintable) + " id: "
- + pid));
- if (valueMap.containsKey("heightMsg")) {
- errorDetails.add(new Label("Height problem: "
- + valueMap.getString("heightMsg")));
- }
- if (valueMap.containsKey("widthMsg")) {
- errorDetails.add(new Label("Width problem: "
- + valueMap.getString("widthMsg")));
- }
- final CheckBox emphasisInUi = new CheckBox("Emphasize component in UI");
- emphasisInUi.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- if (paintable != null) {
- Element element2 = paintable.getWidget().getElement();
- Widget.setStyleName(element2, "invalidlayout",
- emphasisInUi.getValue());
- }
- }
- });
- errorDetails.add(emphasisInUi);
- errorNode.add(errorDetails);
- if (valueMap.containsKey("subErrors")) {
- HTML l = new HTML(
- "<em>Expand this node to show problems that may be dependent on this problem.</em>");
- errorDetails.add(l);
- JsArray<ValueMap> suberrors = valueMap
- .getJSValueMapArray("subErrors");
- for (int i = 0; i < suberrors.length(); i++) {
- ValueMap value = suberrors.get(i);
- printLayoutError(value, errorNode, ac);
- }
-
- }
- root.add(errorNode);
- }
-
- @Override
- public void log(Throwable e) {
- if (e instanceof UmbrellaException) {
- UmbrellaException ue = (UmbrellaException) e;
- for (Throwable t : ue.getCauses()) {
- log(t);
- }
- return;
- }
- log(Util.getSimpleName(e) + ": " + e.getMessage());
- GWT.log(e.getMessage(), e);
- }
-
- @Override
- public void error(Throwable e) {
- handleError(e, this);
- }
-
- static void handleError(Throwable e, Console target) {
- if (e instanceof UmbrellaException) {
- UmbrellaException ue = (UmbrellaException) e;
- for (Throwable t : ue.getCauses()) {
- target.error(t);
- }
- return;
- }
- String exceptionText = Util.getSimpleName(e);
- String message = e.getMessage();
- if (message != null && message.length() != 0) {
- exceptionText += ": " + e.getMessage();
- }
- target.error(exceptionText);
- GWT.log(e.getMessage(), e);
- if (!GWT.isProdMode()) {
- e.printStackTrace();
- }
- try {
- Widget owner = null;
-
- if (!ApplicationConfiguration.getRunningApplications().isEmpty()) {
- // Make a wild guess and use the first available
- // ApplicationConnection. This is better than than leaving the
- // exception completely unstyled...
- ApplicationConnection connection = ApplicationConfiguration
- .getRunningApplications().get(0);
- owner = connection.getUIConnector().getWidget();
- }
- VNotification
- .createNotification(VNotification.DELAY_FOREVER, owner)
- .show("<h1>Uncaught client side exception</h1><br />"
- + exceptionText, VNotification.CENTERED, "error");
- } catch (Exception e2) {
- // Just swallow this exception
- }
- }
-
- @Override
- public void init() {
- panel = new FlowPanel();
- if (!quietMode) {
- DOM.appendChild(getContainerElement(), caption);
- setWidget(panel);
- caption.setClassName("v-debug-console-caption");
- setStyleName("v-debug-console");
- getElement().getStyle().setZIndex(20000);
- getElement().getStyle().setOverflow(Overflow.HIDDEN);
-
- sinkEvents(Event.ONDBLCLICK);
-
- sinkEvents(Event.MOUSEEVENTS);
-
- panel.setStyleName("v-debug-console-content");
-
- caption.setInnerHTML("Debug window");
- caption.getStyle().setHeight(25, Unit.PX);
- caption.setTitle(help);
-
- show();
- setToDefaultSizeAndPos();
-
- actions = new HorizontalPanel();
- Style style = actions.getElement().getStyle();
- style.setPosition(Position.ABSOLUTE);
- style.setBackgroundColor("#666");
- style.setLeft(135, Unit.PX);
- style.setHeight(25, Unit.PX);
- style.setTop(0, Unit.PX);
-
- actions.add(clear);
- actions.add(restart);
- actions.add(forceLayout);
- actions.add(analyzeLayout);
- actions.add(highlight);
- actions.add(connectorStats);
- connectorStats.setTitle("Show connector statistics for client");
- highlight
- .setTitle("Select a component and print details about it to the server log and client side console.");
- actions.add(savePosition);
- savePosition
- .setTitle("Saves the position and size of debug console to a cookie");
- actions.add(autoScroll);
- addDevMode();
- addSuperDevMode();
-
- autoScroll
- .setTitle("Automatically scroll so that new messages are visible");
-
- panel.add(actions);
-
- panel.add(new HTML("<i>" + help + "</i>"));
-
- clear.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- int width = panel.getOffsetWidth();
- int height = panel.getOffsetHeight();
- panel = new FlowPanel();
- panel.setPixelSize(width, height);
- panel.setStyleName("v-debug-console-content");
- panel.add(actions);
- setWidget(panel);
- }
- });
-
- restart.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
-
- String queryString = Window.Location.getQueryString();
- if (queryString != null
- && queryString.contains("restartApplications")) {
- Window.Location.reload();
- } else {
- String url = Location.getHref();
- String separator = "?";
- if (url.contains("?")) {
- separator = "&";
- }
- if (!url.contains("restartApplication")) {
- url += separator;
- url += "restartApplication";
- }
- if (!"".equals(Location.getHash())) {
- String hash = Location.getHash();
- url = url.replace(hash, "") + hash;
- }
- Window.Location.replace(url);
- }
-
- }
- });
-
- forceLayout.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- for (ApplicationConnection applicationConnection : ApplicationConfiguration
- .getRunningApplications()) {
- applicationConnection.forceLayout();
- }
- }
- });
-
- analyzeLayout.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- List<ApplicationConnection> runningApplications = ApplicationConfiguration
- .getRunningApplications();
- for (ApplicationConnection applicationConnection : runningApplications) {
- applicationConnection.analyzeLayouts();
- }
- }
- });
- analyzeLayout
- .setTitle("Analyzes currently rendered view and "
- + "reports possible common problems in usage of relative sizes."
- + "Will cause server visit/rendering of whole screen and loss of"
- + " all non committed variables form client side.");
-
- savePosition.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- String pos = getAbsoluteLeft() + "," + getAbsoluteTop()
- + "," + getOffsetWidth() + "," + getOffsetHeight()
- + "," + autoScroll.getValue();
- Cookies.setCookie(POS_COOKIE_NAME, pos);
- }
- });
-
- highlight.addClickHandler(new ClickHandler() {
-
- @Override
- public void onClick(ClickEvent event) {
- final Label label = new Label("--");
- log("<i>Use mouse to select a component or click ESC to exit highlight mode.</i>");
- panel.add(label);
- highlightModeRegistration = Event
- .addNativePreviewHandler(new HighlightModeHandler(
- label));
-
- }
- });
-
- }
- connectorStats.addClickHandler(new ClickHandler() {
-
- @Override
- public void onClick(ClickEvent event) {
- for (ApplicationConnection a : ApplicationConfiguration
- .getRunningApplications()) {
- dumpConnectorInfo(a);
- }
- }
- });
- log("Starting Vaadin client side engine. Widgetset: "
- + GWT.getModuleName());
-
- log("Widget set is built on version: " + Version.getFullVersion());
-
- logToDebugWindow("<div class=\"v-theme-version v-theme-version-"
- + Version.getFullVersion().replaceAll("\\.", "_")
- + "\">Warning: widgetset version " + Version.getFullVersion()
- + " does not seem to match theme version </div>", true);
-
- }
-
- private void addSuperDevMode() {
- final Storage sessionStorage = Storage.getSessionStorageIfSupported();
- if (sessionStorage == null) {
- return;
- }
- actions.add(superDevMode);
- if (Location.getParameter("superdevmode") != null) {
- superDevMode.setValue(true);
- }
- superDevMode.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
-
- @Override
- public void onValueChange(ValueChangeEvent<Boolean> event) {
- SuperDevMode.redirect(event.getValue());
- }
-
- });
-
- }
-
- private void addDevMode() {
- actions.add(devMode);
- if (Location.getParameter("gwt.codesvr") != null) {
- devMode.setValue(true);
- }
- devMode.addClickHandler(new ClickHandler() {
- @Override
- public void onClick(ClickEvent event) {
- if (devMode.getValue()) {
- addHMParameter();
- } else {
- removeHMParameter();
- }
- }
-
- private void addHMParameter() {
- UrlBuilder createUrlBuilder = Location.createUrlBuilder();
- createUrlBuilder.setParameter("gwt.codesvr", "localhost:9997");
- Location.assign(createUrlBuilder.buildString());
- }
-
- private void removeHMParameter() {
- UrlBuilder createUrlBuilder = Location.createUrlBuilder();
- createUrlBuilder.removeParameter("gwt.codesvr");
- Location.assign(createUrlBuilder.buildString());
-
- }
- });
- }
-
- protected void dumpConnectorInfo(ApplicationConnection a) {
- UIConnector root = a.getUIConnector();
- log("================");
- log("Connector hierarchy for Root: " + root.getState().caption + " ("
- + root.getConnectorId() + ")");
- Set<ServerConnector> connectorsInHierarchy = new HashSet<ServerConnector>();
- SimpleTree rootHierachy = dumpConnectorHierarchy(root, "",
- connectorsInHierarchy);
- if (panel.isAttached()) {
- rootHierachy.open(true);
- panel.add(rootHierachy);
- }
-
- ConnectorMap connectorMap = a.getConnectorMap();
- Collection<? extends ServerConnector> registeredConnectors = connectorMap
- .getConnectors();
- log("Sub windows:");
- Set<ServerConnector> subWindowHierarchyConnectors = new HashSet<ServerConnector>();
- for (WindowConnector wc : root.getSubWindows()) {
- SimpleTree windowHierachy = dumpConnectorHierarchy(wc, "",
- subWindowHierarchyConnectors);
- if (panel.isAttached()) {
- windowHierachy.open(true);
- panel.add(windowHierachy);
- }
- }
- log("Registered connectors not in hierarchy (should be empty):");
- for (ServerConnector registeredConnector : registeredConnectors) {
-
- if (connectorsInHierarchy.contains(registeredConnector)) {
- continue;
- }
-
- if (subWindowHierarchyConnectors.contains(registeredConnector)) {
- continue;
- }
- error(getConnectorString(registeredConnector));
-
- }
- log("Unregistered connectors in hierarchy (should be empty):");
- for (ServerConnector hierarchyConnector : connectorsInHierarchy) {
- if (!connectorMap.hasConnector(hierarchyConnector.getConnectorId())) {
- error(getConnectorString(hierarchyConnector));
- }
-
- }
-
- log("================");
-
- }
-
- private SimpleTree dumpConnectorHierarchy(final ServerConnector connector,
- String indent, Set<ServerConnector> connectors) {
- SimpleTree simpleTree = new SimpleTree(getConnectorString(connector)) {
- @Override
- protected void select(ClickEvent event) {
- super.select(event);
- if (connector instanceof ComponentConnector) {
- VUIDLBrowser.highlight((ComponentConnector) connector);
- }
- }
- };
- simpleTree.addDomHandler(new MouseOutHandler() {
- @Override
- public void onMouseOut(MouseOutEvent event) {
- VUIDLBrowser.deHiglight();
- }
- }, MouseOutEvent.getType());
- connectors.add(connector);
-
- String msg = indent + "* " + getConnectorString(connector);
- GWT.log(msg);
- consoleLog(msg);
- System.out.println(msg);
-
- for (ServerConnector c : connector.getChildren()) {
- simpleTree.add(dumpConnectorHierarchy(c, indent + " ", connectors));
- }
- return simpleTree;
- }
-
- private static String getConnectorString(ServerConnector connector) {
- return Util.getConnectorString(connector);
- }
-
- @Override
- public void setQuietMode(boolean quietDebugMode) {
- quietMode = quietDebugMode;
- }
-}
diff --git a/client/src/com/vaadin/client/VErrorMessage.java b/client/src/com/vaadin/client/VErrorMessage.java
index a384b451dd..2e42b98a05 100644
--- a/client/src/com/vaadin/client/VErrorMessage.java
+++ b/client/src/com/vaadin/client/VErrorMessage.java
@@ -31,6 +31,9 @@ public class VErrorMessage extends FlowPanel {
public VErrorMessage() {
super();
setStyleName(CLASSNAME);
+
+ // Needed for binding with WAI-ARIA attributes
+ getElement().setId(DOM.createUniqueId());
}
/**
diff --git a/client/src/com/vaadin/client/VLoadingIndicator.java b/client/src/com/vaadin/client/VLoadingIndicator.java
new file mode 100644
index 0000000000..fcce35781d
--- /dev/null
+++ b/client/src/com/vaadin/client/VLoadingIndicator.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client;
+
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Timer;
+
+/**
+ * Class representing the loading indicator for Vaadin applications. The loading
+ * indicator has four states: "triggered", "first", "second" and "third". When
+ * {@link #trigger()} is called the indicator moves to its "triggered" state and
+ * then transitions from one state to the next when the timeouts specified using
+ * the set*StateDelay methods occur.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class VLoadingIndicator {
+
+ private static final String PRIMARY_STYLE_NAME = "v-loading-indicator";
+
+ private ApplicationConnection connection;
+
+ private int firstDelay = 300;
+ private int secondDelay = 1500;
+ private int thirdDelay = 5000;
+
+ /**
+ * Timer with method for checking if it has been cancelled. This class is a
+ * workaround for a IE8 problem which causes a timer to be fired even if it
+ * has been cancelled.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+ private abstract static class LoadingIndicatorTimer extends Timer {
+ private boolean cancelled = false;
+
+ @Override
+ public void cancel() {
+ super.cancel();
+ cancelled = true;
+ }
+
+ @Override
+ public void schedule(int delayMillis) {
+ super.schedule(delayMillis);
+ cancelled = false;
+ }
+
+ @Override
+ public void scheduleRepeating(int periodMillis) {
+ super.scheduleRepeating(periodMillis);
+ cancelled = false;
+ }
+
+ /**
+ * Checks if this timer has been cancelled.
+ *
+ * @return true if the timer has been cancelled, false otherwise
+ */
+ public boolean isCancelled() {
+ return cancelled;
+ }
+ }
+
+ private Timer firstTimer = new LoadingIndicatorTimer() {
+ @Override
+ public void run() {
+ if (isCancelled()) {
+ // IE8 does not properly cancel the timer in all cases.
+ return;
+ }
+ show();
+ }
+ };
+ private Timer secondTimer = new LoadingIndicatorTimer() {
+ @Override
+ public void run() {
+ if (isCancelled()) {
+ // IE8 does not properly cancel the timer in all cases.
+ return;
+ }
+ getElement().setClassName(PRIMARY_STYLE_NAME);
+ getElement().addClassName("second");
+ // For backwards compatibility only
+ getElement().addClassName(PRIMARY_STYLE_NAME + "-delay");
+ }
+ };
+ private Timer thirdTimer = new LoadingIndicatorTimer() {
+ @Override
+ public void run() {
+ if (isCancelled()) {
+ // IE8 does not properly cancel the timer in all cases.
+ return;
+ }
+ getElement().setClassName(PRIMARY_STYLE_NAME);
+ getElement().addClassName("third");
+ // For backwards compatibility only
+ getElement().addClassName(PRIMARY_STYLE_NAME + "-wait");
+ }
+ };
+
+ private Element element;
+
+ /**
+ * Returns the delay (in ms) which must pass before the loading indicator
+ * moves into the "first" state and is shown to the user
+ *
+ * @return The delay (in ms) until moving into the "first" state. Counted
+ * from when {@link #trigger()} is called.
+ */
+ public int getFirstDelay() {
+ return firstDelay;
+ }
+
+ /**
+ * Sets the delay (in ms) which must pass before the loading indicator moves
+ * into the "first" state and is shown to the user
+ *
+ * @param firstDelay
+ * The delay (in ms) until moving into the "first" state. Counted
+ * from when {@link #trigger()} is called.
+ */
+ public void setFirstDelay(int firstDelay) {
+ this.firstDelay = firstDelay;
+ }
+
+ /**
+ * Returns the delay (in ms) which must pass before the loading indicator
+ * moves to its "second" state.
+ *
+ * @return The delay (in ms) until the loading indicator moves into its
+ * "second" state. Counted from when {@link #trigger()} is called.
+ */
+ public int getSecondDelay() {
+ return secondDelay;
+ }
+
+ /**
+ * Sets the delay (in ms) which must pass before the loading indicator moves
+ * to its "second" state.
+ *
+ * @param secondDelay
+ * The delay (in ms) until the loading indicator moves into its
+ * "second" state. Counted from when {@link #trigger()} is
+ * called.
+ */
+ public void setSecondDelay(int secondDelay) {
+ this.secondDelay = secondDelay;
+ }
+
+ /**
+ * Returns the delay (in ms) which must pass before the loading indicator
+ * moves to its "third" state.
+ *
+ * @return The delay (in ms) until the loading indicator moves into its
+ * "third" state. Counted from when {@link #trigger()} is called.
+ */
+ public int getThirdDelay() {
+ return thirdDelay;
+ }
+
+ /**
+ * Sets the delay (in ms) which must pass before the loading indicator moves
+ * to its "third" state.
+ *
+ * @param thirdDelay
+ * The delay (in ms) from the event until changing the loading
+ * indicator into its "third" state. Counted from when
+ * {@link #trigger()} is called.
+ */
+ public void setThirdDelay(int thirdDelay) {
+ this.thirdDelay = thirdDelay;
+ }
+
+ /**
+ * Triggers displaying of this loading indicator. The loading indicator will
+ * actually be shown by {@link #show()} when the "first" delay (as specified
+ * by {@link #getFirstDelay()}) has passed.
+ * <p>
+ * The loading indicator will be hidden if shown when calling this method.
+ * </p>
+ */
+ public void trigger() {
+ hide();
+ firstTimer.schedule(getFirstDelay());
+ }
+
+ /**
+ * Shows the loading indicator in its standard state and triggers timers for
+ * transitioning into the "second" and "third" states.
+ */
+ public void show() {
+ // Reset possible style name and display mode
+ getElement().setClassName(PRIMARY_STYLE_NAME);
+ getElement().addClassName("first");
+ getElement().getStyle().setDisplay(Display.BLOCK);
+
+ // Schedule the "second" loading indicator
+ int secondTimerDelay = getSecondDelay() - getFirstDelay();
+ if (secondTimerDelay >= 0) {
+ secondTimer.schedule(secondTimerDelay);
+ }
+
+ // Schedule the "third" loading indicator
+ int thirdTimerDelay = getThirdDelay() - getFirstDelay();
+ if (thirdTimerDelay >= 0) {
+ thirdTimer.schedule(thirdTimerDelay);
+ }
+ }
+
+ /**
+ * Returns the {@link ApplicationConnection} which uses this loading
+ * indicator
+ *
+ * @return The ApplicationConnection for this loading indicator
+ */
+ public ApplicationConnection getConnection() {
+ return connection;
+ }
+
+ /**
+ * Sets the {@link ApplicationConnection} which uses this loading indicator.
+ * Only used internally.
+ *
+ * @param connection
+ * The ApplicationConnection for this loading indicator
+ */
+ void setConnection(ApplicationConnection connection) {
+ this.connection = connection;
+ }
+
+ /**
+ * Hides the loading indicator (if visible). Cancels any possibly running
+ * timers.
+ */
+ public void hide() {
+ firstTimer.cancel();
+ secondTimer.cancel();
+ thirdTimer.cancel();
+
+ getElement().getStyle().setDisplay(Display.NONE);
+ }
+
+ /**
+ * Returns whether or not the loading indicator is showing.
+ *
+ * @return true if the loading indicator is visible, false otherwise
+ */
+ public boolean isVisible() {
+ if (getElement().getStyle().getDisplay()
+ .equals(Display.NONE.getCssName())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the root element of the loading indicator
+ *
+ * @return The loading indicator DOM element
+ */
+ public Element getElement() {
+ if (element == null) {
+ element = DOM.createDiv();
+ element.getStyle().setPosition(Position.ABSOLUTE);
+ getConnection().getUIConnector().getWidget().getElement()
+ .appendChild(element);
+ }
+ return element;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java
index 759b90a8cd..6191821988 100644
--- a/client/src/com/vaadin/client/VTooltip.java
+++ b/client/src/com/vaadin/client/VTooltip.java
@@ -15,8 +15,15 @@
*/
package com.vaadin.client;
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.dom.client.FocusEvent;
+import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
@@ -40,11 +47,6 @@ public class VTooltip extends VOverlay {
public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN
| Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEMOVE
| Event.ONCLICK;
- protected static final int MAX_WIDTH = 500;
- private static final int QUICK_OPEN_TIMEOUT = 1000;
- private static final int CLOSE_TIMEOUT = 300;
- private static final int OPEN_DELAY = 750;
- private static final int QUICK_OPEN_DELAY = 100;
VErrorMessage em = new VErrorMessage();
Element description = DOM.createDiv();
@@ -54,6 +56,16 @@ public class VTooltip extends VOverlay {
// Open next tooltip faster. Disabled after 2 sec of showTooltip-silence.
private boolean justClosed = false;
+ private String uniqueId = DOM.createUniqueId();
+ private Element layoutElement;
+ private int maxWidth;
+
+ // Delays for the tooltip, configurable on the server side
+ private int openDelay;
+ private int quickOpenDelay;
+ private int quickOpenTimeout;
+ private int closeTimeout;
+
/**
* Used to show tooltips; usually used via the singleton in
* {@link ApplicationConnection}. NOTE that #setOwner(Widget)} should be
@@ -68,8 +80,17 @@ public class VTooltip extends VOverlay {
setWidget(layout);
layout.add(em);
DOM.setElementProperty(description, "className", CLASSNAME + "-text");
- DOM.appendChild(layout.getElement(), description);
+
+ layoutElement = layout.getElement();
+ DOM.appendChild(layoutElement, description);
setSinkShadowEvents(true);
+
+ // Used to bind the tooltip to the owner for assistive devices
+ layoutElement.setId(uniqueId);
+
+ description.setId(DOM.createUniqueId());
+ Roles.getTooltipRole().set(layoutElement);
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
}
/**
@@ -104,8 +125,8 @@ public class VTooltip extends VOverlay {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
- if (offsetWidth > MAX_WIDTH) {
- setWidth(MAX_WIDTH + "px");
+ if (offsetWidth > getMaxWidth()) {
+ setWidth(getMaxWidth() + "px");
// Check new height and width with reflowed content
offsetWidth = getOffsetWidth();
@@ -150,7 +171,7 @@ public class VTooltip extends VOverlay {
// Schedule timer for showing the tooltip according to if it was
// recently closed or not.
- int timeout = justClosed ? QUICK_OPEN_DELAY : OPEN_DELAY;
+ int timeout = justClosed ? getQuickOpenDelay() : getOpenDelay();
showTimer.schedule(timeout);
opening = true;
}
@@ -200,19 +221,31 @@ public class VTooltip extends VOverlay {
// already about to close
return;
}
- closeTimer.schedule(CLOSE_TIMEOUT);
+ closeTimer.schedule(getCloseTimeout());
closing = true;
justClosed = true;
- justClosedTimer.schedule(QUICK_OPEN_TIMEOUT);
+ justClosedTimer.schedule(getQuickOpenTimeout());
+ }
+ @Override
+ public void hide() {
+ super.hide();
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, true);
+ Roles.getTooltipRole().removeAriaDescribedbyProperty(
+ tooltipEventHandler.currentElement);
}
private int tooltipEventMouseX;
private int tooltipEventMouseY;
- public void updatePosition(Event event) {
- tooltipEventMouseX = DOM.eventGetClientX(event);
- tooltipEventMouseY = DOM.eventGetClientY(event);
+ public void updatePosition(Event event, boolean isFocused) {
+ if (isFocused) {
+ tooltipEventMouseX = -1000;
+ tooltipEventMouseY = -1000;
+ } else {
+ tooltipEventMouseX = DOM.eventGetClientX(event);
+ tooltipEventMouseY = DOM.eventGetClientY(event);
+ }
}
@Override
@@ -246,7 +279,7 @@ public class VTooltip extends VOverlay {
}
private class TooltipEventHandler implements MouseMoveHandler,
- ClickHandler, KeyDownHandler {
+ ClickHandler, KeyDownHandler, FocusHandler, BlurHandler {
/**
* Current element hovered
@@ -254,6 +287,11 @@ public class VTooltip extends VOverlay {
private com.google.gwt.dom.client.Element currentElement = null;
/**
+ * Current element focused
+ */
+ private boolean currentIsFocused;
+
+ /**
* Current tooltip active
*/
private TooltipInfo currentTooltipInfo = null;
@@ -299,6 +337,9 @@ public class VTooltip extends VOverlay {
}
if (connector != null && info != null) {
+ assert connector.hasTooltip() : "getTooltipInfo for "
+ + Util.getConnectorString(connector)
+ + " returned a tooltip even though hasTooltip claims there are no tooltips for the connector.";
currentTooltipInfo = info;
return true;
}
@@ -319,41 +360,77 @@ public class VTooltip extends VOverlay {
@Override
public void onMouseMove(MouseMoveEvent mme) {
- Event event = Event.as(mme.getNativeEvent());
+ handleShowHide(mme, false);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ handleHideEvent();
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ handleHideEvent();
+ }
+
+ /**
+ * Displays Tooltip when page is navigated with the keyboard.
+ *
+ * Tooltip is not visible. This makes it possible for assistive devices
+ * to recognize the tooltip.
+ */
+ @Override
+ public void onFocus(FocusEvent fe) {
+ handleShowHide(fe, true);
+ }
+
+ /**
+ * Hides Tooltip when the page is navigated with the keyboard.
+ *
+ * Removes the Tooltip from page to make sure assistive devices don't
+ * recognize it by accident.
+ */
+ @Override
+ public void onBlur(BlurEvent be) {
+ handleHideEvent();
+ }
+
+ private void handleShowHide(DomEvent domEvent, boolean isFocused) {
+ Event event = Event.as(domEvent.getNativeEvent());
com.google.gwt.dom.client.Element element = Element.as(event
.getEventTarget());
// We can ignore move event if it's handled by move or over already
- if (currentElement == element) {
+ if (currentElement == element && currentIsFocused == isFocused) {
return;
}
- currentElement = element;
boolean connectorAndTooltipFound = resolveConnector((com.google.gwt.user.client.Element) element);
if (!connectorAndTooltipFound) {
if (isShowing()) {
handleHideEvent();
+ Roles.getButtonRole()
+ .removeAriaDescribedbyProperty(element);
} else {
currentTooltipInfo = null;
}
} else {
- updatePosition(event);
+ updatePosition(event, isFocused);
+
if (isShowing()) {
replaceCurrentTooltip();
+ Roles.getTooltipRole().removeAriaDescribedbyProperty(
+ currentElement);
} else {
showTooltip();
}
- }
- }
- @Override
- public void onClick(ClickEvent event) {
- handleHideEvent();
- }
+ Roles.getTooltipRole().setAriaDescribedbyProperty(element,
+ Id.of(uniqueId));
+ }
- @Override
- public void onKeyDown(KeyDownEvent event) {
- handleHideEvent();
+ currentIsFocused = isFocused;
+ currentElement = element;
}
}
@@ -370,6 +447,141 @@ public class VTooltip extends VOverlay {
widget.addDomHandler(tooltipEventHandler, MouseMoveEvent.getType());
widget.addDomHandler(tooltipEventHandler, ClickEvent.getType());
widget.addDomHandler(tooltipEventHandler, KeyDownEvent.getType());
+ widget.addDomHandler(tooltipEventHandler, FocusEvent.getType());
+ widget.addDomHandler(tooltipEventHandler, BlurEvent.getType());
Profiler.leave("VTooltip.connectHandlersToWidget");
}
+
+ /**
+ * Returns the unique id of the tooltip element.
+ *
+ * @return String containing the unique id of the tooltip, which always has
+ * a value
+ */
+ public String getUniqueId() {
+ return uniqueId;
+ }
+
+ @Override
+ public void setPopupPositionAndShow(PositionCallback callback) {
+ super.setPopupPositionAndShow(callback);
+
+ Roles.getTooltipRole().setAriaHiddenState(layoutElement, false);
+ }
+
+ /**
+ * Returns the time (in ms) the tooltip should be displayed after an event
+ * that will cause it to be closed (e.g. mouse click outside the component,
+ * key down).
+ *
+ * @return The close timeout (in ms)
+ */
+ public int getCloseTimeout() {
+ return closeTimeout;
+ }
+
+ /**
+ * Sets the time (in ms) the tooltip should be displayed after an event that
+ * will cause it to be closed (e.g. mouse click outside the component, key
+ * down).
+ *
+ * @param closeTimeout
+ * The close timeout (in ms)
+ */
+ public void setCloseTimeout(int closeTimeout) {
+ this.closeTimeout = closeTimeout;
+ }
+
+ /**
+ * Returns the time (in ms) during which {@link #getQuickOpenDelay()} should
+ * be used instead of {@link #getOpenDelay()}. The quick open delay is used
+ * when the tooltip has very recently been shown, is currently hidden but
+ * about to be shown again.
+ *
+ * @return The quick open timeout (in ms)
+ */
+ public int getQuickOpenTimeout() {
+ return quickOpenTimeout;
+ }
+
+ /**
+ * Sets the time (in ms) that determines when {@link #getQuickOpenDelay()}
+ * should be used instead of {@link #getOpenDelay()}. The quick open delay
+ * is used when the tooltip has very recently been shown, is currently
+ * hidden but about to be shown again.
+ *
+ * @param quickOpenTimeout
+ * The quick open timeout (in ms)
+ */
+ public void setQuickOpenTimeout(int quickOpenTimeout) {
+ this.quickOpenTimeout = quickOpenTimeout;
+ }
+
+ /**
+ * Returns the time (in ms) that should elapse before a tooltip will be
+ * shown, in the situation when a tooltip has very recently been shown
+ * (within {@link #getQuickOpenDelay()} ms).
+ *
+ * @return The quick open delay (in ms)
+ */
+ public int getQuickOpenDelay() {
+ return quickOpenDelay;
+ }
+
+ /**
+ * Sets the time (in ms) that should elapse before a tooltip will be shown,
+ * in the situation when a tooltip has very recently been shown (within
+ * {@link #getQuickOpenDelay()} ms).
+ *
+ * @param quickOpenDelay
+ * The quick open delay (in ms)
+ */
+ public void setQuickOpenDelay(int quickOpenDelay) {
+ this.quickOpenDelay = quickOpenDelay;
+ }
+
+ /**
+ * Returns the time (in ms) that should elapse after an event triggering
+ * tooltip showing has occurred (e.g. mouse over) before the tooltip is
+ * shown. If a tooltip has recently been shown, then
+ * {@link #getQuickOpenDelay()} is used instead of this.
+ *
+ * @return The open delay (in ms)
+ */
+ public int getOpenDelay() {
+ return openDelay;
+ }
+
+ /**
+ * Sets the time (in ms) that should elapse after an event triggering
+ * tooltip showing has occurred (e.g. mouse over) before the tooltip is
+ * shown. If a tooltip has recently been shown, then
+ * {@link #getQuickOpenDelay()} is used instead of this.
+ *
+ * @param openDelay
+ * The open delay (in ms)
+ */
+ public void setOpenDelay(int openDelay) {
+ this.openDelay = openDelay;
+ }
+
+ /**
+ * Sets the maximum width of the tooltip popup.
+ *
+ * @param maxWidth
+ * The maximum width the tooltip popup (in pixels)
+ */
+ public void setMaxWidth(int maxWidth) {
+ this.maxWidth = maxWidth;
+ }
+
+ /**
+ * Returns the maximum width of the tooltip popup.
+ *
+ * @return The maximum width the tooltip popup (in pixels)
+ */
+ public int getMaxWidth() {
+ return maxWidth;
+ }
+
}
diff --git a/client/src/com/vaadin/client/VUIDLBrowser.java b/client/src/com/vaadin/client/VUIDLBrowser.java
index fded37ec5c..e6f38fb167 100644
--- a/client/src/com/vaadin/client/VUIDLBrowser.java
+++ b/client/src/com/vaadin/client/VUIDLBrowser.java
@@ -42,8 +42,12 @@ import com.vaadin.client.ui.UnknownComponentConnector;
import com.vaadin.client.ui.VWindow;
/**
- * TODO Rename to something more Vaadin7-ish?
+ * @author Vaadin Ltd
+ *
+ * @deprecated as of 7.1. This class was mainly used by the old debug console
+ * but is retained for now for backwards compatibility.
*/
+@Deprecated
public class VUIDLBrowser extends SimpleTree {
private static final String HELP = "Shift click handle to open recursively. "
+ " Click components to highlight them on client side."
diff --git a/client/src/com/vaadin/client/ValueMap.java b/client/src/com/vaadin/client/ValueMap.java
index 5157bc91f5..4141eaa9d6 100644
--- a/client/src/com/vaadin/client/ValueMap.java
+++ b/client/src/com/vaadin/client/ValueMap.java
@@ -70,12 +70,12 @@ public final class ValueMap extends JavaScriptObject {
return attrs;
}
- native JsArrayString getJSStringArray(String name)
+ public native JsArrayString getJSStringArray(String name)
/*-{
return this[name];
}-*/;
- native JsArray<ValueMap> getJSValueMapArray(String name)
+ public native JsArray<ValueMap> getJSValueMapArray(String name)
/*-{
return this[name];
}-*/;
diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
new file mode 100644
index 0000000000..bc7e0b3fd2
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.communication;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.user.client.Command;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ResourceLoader;
+import com.vaadin.client.ResourceLoader.ResourceLoadEvent;
+import com.vaadin.client.ResourceLoader.ResourceLoadListener;
+import com.vaadin.client.VConsole;
+import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.ui.ui.UIConstants;
+
+/**
+ * The default {@link PushConnection} implementation that uses Atmosphere for
+ * handling the communication channel.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class AtmospherePushConnection implements PushConnection {
+
+ protected enum State {
+ /**
+ * Opening request has been sent, but still waiting for confirmation
+ */
+ CONNECT_PENDING,
+
+ /**
+ * Connection is open and ready to use.
+ */
+ CONNECTED,
+
+ /**
+ * Connection was disconnected while the connection was pending. Wait
+ * for the connection to get established before closing it. No new
+ * messages are accepted, but pending messages will still be delivered.
+ */
+ DISCONNECT_PENDING,
+
+ /**
+ * Connection has been disconnected and should not be used any more.
+ */
+ DISCONNECTED;
+ }
+
+ /**
+ * Represents a message that should be sent as multiple fragments.
+ */
+ protected static class FragmentedMessage {
+
+ // Jetty requires length less than buffer size
+ private int FRAGMENT_LENGTH = ApplicationConstants.WEBSOCKET_BUFFER_SIZE - 1;
+
+ private String message;
+ private int index = 0;
+
+ public FragmentedMessage(String message) {
+ this.message = message;
+ }
+
+ public boolean hasNextFragment() {
+ return index < message.length();
+ }
+
+ public String getNextFragment() {
+ String result;
+ if (index == 0) {
+ String header = "" + message.length()
+ + ApplicationConstants.WEBSOCKET_MESSAGE_DELIMITER;
+ int fragmentLen = FRAGMENT_LENGTH - header.length();
+ result = header + getFragment(0, fragmentLen);
+ index += fragmentLen;
+ } else {
+ result = getFragment(index, index + FRAGMENT_LENGTH);
+ index += FRAGMENT_LENGTH;
+ }
+ return result;
+ }
+
+ private String getFragment(int begin, int end) {
+ return message.substring(begin, Math.min(message.length(), end));
+ }
+ }
+
+ private ApplicationConnection connection;
+
+ private JavaScriptObject socket;
+
+ private ArrayList<String> messageQueue = new ArrayList<String>();
+
+ private State state = State.CONNECT_PENDING;
+
+ private AtmosphereConfiguration config;
+
+ private String uri;
+
+ private String transport;
+
+ /**
+ * Keeps track of the disconnect confirmation command for cases where
+ * pending messages should be pushed before actually disconnecting.
+ */
+ private Command pendingDisconnectCommand;
+
+ public AtmospherePushConnection() {
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.client.communication.PushConenction#init(com.vaadin.client
+ * .ApplicationConnection)
+ */
+ @Override
+ public void init(final ApplicationConnection connection) {
+ this.connection = connection;
+
+ runWhenAtmosphereLoaded(new Command() {
+ @Override
+ public void execute() {
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ connect();
+ }
+ });
+ }
+ });
+ }
+
+ private void connect() {
+ String baseUrl = connection
+ .translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ApplicationConstants.PUSH_PATH + '/');
+ String extraParams = UIConstants.UI_ID_PARAMETER + "="
+ + connection.getConfiguration().getUIId();
+ extraParams += "&" + ApplicationConstants.CSRF_TOKEN_PARAMETER + "="
+ + connection.getCsrfToken();
+
+ // uri is needed to identify the right connection when closing
+ uri = ApplicationConnection.addGetParameters(baseUrl, extraParams);
+
+ VConsole.log("Establishing push connection");
+ socket = doConnect(uri, getConfig());
+ }
+
+ @Override
+ public boolean isActive() {
+ switch (state) {
+ case CONNECT_PENDING:
+ case CONNECTED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.client.communication.PushConenction#push(java.lang.String)
+ */
+ @Override
+ public void push(String message) {
+ switch (state) {
+ case CONNECT_PENDING:
+ assert isActive();
+ VConsole.log("Queuing push message: " + message);
+ messageQueue.add(message);
+ break;
+ case CONNECTED:
+ assert isActive();
+ VConsole.log("Sending push message: " + message);
+
+ if (transport.equals("websocket")) {
+ FragmentedMessage fragmented = new FragmentedMessage(message);
+ while (fragmented.hasNextFragment()) {
+ doPush(socket, fragmented.getNextFragment());
+ }
+ } else {
+ doPush(socket, message);
+ }
+ break;
+ case DISCONNECT_PENDING:
+ case DISCONNECTED:
+ throw new IllegalStateException("Can not push after disconnecting");
+ }
+ }
+
+ protected AtmosphereConfiguration getConfig() {
+ if (config == null) {
+ config = createConfig();
+ }
+ return config;
+ }
+
+ protected void onOpen(AtmosphereResponse response) {
+ transport = response.getTransport();
+
+ VConsole.log("Push connection established using " + transport);
+
+ switch (state) {
+ case CONNECT_PENDING:
+ state = State.CONNECTED;
+ for (String message : messageQueue) {
+ push(message);
+ }
+ messageQueue.clear();
+ break;
+ case DISCONNECT_PENDING:
+ // Set state to connected to make disconnect close the connection
+ state = State.CONNECTED;
+ assert pendingDisconnectCommand != null;
+ disconnect(pendingDisconnectCommand);
+ break;
+ case CONNECTED:
+ // IE likes to open the same connection multiple times, just ignore
+ break;
+ default:
+ throw new IllegalStateException(
+ "Got onOpen event when conncetion state is " + state
+ + ". This should never happen.");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.client.communication.PushConenction#disconnect()
+ */
+ @Override
+ public void disconnect(Command command) {
+ assert command != null;
+
+ switch (state) {
+ case CONNECT_PENDING:
+ // Make the connection callback initiate the disconnection again
+ state = State.DISCONNECT_PENDING;
+ pendingDisconnectCommand = command;
+ break;
+ case CONNECTED:
+ // Normal disconnect
+ VConsole.log("Closing push connection");
+ doDisconnect(uri);
+ state = State.DISCONNECTED;
+ command.execute();
+ break;
+ case DISCONNECT_PENDING:
+ case DISCONNECTED:
+ throw new IllegalStateException("Can not disconnect more than once");
+ }
+ }
+
+ protected void onMessage(AtmosphereResponse response) {
+ String message = response.getResponseBody();
+ if (message.startsWith("for(;;);")) {
+ VConsole.log("Received push message: " + message);
+ // "for(;;);[{json}]" -> "{json}"
+ message = message.substring(9, message.length() - 1);
+ connection.handlePushMessage(message);
+ }
+ }
+
+ /**
+ * Called if the transport mechanism cannot be used and the fallback will be
+ * tried
+ */
+ protected void onTransportFailure() {
+ VConsole.log("Push connection using primary method ("
+ + getConfig().getTransport() + ") failed. Trying with "
+ + getConfig().getFallbackTransport());
+ }
+
+ /**
+ * Called if the push connection fails. Atmosphere will automatically retry
+ * the connection until successful.
+ *
+ */
+ protected void onError() {
+ VConsole.error("Push connection using " + getConfig().getTransport()
+ + " failed!");
+ }
+
+ public static abstract class AbstractJSO extends JavaScriptObject {
+ protected AbstractJSO() {
+
+ }
+
+ protected final native String getStringValue(String key)
+ /*-{
+ return this[key];
+ }-*/;
+
+ protected final native void setStringValue(String key, String value)
+ /*-{
+ this[key] = value;
+ }-*/;
+
+ protected final native int getIntValue(String key)
+ /*-{
+ return this[key];
+ }-*/;
+
+ protected final native void setIntValue(String key, int value)
+ /*-{
+ this[key] = value;
+ }-*/;
+
+ }
+
+ public static class AtmosphereConfiguration extends AbstractJSO {
+
+ protected AtmosphereConfiguration() {
+ super();
+ }
+
+ public final String getTransport() {
+ return getStringValue("transport");
+ }
+
+ public final String getFallbackTransport() {
+ return getStringValue("fallbackTransport");
+ }
+
+ public final void setTransport(String transport) {
+ setStringValue("transport", transport);
+ }
+
+ public final void setFallbackTransport(String fallbackTransport) {
+ setStringValue("fallbackTransport", fallbackTransport);
+ }
+ }
+
+ public static class AtmosphereResponse extends AbstractJSO {
+
+ protected AtmosphereResponse() {
+
+ }
+
+ public final String getResponseBody() {
+ return getStringValue("responseBody");
+ }
+
+ public final String getState() {
+ return getStringValue("state");
+ }
+
+ public final String getError() {
+ return getStringValue("error");
+ }
+
+ public final String getTransport() {
+ return getStringValue("transport");
+ }
+
+ }
+
+ protected native AtmosphereConfiguration createConfig()
+ /*-{
+ return {
+ transport: 'websocket',
+ fallbackTransport: 'streaming',
+ contentType: 'application/json; charset=UTF-8',
+ reconnectInterval: '5000',
+ maxReconnectOnClose: 10000000,
+ trackMessageLength: true
+ };
+ }-*/;
+
+ private native JavaScriptObject doConnect(String uri,
+ JavaScriptObject config)
+ /*-{
+ var self = this;
+
+ config.url = uri;
+ config.onOpen = $entry(function(response) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onOpen(*)(response);
+ });
+ config.onMessage = $entry(function(response) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onMessage(*)(response);
+ });
+ config.onError = $entry(function(response) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onError()(response);
+ });
+ config.onTransportFailure = $entry(function(reason,request) {
+ self.@com.vaadin.client.communication.AtmospherePushConnection::onTransportFailure(*)(reason);
+ });
+
+ return $wnd.jQueryVaadin.atmosphere.subscribe(config);
+ }-*/;
+
+ private native void doPush(JavaScriptObject socket, String message)
+ /*-{
+ socket.push(message);
+ }-*/;
+
+ private static native void doDisconnect(String url)
+ /*-{
+ $wnd.jQueryVaadin.atmosphere.unsubscribeUrl(url);
+ }-*/;
+
+ private static native boolean isAtmosphereLoaded()
+ /*-{
+ return $wnd.jQueryVaadin != undefined;
+ }-*/;
+
+ private void runWhenAtmosphereLoaded(final Command command) {
+
+ if (isAtmosphereLoaded()) {
+ command.execute();
+ } else {
+ VConsole.log("Loading " + ApplicationConstants.VAADIN_PUSH_JS);
+ ResourceLoader.get().loadScript(
+ connection.getConfiguration().getVaadinDirUrl()
+ + ApplicationConstants.VAADIN_PUSH_JS,
+ new ResourceLoadListener() {
+ @Override
+ public void onLoad(ResourceLoadEvent event) {
+ VConsole.log(ApplicationConstants.VAADIN_PUSH_JS
+ + " loaded");
+ command.execute();
+ }
+
+ @Override
+ public void onError(ResourceLoadEvent event) {
+ VConsole.error(event.getResourceUrl()
+ + " could not be loaded. Push will not work.");
+ }
+ });
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/communication/PushConnection.java b/client/src/com/vaadin/client/communication/PushConnection.java
new file mode 100644
index 0000000000..61656242bd
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/PushConnection.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.communication;
+
+import com.google.gwt.user.client.Command;
+import com.vaadin.client.ApplicationConnection;
+
+/**
+ * Represents the client-side endpoint of a bidirectional ("push") communication
+ * channel. Can be used to send UIDL request messages to the server and to
+ * receive UIDL messages from the server (either asynchronously or as a response
+ * to a UIDL request.) Delegates the UIDL handling to the
+ * {@link ApplicationConnection}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public interface PushConnection {
+
+ /**
+ * Two-phase construction to allow using GWT.create().
+ *
+ * @param connection
+ * The ApplicationConnection
+ */
+ public void init(ApplicationConnection connection);
+
+ /**
+ * Pushes a message to the server. Will throw an exception if the connection
+ * is not active (see {@link #isActive()}).
+ * <p>
+ * Implementation detail: The implementation is responsible for queuing
+ * messages that are pushed after {@link #init(ApplicationConnection)} has
+ * been called but before the connection has internally been set up and then
+ * replay those messages in the original order when the connection has been
+ * established.
+ *
+ * @param message
+ * the message to push
+ * @throws IllegalStateException
+ * if this connection is not active
+ *
+ * @see #isActive()
+ */
+ public void push(String message);
+
+ /**
+ * Checks whether this push connection is in a state where it can push
+ * messages to the server. A connection is active until
+ * {@link #disconnect(Command)} has been called.
+ *
+ * @return <code>true</code> if this connection can accept new messages;
+ * <code>false</code> if this connection is disconnected or
+ * disconnecting.
+ */
+ public boolean isActive();
+
+ /**
+ * Closes the push connection. To ensure correct message delivery order, new
+ * messages should not be sent using any other channel until it has been
+ * confirmed that all messages pending for this connection have been
+ * delivered. The provided command callback is invoked when messages can be
+ * passed using some other communication channel.
+ * <p>
+ * After this method has been called, {@link #isActive()} returns
+ * <code>false</code>. Calling this method for a connection that is no
+ * longer active will throw an exception.
+ *
+ * @param command
+ * callback command invoked when the connection has been properly
+ * disconnected
+ * @throws IllegalStateException
+ * if this connection is not active
+ */
+ public void disconnect(Command command);
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/DebugButton.java b/client/src/com/vaadin/client/debug/internal/DebugButton.java
new file mode 100644
index 0000000000..a49a392fbe
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/DebugButton.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.Button;
+
+/**
+ * Simple extension of {@link Button} that is preconfigured with for use in
+ * {@link VDebugWindow}. Uses icon-font for icons, and allows title to be
+ * specified in the constructor.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class DebugButton extends Button {
+
+ protected boolean active = false;
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon. The icon id will
+ * be used in the <i>data-icon</i> attribute of an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ */
+ public DebugButton(Icon icon) {
+ this(icon, null, null);
+ }
+
+ /*-
+ public DebugButton(String caption) {
+ this(null, null, caption);
+ }
+
+ public DebugButton(String caption, String title) {
+ this(null, title, caption);
+ }
+ -*/
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon and title
+ * (tooltip). The icon id will be used in the <i>data-icon</i> attribute of
+ * an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ * @param title
+ * Button title (tooltip)
+ *
+ */
+ public DebugButton(Icon icon, String title) {
+ this(icon, title, null);
+ }
+
+ /**
+ * Creates a {@link Button} with the given icon-font icon, title (tooltip),
+ * and caption. The icon id will be used in the <i>data-icon</i> attribute
+ * of an <i>&lt;i&gt;</i> -tag.
+ *
+ * @param icon
+ * Identifier for the desired icon in an icon-font
+ * @param title
+ * Title (tooltip)
+ * @param caption
+ * Button baption
+ */
+ public DebugButton(Icon icon, String title, String caption) {
+ super((icon != null ? icon : "") + (caption != null ? caption : ""));
+ if (title != null) {
+ setTitle(title);
+ }
+
+ setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ }
+
+ /**
+ * Adds or removes a stylename, indicating whether or not the button is in
+ * it's active state.
+ *
+ * @param active
+ */
+ public void setActive(boolean active) {
+ this.active = active;
+ setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, active);
+ }
+
+ /**
+ * Indicates wheter the Button is currently in its active state or not
+ *
+ * @return true if the Button is active, false otherwise
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/ErrorNotificationHandler.java b/client/src/com/vaadin/client/debug/internal/ErrorNotificationHandler.java
new file mode 100644
index 0000000000..0e4c57494b
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/ErrorNotificationHandler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.debug.internal;
+
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+import com.google.gwt.logging.client.TextLogFormatter;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ui.VNotification;
+
+/**
+ * Log message handler that shows big red notification for {@link Level#SEVERE}
+ * messages that have a throwable.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class ErrorNotificationHandler extends Handler {
+ public ErrorNotificationHandler() {
+ setLevel(Level.SEVERE);
+ setFormatter(new TextLogFormatter(true) {
+ @Override
+ protected String getRecordInfo(LogRecord event, String newline) {
+ return "";
+ }
+ });
+ }
+
+ @Override
+ public void publish(LogRecord record) {
+ if (!isLoggable(record) || record.getThrown() == null) {
+ return;
+ }
+
+ try {
+ String exceptionText = getFormatter().format(record);
+
+ Widget owner = null;
+
+ if (!ApplicationConfiguration.getRunningApplications().isEmpty()) {
+ /*
+ * Make a wild guess and use the first available
+ * ApplicationConnection. This is better than than leaving the
+ * exception completely unstyled...
+ */
+ ApplicationConnection connection = ApplicationConfiguration
+ .getRunningApplications().get(0);
+ owner = connection.getUIConnector().getWidget();
+ }
+ VNotification
+ .createNotification(VNotification.DELAY_FOREVER, owner)
+ .show("<h1>Uncaught client side exception</h1><br />"
+ + exceptionText, VNotification.CENTERED, "error");
+ } catch (Exception e2) {
+ // Just swallow this exception
+ }
+ }
+
+ @Override
+ public void close() {
+ // Nothing to do
+ }
+
+ @Override
+ public void flush() {
+ // Nothing todo
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/HierarchySection.java b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
new file mode 100644
index 0000000000..e4f3e2dcb1
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
@@ -0,0 +1,676 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.dom.client.Style.TextDecoration;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
+import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
+import com.google.gwt.event.dom.client.KeyCodes;
+import com.google.gwt.event.dom.client.MouseOutEvent;
+import com.google.gwt.event.dom.client.MouseOutHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConfiguration;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ComputedStyle;
+import com.vaadin.client.ConnectorMap;
+import com.vaadin.client.JsArrayObject;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.SimpleTree;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.client.ui.UnknownComponentConnector;
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.communication.SharedState;
+
+/**
+ * Provides functionality for examining the UI component hierarchy.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class HierarchySection implements Section {
+ private final DebugButton tabButton = new DebugButton(Icon.HIERARCHY,
+ "Examine component hierarchy");
+
+ private final FlowPanel content = new FlowPanel();
+ private final FlowPanel controls = new FlowPanel();
+
+ private final Button find = new DebugButton(Icon.HIGHLIGHT,
+ "Select a component on the page to inspect it");
+ private final Button analyze = new DebugButton(Icon.ANALYZE,
+ "Check layouts for potential problems");
+ private final Button generateWS = new DebugButton(Icon.OPTIMIZE,
+ "Show used connectors and how to optimize widgetset");
+ private final Button showHierarchy = new DebugButton(Icon.HIERARCHY,
+ "Show the connector hierarchy tree");
+
+ private HandlerRegistration highlightModeRegistration = null;
+
+ public HierarchySection() {
+ controls.add(showHierarchy);
+ showHierarchy.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ showHierarchy.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ showHierarchy();
+ }
+ });
+
+ controls.add(find);
+ find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ find.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleFind();
+ }
+ });
+
+ controls.add(analyze);
+ analyze.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ analyze.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ stopFind();
+ analyzeLayouts();
+ }
+ });
+
+ controls.add(generateWS);
+ generateWS.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ generateWS.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ generateWidgetset();
+ }
+ });
+
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-hierarchy");
+ }
+
+ private void showHierarchy() {
+ Highlight.hideAll();
+ content.clear();
+
+ // TODO Clearing and rebuilding the contents is not optimal for UX as
+ // any previous expansions are lost.
+ SimplePanel trees = new SimplePanel();
+
+ for (ApplicationConnection application : ApplicationConfiguration
+ .getRunningApplications()) {
+ ServerConnector uiConnector = application.getUIConnector();
+ Widget connectorTree = buildConnectorTree(uiConnector);
+
+ trees.add(connectorTree);
+ }
+
+ content.add(trees);
+ }
+
+ private Widget buildConnectorTree(final ServerConnector connector) {
+ String connectorString = Util.getConnectorString(connector);
+
+ List<ServerConnector> children = connector.getChildren();
+
+ Widget widget;
+ if (children == null || children.isEmpty()) {
+ // Leaf node, just add a label
+ Label label = new Label(connectorString);
+ label.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ Highlight.showOnly(connector);
+ }
+ });
+ widget = label;
+ } else {
+ SimpleTree tree = new SimpleTree(connectorString) {
+ @Override
+ protected void select(ClickEvent event) {
+ super.select(event);
+ Highlight.showOnly(connector);
+ }
+ };
+ for (ServerConnector child : children) {
+ tree.add(buildConnectorTree(child));
+ }
+ widget = tree;
+ }
+
+ if (widget instanceof HasDoubleClickHandlers) {
+ HasDoubleClickHandlers has = (HasDoubleClickHandlers) widget;
+ has.addDoubleClickHandler(new DoubleClickHandler() {
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ printState(connector);
+ }
+ });
+ }
+
+ return widget;
+ }
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+
+ }
+
+ @Override
+ public void hide() {
+ stopFind();
+ }
+
+ private void generateWidgetset() {
+
+ content.clear();
+ HTML h = new HTML("Getting used connectors");
+ content.add(h);
+
+ String s = "";
+ for (ApplicationConnection ac : ApplicationConfiguration
+ .getRunningApplications()) {
+ ApplicationConfiguration conf = ac.getConfiguration();
+ s += "<h1>Used connectors for " + conf.getServiceUrl() + "</h1>";
+
+ for (String connectorName : getUsedConnectorNames(conf)) {
+ s += connectorName + "<br/>";
+ }
+
+ s += "<h2>To make an optimized widgetset based on these connectors, do:</h2>";
+ s += "<h3>1. Add to your widgetset.gwt.xml file:</h2>";
+ s += "<textarea rows=\"3\" style=\"width:90%\">";
+ s += "<generate-with class=\"OptimizedConnectorBundleLoaderFactory\">\n";
+ s += " <when-type-assignable class=\"com.vaadin.client.metadata.ConnectorBundleLoader\" />\n";
+ s += "</generate-with>";
+ s += "</textarea>";
+
+ s += "<h3>2. Add the following java file to your project:</h2>";
+ s += "<textarea rows=\"5\" style=\"width:90%\">";
+ s += generateOptimizedWidgetSet(getUsedConnectorNames(conf));
+ s += "</textarea>";
+ s += "<h3>3. Recompile widgetset</h2>";
+
+ }
+
+ h.setHTML(s);
+ }
+
+ private Set<String> getUsedConnectorNames(
+ ApplicationConfiguration configuration) {
+ int tag = 0;
+ Set<String> usedConnectors = new HashSet<String>();
+ while (true) {
+ String serverSideClass = configuration
+ .getServerSideClassNameForTag(tag);
+ if (serverSideClass == null) {
+ break;
+ }
+ Class<? extends ServerConnector> connectorClass = configuration
+ .getConnectorClassByEncodedTag(tag);
+ if (connectorClass == null) {
+ break;
+ }
+
+ if (connectorClass != UnknownComponentConnector.class) {
+ usedConnectors.add(connectorClass.getName());
+ }
+ tag++;
+ if (tag > 10000) {
+ // Sanity check
+ VConsole.error("Search for used connector classes was forcefully terminated");
+ break;
+ }
+ }
+ return usedConnectors;
+ }
+
+ public String generateOptimizedWidgetSet(Set<String> usedConnectors) {
+ String s = "import java.util.HashSet;\n";
+ s += "import java.util.Set;\n";
+
+ s += "import com.google.gwt.core.ext.typeinfo.JClassType;\n";
+ s += "import com.vaadin.client.ui.ui.UIConnector;\n";
+ s += "import com.vaadin.server.widgetsetutils.ConnectorBundleLoaderFactory;\n";
+ s += "import com.vaadin.shared.ui.Connect.LoadStyle;\n\n";
+
+ s += "public class OptimizedConnectorBundleLoaderFactory extends\n";
+ s += " ConnectorBundleLoaderFactory {\n";
+ s += " private Set<String> eagerConnectors = new HashSet<String>();\n";
+ s += " {\n";
+ for (String c : usedConnectors) {
+ s += " eagerConnectors.add(" + c
+ + ".class.getName());\n";
+ }
+ s += " }\n";
+ s += "\n";
+ s += " @Override\n";
+ s += " protected LoadStyle getLoadStyle(JClassType connectorType) {\n";
+ s += " if (eagerConnectors.contains(connectorType.getQualifiedBinaryName())) {\n";
+ s += " return LoadStyle.EAGER;\n";
+ s += " } else {\n";
+ s += " // Loads all other connectors immediately after the initial view has\n";
+ s += " // been rendered\n";
+ s += " return LoadStyle.DEFERRED;\n";
+ s += " }\n";
+ s += " }\n";
+ s += "}\n";
+
+ return s;
+ }
+
+ private void analyzeLayouts() {
+ content.clear();
+ content.add(new Label("Analyzing layouts..."));
+ List<ApplicationConnection> runningApplications = ApplicationConfiguration
+ .getRunningApplications();
+ for (ApplicationConnection applicationConnection : runningApplications) {
+ applicationConnection.analyzeLayouts();
+ }
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ content.clear();
+ JsArray<ValueMap> valueMapArray = meta
+ .getJSValueMapArray("invalidLayouts");
+ int size = valueMapArray.length();
+
+ if (size > 0) {
+ SimpleTree root = new SimpleTree("Layouts analyzed, " + size
+ + " top level problems");
+ for (int i = 0; i < size; i++) {
+ printLayoutError(ac, valueMapArray.get(i), root);
+ }
+ root.open(false);
+ content.add(root);
+ } else {
+ content.add(new Label("Layouts analyzed, no top level problems"));
+ }
+
+ Set<ComponentConnector> zeroHeightComponents = new HashSet<ComponentConnector>();
+ Set<ComponentConnector> zeroWidthComponents = new HashSet<ComponentConnector>();
+ findZeroSizeComponents(zeroHeightComponents, zeroWidthComponents,
+ ac.getUIConnector());
+ if (zeroHeightComponents.size() > 0 || zeroWidthComponents.size() > 0) {
+ content.add(new HTML("<h4> Client side notifications</h4>"
+ + " <em>The following relative sized components were "
+ + "rendered to a zero size container on the client side."
+ + " Note that these are not necessarily invalid "
+ + "states, but reported here as they might be.</em>"));
+ if (zeroHeightComponents.size() > 0) {
+ content.add(new HTML(
+ "<p><strong>Vertically zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroHeightComponents, ac);
+ }
+ if (zeroWidthComponents.size() > 0) {
+ content.add(new HTML(
+ "<p><strong>Horizontally zero size:</strong></p>"));
+ printClientSideDetectedIssues(zeroWidthComponents, ac);
+ }
+ }
+
+ }
+
+ private void printClientSideDetectedIssues(
+ Set<ComponentConnector> zeroSized, ApplicationConnection ac) {
+
+ // keep track of already highlighted parents
+ HashSet<String> parents = new HashSet<String>();
+
+ for (final ComponentConnector connector : zeroSized) {
+ final ServerConnector parent = connector.getParent();
+ final String parentId = parent.getConnectorId();
+
+ final Label errorDetails = new Label(Util.getSimpleName(connector)
+ + "[" + connector.getConnectorId() + "]" + " inside "
+ + Util.getSimpleName(parent));
+
+ if (parent instanceof ComponentConnector) {
+ final ComponentConnector parentConnector = (ComponentConnector) parent;
+ if (!parents.contains(parentId)) {
+ parents.add(parentId);
+ Highlight.show(parentConnector, "yellow");
+ }
+
+ errorDetails.addMouseOverHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.hideAll();
+ Highlight.show(parentConnector, "yellow");
+ Highlight.show(connector);
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ });
+ errorDetails.addMouseOutHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ errorDetails.getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ });
+ errorDetails.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ printState(connector);
+ Highlight.show(connector);
+ }
+ });
+
+ }
+
+ Highlight.show(connector);
+ content.add(errorDetails);
+
+ }
+ }
+
+ private void printLayoutError(ApplicationConnection ac, ValueMap valueMap,
+ SimpleTree root) {
+ final String pid = valueMap.getString("id");
+
+ // find connector
+ final ComponentConnector connector = (ComponentConnector) ConnectorMap
+ .get(ac).getConnector(pid);
+
+ if (connector == null) {
+ root.add(new SimpleTree("[" + pid + "] NOT FOUND"));
+ return;
+ }
+
+ Highlight.show(connector);
+
+ final SimpleTree errorNode = new SimpleTree(
+ Util.getSimpleName(connector) + " id: " + pid);
+ errorNode.addDomHandler(new MouseOverHandler() {
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ Highlight.showOnly(connector);
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.UNDERLINE);
+ }
+ }, MouseOverEvent.getType());
+ errorNode.addDomHandler(new MouseOutHandler() {
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ ((Widget) event.getSource()).getElement().getStyle()
+ .setTextDecoration(TextDecoration.NONE);
+ }
+ }, MouseOutEvent.getType());
+
+ errorNode.addDomHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (event.getNativeEvent().getEventTarget().cast() == errorNode
+ .getElement().getChild(1).cast()) {
+ printState(connector);
+ }
+ }
+ }, ClickEvent.getType());
+
+ VerticalPanel errorDetails = new VerticalPanel();
+
+ if (valueMap.containsKey("heightMsg")) {
+ errorDetails.add(new Label("Height problem: "
+ + valueMap.getString("heightMsg")));
+ }
+ if (valueMap.containsKey("widthMsg")) {
+ errorDetails.add(new Label("Width problem: "
+ + valueMap.getString("widthMsg")));
+ }
+ if (errorDetails.getWidgetCount() > 0) {
+ errorNode.add(errorDetails);
+ }
+ if (valueMap.containsKey("subErrors")) {
+ HTML l = new HTML(
+ "<em>Expand this node to show problems that may be dependent on this problem.</em>");
+ errorDetails.add(l);
+ JsArray<ValueMap> suberrors = valueMap
+ .getJSValueMapArray("subErrors");
+ for (int i = 0; i < suberrors.length(); i++) {
+ ValueMap value = suberrors.get(i);
+ printLayoutError(ac, value, errorNode);
+ }
+
+ }
+ root.add(errorNode);
+ }
+
+ private void findZeroSizeComponents(
+ Set<ComponentConnector> zeroHeightComponents,
+ Set<ComponentConnector> zeroWidthComponents,
+ ComponentConnector connector) {
+ Widget widget = connector.getWidget();
+ ComputedStyle computedStyle = new ComputedStyle(widget.getElement());
+ if (computedStyle.getIntProperty("height") == 0) {
+ zeroHeightComponents.add(connector);
+ }
+ if (computedStyle.getIntProperty("width") == 0) {
+ zeroWidthComponents.add(connector);
+ }
+ List<ServerConnector> children = connector.getChildren();
+ for (ServerConnector serverConnector : children) {
+ if (serverConnector instanceof ComponentConnector) {
+ findZeroSizeComponents(zeroHeightComponents,
+ zeroWidthComponents,
+ (ComponentConnector) serverConnector);
+ }
+ }
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ // NOP
+ }
+
+ private boolean isFindMode() {
+ return (highlightModeRegistration != null);
+ }
+
+ private void toggleFind() {
+ if (isFindMode()) {
+ stopFind();
+ } else {
+ startFind();
+ }
+ }
+
+ private void startFind() {
+ Highlight.hideAll();
+ if (!isFindMode()) {
+ highlightModeRegistration = Event
+ .addNativePreviewHandler(highlightModeHandler);
+ find.addStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ }
+
+ private void stopFind() {
+ if (isFindMode()) {
+ highlightModeRegistration.removeHandler();
+ highlightModeRegistration = null;
+ find.removeStyleDependentName(VDebugWindow.STYLENAME_ACTIVE);
+ }
+ }
+
+ private void printState(ServerConnector connector) {
+ Highlight.showOnly(connector);
+
+ SharedState state = connector.getState();
+
+ Set<String> ignoreProperties = new HashSet<String>();
+ ignoreProperties.add("id");
+
+ String html = getRowHTML("Id", connector.getConnectorId());
+ html += getRowHTML("Connector", Util.getSimpleName(connector));
+
+ if (connector instanceof ComponentConnector) {
+ ComponentConnector component = (ComponentConnector) connector;
+
+ ignoreProperties.addAll(Arrays.asList("caption", "description",
+ "width", "height"));
+
+ AbstractComponentState componentState = component.getState();
+
+ html += getRowHTML("Widget",
+ Util.getSimpleName(component.getWidget()));
+ html += getRowHTML("Caption", componentState.caption);
+ html += getRowHTML("Description", componentState.description);
+ html += getRowHTML("Width", componentState.width + " (actual: "
+ + component.getWidget().getOffsetWidth() + "px)");
+ html += getRowHTML("Height", componentState.height + " (actual: "
+ + component.getWidget().getOffsetHeight() + "px)");
+ }
+
+ try {
+ JsArrayObject<Property> properties = AbstractConnector
+ .getStateType(connector).getPropertiesAsArray();
+ for (int i = 0; i < properties.size(); i++) {
+ Property property = properties.get(i);
+ String name = property.getName();
+ if (!ignoreProperties.contains(name)) {
+ html += getRowHTML(property.getDisplayName(),
+ property.getValue(state));
+ }
+ }
+ } catch (NoDataException e) {
+ html += "<div>Could not read state, error has been logged to the console</div>";
+ VConsole.error(e);
+ }
+
+ content.clear();
+ content.add(new HTML(html));
+ }
+
+ private String getRowHTML(String caption, Object value) {
+ return "<div class=\"" + VDebugWindow.STYLENAME
+ + "-row\"><span class=\"caption\">" + caption
+ + "</span><span class=\"value\">"
+ + Util.escapeHTML(String.valueOf(value)) + "</span></div>";
+ }
+
+ private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() {
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+
+ if (event.getTypeInt() == Event.ONKEYDOWN
+ && event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
+ stopFind();
+ Highlight.hideAll();
+ return;
+ }
+ if (event.getTypeInt() == Event.ONMOUSEMOVE) {
+ Highlight.hideAll();
+ Element eventTarget = Util.getElementFromPoint(event
+ .getNativeEvent().getClientX(), event.getNativeEvent()
+ .getClientY());
+ if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) {
+ content.clear();
+ return;
+ }
+
+ for (ApplicationConnection a : ApplicationConfiguration
+ .getRunningApplications()) {
+ ComponentConnector connector = Util.getConnectorForElement(
+ a, a.getUIConnector().getWidget(), eventTarget);
+ if (connector == null) {
+ connector = Util.getConnectorForElement(a,
+ RootPanel.get(), eventTarget);
+ }
+ if (connector != null) {
+ printState(connector);
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ return;
+ }
+ }
+ content.clear();
+ }
+ if (event.getTypeInt() == Event.ONCLICK) {
+ Highlight.hideAll();
+ event.cancel();
+ event.consume();
+ event.getNativeEvent().stopPropagation();
+ stopFind();
+ Element eventTarget = Util.getElementFromPoint(event
+ .getNativeEvent().getClientX(), event.getNativeEvent()
+ .getClientY());
+ for (ApplicationConnection a : ApplicationConfiguration
+ .getRunningApplications()) {
+ ComponentConnector connector = Util.getConnectorForElement(
+ a, a.getUIConnector().getWidget(), eventTarget);
+ if (connector == null) {
+ connector = Util.getConnectorForElement(a,
+ RootPanel.get(), eventTarget);
+ }
+
+ if (connector != null) {
+ printState(connector);
+ return;
+ }
+ }
+ }
+ event.cancel();
+ }
+
+ };
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/Highlight.java b/client/src/com/vaadin/client/debug/internal/Highlight.java
new file mode 100644
index 0000000000..f2695f58ca
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Highlight.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.HashSet;
+
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.ui.VWindow;
+
+/**
+ * Highlights a widget in the UI by overlaying a semi-transparent colored div.
+ * <p>
+ * Multiple highlights can be added, then selectively removed with
+ * {@link #hide(Element)} or all at once with {@link #hideAll()}.
+ * </p>
+ * <p>
+ * Note that highlights are intended to be short-term; highlights do not move or
+ * disappear with the highlighted widget, and it is also fairly likely that
+ * someone else calls {@link #hideAll()} eventually.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class Highlight {
+
+ private static final String DEFAULT_COLOR = "red";
+ private static final double DEFAULT_OPACITY = 0.3;
+ private static final int MIN_WIDTH = 3;
+ private static final int MIN_HEIGHT = 3;
+
+ static HashSet<Element> highlights;
+
+ /**
+ * Highlight the {@link Widget} for the given {@link ComponentConnector}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param connector
+ * ComponentConnector
+ * @return Highlight element
+ */
+ static Element show(ComponentConnector connector) {
+ return show(connector, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlight the {@link Widget} for the given connector if it is a
+ * {@link ComponentConnector}. Hide any other highlight.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @since 7.1
+ *
+ * @param connector
+ * the server connector to highlight
+ * @return Highlight element, or <code>null</code> if the connector isn't a
+ * component
+ */
+ static Element showOnly(ServerConnector connector) {
+ hideAll();
+ if (connector instanceof ComponentConnector) {
+ return show((ComponentConnector) connector);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Highlights the {@link Widget} for the given {@link ComponentConnector}
+ * using the given color.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param connector
+ * ComponentConnector
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(ComponentConnector connector, String color) {
+ if (connector != null) {
+ Widget w = connector.getWidget();
+ return show(w, color);
+ }
+ return null;
+ }
+
+ /**
+ * Highlights the given {@link Widget}.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param widget
+ * Widget to highlight
+ * @return Highlight element
+ */
+ static Element show(Widget widget) {
+ return show(widget, DEFAULT_COLOR);
+ }
+
+ /**
+ * Highlight the given {@link Widget} using the given color.
+ * <p>
+ * Pass the returned {@link Element} to {@link #hide(Element)} to remove
+ * this particular highlight.
+ * </p>
+ *
+ * @param widget
+ * Widget to highlight
+ * @param color
+ * Color of highlight
+ * @return Highlight element
+ */
+ static Element show(Widget widget, String color) {
+ if (widget != null) {
+ if (highlights == null) {
+ highlights = new HashSet<Element>();
+ }
+
+ Element highlight = DOM.createDiv();
+ Style style = highlight.getStyle();
+ style.setTop(widget.getAbsoluteTop(), Unit.PX);
+ style.setLeft(widget.getAbsoluteLeft(), Unit.PX);
+ int width = widget.getOffsetWidth();
+ if (width < MIN_WIDTH) {
+ width = MIN_WIDTH;
+ }
+ style.setWidth(width, Unit.PX);
+ int height = widget.getOffsetHeight();
+ if (height < MIN_HEIGHT) {
+ height = MIN_HEIGHT;
+ }
+ style.setHeight(height, Unit.PX);
+ RootPanel.getBodyElement().appendChild(highlight);
+
+ style.setPosition(Position.ABSOLUTE);
+ style.setZIndex(VWindow.Z_INDEX + 1000);
+ style.setBackgroundColor(color);
+ style.setOpacity(DEFAULT_OPACITY);
+ if (BrowserInfo.get().isIE()) {
+ style.setProperty("filter", "alpha(opacity="
+ + (DEFAULT_OPACITY * 100) + ")");
+ }
+
+ highlights.add(highlight);
+
+ return highlight;
+ }
+ return null;
+ }
+
+ /**
+ * Hides the given highlight.
+ *
+ * @param highlight
+ * Highlight to hide
+ */
+ static void hide(Element highlight) {
+ if (highlight != null && highlight.getParentElement() != null) {
+ highlight.getParentElement().removeChild(highlight);
+ highlights.remove(highlight);
+ }
+ }
+
+ /**
+ * Hides all highlights
+ */
+ static void hideAll() {
+ if (highlights != null) {
+ for (Element highlight : highlights) {
+ if (highlight.getParentElement() != null) {
+ highlight.getParentElement().removeChild(highlight);
+ }
+ }
+ highlights = null;
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/Icon.java b/client/src/com/vaadin/client/debug/internal/Icon.java
new file mode 100644
index 0000000000..cc2ef3b348
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Icon.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.debug.internal;
+
+public enum Icon {
+
+ SEARCH("&#xf002;"), //
+ OK("&#xf00c;"), //
+ REMOVE("&#xf00d;"), //
+ CLOSE("&#xf011;"), //
+ CLEAR("&#xf014;"), //
+ RESET_TIMER("&#xf017;"), //
+ MINIMIZE("&#xf066;"), //
+ WARNING("&#xf071;"), //
+ INFO("&#xf05a;"), //
+ ERROR("&#xf06a;"), //
+ HIGHLIGHT("&#xf05b;"), //
+ LOG("&#xf0c9;"), //
+ OPTIMIZE("&#xf0d0;"), //
+ HIERARCHY("&#xf0e8;"), //
+ MENU("&#xf013;"), //
+ NETWORK("&#xf0ec;"), //
+ ANALYZE("&#xf0f0;"), //
+ SCROLL_LOCK("&#xf023;"), //
+ DEVMODE_OFF("&#xf10c;"), //
+ DEVMODE_SUPER("&#xf111;"), //
+ DEVMODE_ON("&#xf110;"), //
+ // BAN_CIRCLE("&#xf05e;"), //
+ MAXIMIZE("&#xf065;"), //
+ RESET("&#xf021;"), //
+ PERSIST("&#xf02e"); //
+
+ private String id;
+
+ private Icon(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String toString() {
+ return "<i data-icon=\"" + id + "\"></i>";
+ }
+
+ public String getId() {
+ return id;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/LogSection.java b/client/src/com/vaadin/client/debug/internal/LogSection.java
new file mode 100644
index 0000000000..74ac3641c2
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/LogSection.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.logging.client.HtmlLogFormatter;
+import com.google.gwt.storage.client.Storage;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Displays the log messages.
+ * <p>
+ * Scroll lock state is persisted.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class LogSection implements Section {
+
+ private final class LogSectionHandler extends Handler {
+ private LogSectionHandler() {
+ setLevel(Level.ALL);
+ setFormatter(new HtmlLogFormatter(true) {
+ @Override
+ protected String getHtmlPrefix(LogRecord event) {
+ return "";
+ }
+
+ @Override
+ protected String getHtmlSuffix(LogRecord event) {
+ return "";
+ }
+
+ @Override
+ protected String getRecordInfo(LogRecord event, String newline) {
+ return "";
+ }
+ });
+ }
+
+ @Override
+ public void publish(LogRecord record) {
+ if (!isLoggable(record)) {
+ return;
+ }
+
+ Formatter formatter = getFormatter();
+ String msg = formatter.format(record);
+
+ addRow(record.getLevel(), msg);
+ }
+
+ @Override
+ public void close() {
+ // Nothing to do
+ }
+
+ @Override
+ public void flush() {
+ // Nothing todo
+ }
+ }
+
+ // If scroll is not locked, content will be scrolled after delay
+ private static final int SCROLL_DELAY = 100;
+ private Timer scrollTimer = null;
+
+ // TODO should be persisted
+ // log content limit
+ private int limit = 500;
+
+ private final DebugButton tabButton = new DebugButton(Icon.LOG,
+ "Debug message log");
+
+ private final HTML content = new HTML();
+ private final Element contentElement;
+ private final FlowPanel controls = new FlowPanel();
+
+ private final Button clear = new DebugButton(Icon.CLEAR, "Clear log");
+ private final Button reset = new DebugButton(Icon.RESET_TIMER,
+ "Reset timer");
+ private final Button scroll = new DebugButton(Icon.SCROLL_LOCK,
+ "Scroll lock");
+
+ public LogSection() {
+ contentElement = content.getElement();
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-log");
+
+ // clear log button
+ controls.add(clear);
+ clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ clear.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ clear();
+ }
+ });
+
+ // reset timer button
+ controls.add(reset);
+ reset.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ reset.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ resetTimer();
+ }
+ });
+
+ // scroll lock toggle
+ controls.add(scroll);
+ scroll.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ scroll.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleScrollLock();
+ }
+ });
+
+ // select message if row is clicked
+ content.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ Element el = Element
+ .as(event.getNativeEvent().getEventTarget());
+ while (!el.getClassName().contains(
+ VDebugWindow.STYLENAME + "-message")) {
+ el = el.getParentElement();
+ if (el == contentElement) {
+ // clicked something else
+ return;
+ }
+ }
+ selectText(el);
+ }
+ });
+
+ // Add handler to the root logger
+ Logger.getLogger("").addHandler(new LogSectionHandler());
+ }
+
+ /**
+ * Toggles scroll lock, writes state to persistent storage.
+ */
+ void toggleScrollLock() {
+ setScrollLock(scrollTimer != null);
+
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ VDebugWindow.writeState(storage, "log-scrollLock", scrollTimer == null);
+ }
+
+ /**
+ * Activates or deactivates scroll lock
+ *
+ * @param locked
+ */
+ void setScrollLock(boolean locked) {
+ if (locked && scrollTimer != null) {
+ scrollTimer.cancel();
+ scrollTimer = null;
+
+ } else if (!locked && scrollTimer == null) {
+ scrollTimer = new Timer() {
+ @Override
+ public void run() {
+ Element el = (Element) contentElement.getLastChild();
+ if (el != null) {
+ el = el.getFirstChildElement();
+ if (el != null) {
+ el.scrollIntoView();
+ }
+ }
+ }
+ };
+
+ }
+ scroll.setStyleDependentName(VDebugWindow.STYLENAME_ACTIVE, locked);
+
+ }
+
+ private native void selectText(Element el)
+ /*-{
+ if ($doc.selection && $doc.selection.createRange) {
+ var r = $doc.selection.createRange();
+ r.moveToElementText(el);
+ r.select();
+ } else if ($doc.createRange && $wnd.getSelection) {
+ var r = $doc.createRange();
+ r.selectNode(el);
+ var selection = $wnd.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(r);
+ }
+ }-*/;
+
+ private void clear() {
+ contentElement.setInnerText("");
+ }
+
+ private void applyLimit() {
+ while (contentElement.getChildCount() > limit) {
+ contentElement.removeChild(contentElement.getFirstChild());
+ }
+ }
+
+ /**
+ * Sets the log row limit.
+ *
+ * @param limit
+ */
+ public void setLimit(int limit) {
+ this.limit = limit;
+ applyLimit();
+
+ // TODO shoud be persisted
+ }
+
+ /**
+ * Gets the current log row limit.
+ *
+ * @return
+ */
+ public int getLimit() {
+ // TODO should be read from persistent storage
+ return limit;
+ }
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ setScrollLock(VDebugWindow.readState(storage, "log-scrollLock", false));
+ }
+
+ @Override
+ public void hide() {
+ // remove timer
+ setScrollLock(true);
+ }
+
+ /**
+ * Schedules a scoll if scroll lock is not active.
+ */
+ private void maybeScroll() {
+ if (scrollTimer != null) {
+ scrollTimer.cancel();
+ scrollTimer.schedule(SCROLL_DELAY);
+ }
+ }
+
+ /**
+ * Resets the timer and inserts a log row indicating this.
+ */
+ private void resetTimer() {
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+ int sinceReset = VDebugWindow.resetTimer();
+ Element row = DOM.createDiv();
+ row.addClassName(VDebugWindow.STYLENAME + "-reset");
+ row.setInnerHTML(Icon.RESET_TIMER + " Timer reset");
+ row.setTitle(VDebugWindow.getTimingTooltip(sinceStart, sinceReset));
+ contentElement.appendChild(row);
+ maybeScroll();
+ }
+
+ /**
+ * Adds a row to the log, applies the log row limit by removing old rows if
+ * needed, and scrolls new row into view if scroll lock is not active.
+ *
+ * @param level
+ * @param msg
+ * @return
+ */
+ private Element addRow(Level level, String msg) {
+ int sinceReset = VDebugWindow.getMillisSinceReset();
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+
+ Element row = DOM.createDiv();
+ row.addClassName(VDebugWindow.STYLENAME + "-row");
+ row.addClassName(level.getName());
+
+ String inner = "<span class='" + VDebugWindow.STYLENAME + "-"
+ + "'></span><span class='" + VDebugWindow.STYLENAME
+ + "-time' title='"
+ + VDebugWindow.getTimingTooltip(sinceStart, sinceReset) + "'>"
+ + sinceReset + "ms</span><span class='"
+ + VDebugWindow.STYLENAME + "-message'>" + msg + "</span>";
+ row.setInnerHTML(inner);
+
+ contentElement.appendChild(row);
+ applyLimit();
+
+ maybeScroll();
+
+ return row;
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ addRow(Level.FINE, "Meta: " + meta.toSource());
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ addRow(Level.FINE, "UIDL: " + uidl.toSource());
+ }
+
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/NetworkSection.java b/client/src/com/vaadin/client/debug/internal/NetworkSection.java
new file mode 100644
index 0000000000..e94791ce1f
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/NetworkSection.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.VUIDLBrowser;
+import com.vaadin.client.ValueMap;
+
+/**
+ * Displays network activity; requests and responses.
+ *
+ * Currently only displays responses in a simple manner.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public class NetworkSection implements Section {
+
+ private final int maxSize = 10;
+
+ private final DebugButton tabButton = new DebugButton(Icon.NETWORK,
+ "Communication");
+
+ private final HorizontalPanel controls = new HorizontalPanel();
+ private final FlowPanel content = new FlowPanel();
+
+ @Override
+ public DebugButton getTabButton() {
+ return tabButton;
+ }
+
+ @Override
+ public Widget getControls() {
+ return controls;
+ }
+
+ @Override
+ public Widget getContent() {
+ return content;
+ }
+
+ @Override
+ public void show() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void hide() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ // NOP
+ }
+
+ @Override
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ int sinceStart = VDebugWindow.getMillisSinceStart();
+ int sinceReset = VDebugWindow.getMillisSinceReset();
+ VUIDLBrowser vuidlBrowser = new VUIDLBrowser(uidl, ac);
+ vuidlBrowser.addStyleName(VDebugWindow.STYLENAME + "-row");
+ // TODO style this
+ /*-
+ vuidlBrowser.setText("<span class=\"" + VDebugWindow.STYLENAME
+ + "-time\">" + sinceReset + "ms</span><span class=\""
+ + VDebugWindow.STYLENAME + "-message\">response</span>");
+ -*/
+ vuidlBrowser.setText("Response @ " + sinceReset + "ms");
+ vuidlBrowser.setTitle(VDebugWindow.getTimingTooltip(sinceStart,
+ sinceReset));
+ vuidlBrowser.close();
+ content.add(vuidlBrowser);
+ while (content.getWidgetCount() > maxSize) {
+ content.remove(0);
+ }
+ }
+
+}
diff --git a/client/src/com/vaadin/client/debug/internal/Section.java b/client/src/com/vaadin/client/debug/internal/Section.java
new file mode 100644
index 0000000000..c6b8af55e8
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/Section.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+
+/**
+ * A Section is displayed as a tab in the {@link VDebugWindow}.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public interface Section {
+
+ /**
+ * Returns a button that will be used to activate this section, displayed as
+ * a tab in {@link VDebugWindow}.
+ * <p>
+ * <em>The same instance <b>must</b> be returned each time this method is called.</em>
+ * </p>
+ * <p>
+ * The button should preferably only have an icon (no caption), and should
+ * have a longer description as title (tooltip).
+ * </p>
+ *
+ * @return section id
+ */
+ public DebugButton getTabButton();
+
+ /**
+ * Returns a widget that is placed on top of the Section content when the
+ * Section (tab) is active in the {@link VDebugWindow}.
+ *
+ * @return section controls
+ */
+ public Widget getControls();
+
+ /**
+ * Returns a widget that is the main content of the section, displayed when
+ * the section is active in the {@link VDebugWindow}.
+ *
+ * @return
+ */
+ public Widget getContent();
+
+ /**
+ * Called when the section is activated in {@link VDebugWindow}. Provides an
+ * opportunity to e.g start timers, add listeners etc.
+ */
+ public void show();
+
+ /**
+ * Called when the section is deactivated in {@link VDebugWindow}. Provides
+ * an opportunity to e.g stop timers, remove listeners etc.
+ */
+ public void hide();
+
+ public void meta(ApplicationConnection ac, ValueMap meta);
+
+ public void uidl(ApplicationConnection ac, ValueMap uidl);
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/debug/internal/VDebugWindow.java b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
new file mode 100644
index 0000000000..5aab95616a
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/VDebugWindow.java
@@ -0,0 +1,1062 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Cursor;
+import com.google.gwt.dom.client.Style.Overflow;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseEvent;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.http.client.UrlBuilder;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.i18n.client.NumberFormat;
+import com.google.gwt.storage.client.Storage;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.Event.NativePreviewEvent;
+import com.google.gwt.user.client.Event.NativePreviewHandler;
+import com.google.gwt.user.client.Timer;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.Location;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ValueMap;
+import com.vaadin.client.ui.VOverlay;
+
+/**
+ * Debug window implementation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+public final class VDebugWindow extends VOverlay {
+
+ // CSS classes
+ static final String STYLENAME = "v-debugwindow";
+ static final String STYLENAME_BUTTON = STYLENAME + "-button";
+ static final String STYLENAME_ACTIVE = "active";
+
+ protected static final String STYLENAME_HEAD = STYLENAME + "-head";
+ protected static final String STYLENAME_TABS = STYLENAME + "-tabs";
+ protected static final String STYLENAME_TAB = STYLENAME + "-tab";
+ protected static final String STYLENAME_CONTROLS = STYLENAME + "-controls";
+ protected static final String STYLENAME_SECTION_HEAD = STYLENAME
+ + "-section-head";
+ protected static final String STYLENAME_CONTENT = STYLENAME + "-content";
+ protected static final String STYLENAME_SELECTED = "selected";
+
+ // drag this far before actually moving window
+ protected static final int MOVE_TRESHOLD = 5;
+
+ // window minimum sizes
+ protected static final int MIN_HEIGHT = 40;
+ protected static final int HANDLE_SIZE = 5;
+
+ // identifiers for localStorage
+ private static final String STORAGE_PREFIX = "v-debug-";
+ private static final String STORAGE_FULL_X = "x";
+ private static final String STORAGE_FULL_Y = "y";
+ private static final String STORAGE_FULL_W = "w";
+ private static final String STORAGE_FULL_H = "h";
+ private static final String STORAGE_MIN_X = "mx";
+ private static final String STORAGE_MIN_Y = "my";
+ private static final String STORAGE_ACTIVE_SECTION = "t";
+ private static final String STORAGE_IS_MINIMIZED = "m";
+ private static final String STORAGE_FONT_SIZE = "s";
+
+ // state, these are persisted
+ protected Section activeSection;
+ protected boolean minimized = false;
+ protected int fullX = -10;
+ protected int fullY = -10;
+ protected int fullW = 300;
+ protected int fullH = 150;
+ protected int minX = -10;
+ protected int minY = 10;
+ protected int fontSize = 1; // 0-2
+
+ // Timers since application start, and last timer reset
+ private static final Duration start = new Duration();
+ private static Duration lastReset = start;
+
+ // outer panel
+ protected FlowPanel window = new FlowPanel();
+ // top (tabs + controls)
+ protected FlowPanel head = new FlowPanel();
+ protected FlowPanel tabs = new FlowPanel();
+ protected FlowPanel controls = new FlowPanel();
+ protected Button minimize = new DebugButton(Icon.MINIMIZE, "Minimize");
+ protected Button menu = new DebugButton(Icon.MENU, "Menu");
+ protected Button close = new DebugButton(Icon.CLOSE, "Close");
+
+ // menu
+ protected Menu menuPopup = new Menu();
+
+ // section specific area
+ protected FlowPanel sectionHead = new FlowPanel();
+ // content wrapper
+ protected SimplePanel content = new SimplePanel();
+
+ // sections
+ protected ArrayList<Section> sections = new ArrayList<Section>();
+
+ // handles resizing (mouse)
+ protected ResizeHandler resizeHandler = new ResizeHandler();
+ protected HandlerRegistration resizeReg = null;
+ protected HandlerRegistration resizeReg2 = null;
+
+ // handles window movement (mouse)
+ protected MoveHandler moveHandler = new MoveHandler();
+ protected HandlerRegistration moveReg = null;
+
+ // TODO this class should really be a singleton.
+ static VDebugWindow instance;
+
+ /**
+ * This class should only be instantiated by the framework, use
+ * {@link #get()} instead to get the singleton instance.
+ * <p>
+ * {@link VDebugWindow} provides windowing functionality and shows
+ * {@link Section}s added with {@link #addSection(Section)} as tabs.
+ * </p>
+ * <p>
+ * {@link Section#getTabButton()} is called to obtain a unique id for the
+ * Sections; the id should actually be an identifier for an icon in the
+ * icon-font in use.
+ * </p>
+ * <p>
+ * {@link Section#getControls()} and {@link Section#getContent()} is called
+ * when the Section is activated (displayed). Additionally
+ * {@link Section#show()} is called to allow the Section to initialize
+ * itself as needed when shown. Conversely {@link Section#hide()} is called
+ * when the Section is deactivated.
+ * </p>
+ * <p>
+ * Sections should take care to prefix CSS classnames used with
+ * {@link VDebugWindow}.{@link #STYLENAME} to avoid that application theme
+ * interferes with the debug window content.
+ * </p>
+ * <p>
+ * Some of the window state, such as position and size, is persisted to
+ * localStorage. Sections can use
+ * {@link #writeState(Storage, String, Object)} and
+ * {@link #readState(Storage, String, String)} (and relatives) to write and
+ * read own persisted settings, keys will automatically be prefixed with
+ * {@value #STORAGE_PREFIX}.
+ * </p>
+ */
+ public VDebugWindow() {
+ super(false, false);
+ instance = this;
+ getElement().getStyle().setOverflow(Overflow.HIDDEN);
+ setStylePrimaryName(STYLENAME);
+
+ setWidget(window);
+ window.add(head);
+ head.add(tabs);
+ head.add(controls);
+ head.add(sectionHead);
+ window.add(content);
+
+ head.setStylePrimaryName(STYLENAME_HEAD);
+ tabs.setStylePrimaryName(STYLENAME_TABS);
+ controls.setStylePrimaryName(STYLENAME_CONTROLS);
+ sectionHead.setStylePrimaryName(STYLENAME_SECTION_HEAD);
+ content.setStylePrimaryName(STYLENAME_CONTENT);
+
+ // add controls TODO move these
+ controls.add(menu);
+ menu.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ menuPopup.showRelativeTo(menu);
+ }
+ });
+
+ controls.add(minimize);
+ minimize.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleMinimized();
+ writeStoredState();
+ }
+ });
+ controls.add(close);
+ close.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ close();
+ }
+ });
+
+ Style s = content.getElement().getStyle();
+ s.setOverflow(Overflow.AUTO);
+
+ // window can be moved by dragging header
+ moveReg = head.addDomHandler(moveHandler, MouseDownEvent.getType());
+ // resize from all sides and corners
+ resizeReg = content.addDomHandler(resizeHandler,
+ MouseDownEvent.getType());
+ // changes mouse pointer when hovering sides / corners
+ resizeReg2 = content.addDomHandler(resizeHandler,
+ MouseMoveEvent.getType());
+ }
+
+ /**
+ * Gets the {@link #VDebugWindow()} singleton instance.
+ *
+ * @return
+ */
+ public static VDebugWindow get() {
+ if (instance == null) {
+ instance = new VDebugWindow();
+ }
+ return instance;
+ }
+
+ /**
+ * Closes the window and stops visual logging.
+ */
+ public void close() {
+ // TODO disable even more
+ if (resizeReg != null) {
+ resizeReg.removeHandler();
+ resizeReg2.removeHandler();
+ moveReg.removeHandler();
+ }
+ Highlight.hideAll();
+ hide();
+
+ }
+
+ boolean isClosed() {
+ return !isShowing();
+ }
+
+ /**
+ * Reads the stored state from localStorage.
+ */
+ private void readStoredState() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+
+ fullX = readState(storage, STORAGE_FULL_X, -510);
+ fullY = readState(storage, STORAGE_FULL_Y, -230);
+ fullW = readState(storage, STORAGE_FULL_W, 500);
+ fullH = readState(storage, STORAGE_FULL_H, 150);
+ minX = readState(storage, STORAGE_MIN_X, -40);
+ minY = readState(storage, STORAGE_MIN_Y, -70);
+ setFontSize(readState(storage, STORAGE_FONT_SIZE, 1));
+
+ activateSection(readState(storage, STORAGE_ACTIVE_SECTION, 0));
+
+ setMinimized(readState(storage, STORAGE_IS_MINIMIZED, false));
+
+ applyPositionAndSize();
+ }
+
+ /**
+ * Writes the persistent state to localStorage.
+ */
+ private void writeStoredState() {
+ if (isClosed()) {
+ return;
+ }
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+
+ writeState(storage, STORAGE_FULL_X, fullX);
+ writeState(storage, STORAGE_FULL_Y, fullY);
+ writeState(storage, STORAGE_FULL_W, fullW);
+ writeState(storage, STORAGE_FULL_H, fullH);
+ writeState(storage, STORAGE_MIN_X, minX);
+ writeState(storage, STORAGE_MIN_Y, minY);
+ writeState(storage, STORAGE_FONT_SIZE, fontSize);
+
+ if (activeSection != null) {
+ writeState(storage, STORAGE_ACTIVE_SECTION,
+ activeSection.getTabButton());
+ }
+
+ writeState(storage, STORAGE_IS_MINIMIZED, minimized);
+ }
+
+ /**
+ * Writes the given value to the given {@link Storage} using the given key
+ * (automatically prefixed with {@value #STORAGE_PREFIX}).
+ *
+ * @param storage
+ * @param key
+ * @param value
+ */
+ static void writeState(Storage storage, String key, Object value) {
+ storage.setItem(STORAGE_PREFIX + key, String.valueOf(value));
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as an int from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static int readState(Storage storage, String key, int def) {
+ try {
+ return Integer.parseInt(storage.getItem(STORAGE_PREFIX + key));
+ } catch (Exception e) {
+ return def;
+ }
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as a boolean from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static boolean readState(Storage storage, String key, boolean def) {
+ try {
+ return Boolean.parseBoolean(storage.getItem(STORAGE_PREFIX + key));
+ } catch (Exception e) {
+ return def;
+ }
+ }
+
+ /**
+ * Returns the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) as a String from the given {@link Storage},
+ * returning the given default value instead if not successful (e.g missing
+ * item).
+ *
+ * @param storage
+ * @param key
+ * @param def
+ * @return stored or default value
+ */
+ static String readState(Storage storage, String key, String def) {
+ String val = storage.getItem(STORAGE_PREFIX + key);
+ return val != null ? val : def;
+ }
+
+ /**
+ * Resets (clears) the stored state from localStorage.
+ */
+ private void resetStoredState() {
+ Storage storage = Storage.getLocalStorageIfSupported();
+ if (storage == null) {
+ return;
+ }
+ // note: length is live
+ for (int i = 0; i < storage.getLength();) {
+ String key = storage.key(i);
+ if (key.startsWith(STORAGE_PREFIX)) {
+ removeState(storage, key.substring(STORAGE_PREFIX.length()));
+ } else {
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Removes the item with the given key (automatically prefixed with
+ * {@value #STORAGE_PREFIX}) from the given {@link Storage}.
+ *
+ * @param storage
+ * @param key
+ */
+ private void removeState(Storage storage, String key) {
+ storage.removeItem(STORAGE_PREFIX + key);
+ }
+
+ /**
+ * Applies the appropriate instance variables for width, height, x, y
+ * depending on if the window is minimized or not.
+ *
+ * If the value is negative, the window is positioned that amount of pixels
+ * from the right/bottom instead of left/top.
+ *
+ * Finally, the position is bounds-checked so that the window is not moved
+ * off-screen (the adjusted values are not saved).
+ */
+ private void applyPositionAndSize() {
+ int x = 0;
+ int y = 0;
+ if (minimized) {
+ x = minX;
+ if (minX < 0) {
+ x = Window.getClientWidth() + minX;
+ }
+ y = minY;
+ if (minY < 0) {
+ y = Window.getClientHeight() + minY;
+ }
+
+ } else {
+ x = fullX;
+ if (fullX < 0) {
+ x = Window.getClientWidth() + fullX;
+ }
+ y = fullY;
+ if (y < 0) {
+ y = Window.getClientHeight() + fullY;
+ }
+ content.setWidth(fullW + "px");
+ content.setHeight(fullH + "px");
+ }
+
+ // bounds check
+ if (x < 0) {
+ x = 0;
+ }
+ if (x > Window.getClientWidth() - getOffsetWidth()) {
+ // not allowed off-screen to the right
+ x = Window.getClientWidth() - getOffsetWidth();
+ }
+ if (y > Window.getClientHeight() - getOffsetHeight()) {
+ y = Window.getClientHeight() - getOffsetHeight();
+ }
+ if (y < 0) {
+ y = 0;
+ }
+
+ setPopupPosition(x, y);
+ }
+
+ /**
+ * Reads position and size from the DOM to local variables (which in turn
+ * can be stored to localStorage)
+ */
+ private void readPositionAndSize() {
+ int x = getPopupLeft();
+ int fromRight = Window.getClientWidth() - x - getOffsetWidth();
+ if (fromRight < x) {
+ x -= Window.getClientWidth();
+ }
+
+ int y = getPopupTop();
+ int fromBottom = Window.getClientHeight() - y - getOffsetHeight();
+ if (fromBottom < y) {
+ y -= Window.getClientHeight();
+ }
+
+ if (minimized) {
+ minY = y;
+ minX = x;
+ } else {
+ fullY = y;
+ fullX = x;
+ fullW = content.getOffsetWidth();
+ fullH = content.getOffsetHeight();
+ }
+
+ }
+
+ /**
+ * Adds the given {@link Section} as a tab in the {@link VDebugWindow} UI.
+ * {@link Section#getTabButton()} is called to obtain a button which is used
+ * tab.
+ *
+ * @param section
+ */
+ public void addSection(final Section section) {
+ Button b = section.getTabButton();
+ b.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ activateSection(section);
+ writeStoredState();
+ }
+ });
+ b.setStylePrimaryName(STYLENAME_TAB);
+ tabs.add(b);
+ sections.add(section);
+
+ if (activeSection == null) {
+ activateSection(section);
+ }
+ }
+
+ /**
+ * Activates the given {@link Section}
+ *
+ * @param section
+ */
+ void activateSection(Section section) {
+ if (section != null && section != activeSection) {
+ Highlight.hideAll();
+ // remove old stuff
+ if (activeSection != null) {
+ activeSection.hide();
+ content.remove(activeSection.getContent());
+ sectionHead.remove(activeSection.getControls());
+ }
+ // update tab styles
+ for (int i = 0; i < tabs.getWidgetCount(); i++) {
+ Widget tab = tabs.getWidget(i);
+ tab.setStyleDependentName(STYLENAME_SELECTED,
+ tab == section.getTabButton());
+ }
+ // add new stuff
+ content.add(section.getContent());
+ sectionHead.add(section.getControls());
+ activeSection = section;
+ activeSection.show();
+ }
+ }
+
+ void activateSection(int n) {
+ if (n < sections.size()) {
+ activateSection(sections.get(n));
+ }
+ }
+
+ /**
+ * Toggles the window between minimized and full states.
+ */
+ private void toggleMinimized() {
+ setMinimized(!minimized);
+ writeStoredState();
+ }
+
+ /**
+ * Sets whether or not the window is minimized.
+ *
+ * @param minimized
+ */
+ private void setMinimized(boolean minimized) {
+ this.minimized = minimized;
+
+ tabs.setVisible(!minimized);
+ content.setVisible(!minimized);
+ sectionHead.setVisible(!minimized);
+ menu.setVisible(!minimized);
+
+ applyPositionAndSize();
+ }
+
+ /**
+ * Sets the font size in use.
+ *
+ * @param size
+ */
+ private void setFontSize(int size) {
+ removeStyleDependentName("size" + fontSize);
+ fontSize = size;
+ addStyleDependentName("size" + size);
+ }
+
+ /**
+ * Gets the font size currently in use.
+ *
+ * @return
+ */
+ private int getFontSize() {
+ return fontSize;
+ }
+
+ /**
+ * Gets the milliseconds since application start.
+ *
+ * @return
+ */
+ static int getMillisSinceStart() {
+ return start.elapsedMillis();
+ }
+
+ /**
+ * Gets the milliseconds since last {@link #resetTimer()} call.
+ *
+ * @return
+ */
+ static int getMillisSinceReset() {
+ return lastReset.elapsedMillis();
+ }
+
+ /**
+ * Resets the timer.
+ *
+ * @return Milliseconds elapsed since the timer was last reset.
+ */
+ static int resetTimer() {
+ int sinceLast = lastReset.elapsedMillis();
+ lastReset = new Duration();
+ return sinceLast;
+ }
+
+ /**
+ * Gets a nicely formatted string with timing information suitable for
+ * display in tooltips.
+ *
+ * @param sinceStart
+ * @param sinceReset
+ * @return
+ */
+ static String getTimingTooltip(int sinceStart, int sinceReset) {
+ String title = formatDuration(sinceStart) + " since start";
+ title += ", &#10;" + formatDuration(sinceReset) + " since timer reset";
+ title += " &#10;@ "
+ + DateTimeFormat.getFormat("HH:mm:ss.SSS").format(new Date());
+ return title;
+ }
+
+ /**
+ * Formats the given milliseconds as hours, minutes, seconds and
+ * milliseconds.
+ *
+ * @param ms
+ * @return
+ */
+ static String formatDuration(int ms) {
+ NumberFormat fmt = NumberFormat.getFormat("00");
+ String seconds = fmt.format((ms / 1000) % 60);
+ String minutes = fmt.format((ms / (1000 * 60)) % 60);
+ String hours = fmt.format((ms / (1000 * 60 * 60)) % 24);
+
+ String millis = NumberFormat.getFormat("000").format(ms % 1000);
+
+ return hours + "h " + minutes + "m " + seconds + "s " + millis + "ms";
+ }
+
+ /**
+ * Called when the window is initialized.
+ */
+ public void init() {
+
+ show();
+ readStoredState();
+
+ Window.addResizeHandler(new com.google.gwt.event.logical.shared.ResizeHandler() {
+
+ Timer t = new Timer() {
+ @Override
+ public void run() {
+ applyPositionAndSize();
+ }
+ };
+
+ @Override
+ public void onResize(ResizeEvent event) {
+ t.cancel();
+ // TODO less
+ t.schedule(1000);
+ }
+ });
+ }
+
+ /**
+ * Called when the result from analyzeLayouts is received.
+ *
+ * @param ac
+ * @param meta
+ */
+ public void meta(ApplicationConnection ac, ValueMap meta) {
+ if (isClosed()) {
+ return;
+ }
+ for (Section s : sections) {
+ s.meta(ac, meta);
+ }
+ }
+
+ /**
+ * Called when a response is received
+ *
+ * @param ac
+ * @param uidl
+ */
+ public void uidl(ApplicationConnection ac, ValueMap uidl) {
+ if (isClosed()) {
+ return;
+ }
+ for (Section s : sections) {
+ s.uidl(ac, uidl);
+ }
+ }
+
+ /*
+ * Inner classes
+ */
+
+ /**
+ * Popup menu for {@link VDebugWindow}.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class Menu extends VOverlay {
+ FlowPanel content = new FlowPanel();
+
+ DebugButton[] sizes = new DebugButton[] {
+ new DebugButton(null, "Small", "A"),
+ new DebugButton(null, "Medium", "A"),
+ new DebugButton(null, "Large", "A") };
+
+ DebugButton[] modes = new DebugButton[] {
+ new DebugButton(Icon.DEVMODE_OFF,
+ "Debug only (causes page reload)"),
+ new DebugButton(Icon.DEVMODE_ON, "DevMode (causes page reload)"),
+ new DebugButton(Icon.DEVMODE_SUPER,
+ "SuperDevMode (causes page reload)") };
+
+ Menu() {
+ super(true, true);
+ setWidget(content);
+
+ setStylePrimaryName(STYLENAME + "-menu");
+ content.setStylePrimaryName(STYLENAME + "-menu-content");
+
+ FlowPanel size = new FlowPanel();
+ content.add(size);
+
+ final ClickHandler sizeHandler = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ if (b == event.getSource()) {
+ setSize(i);
+ }
+ }
+ hide();
+ }
+ };
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ b.setStyleDependentName("size" + i, true);
+ b.addClickHandler(sizeHandler);
+ size.add(b);
+ }
+
+ FlowPanel mode = new FlowPanel();
+ content.add(mode);
+ final ClickHandler modeHandler = new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ for (int i = 0; i < modes.length; i++) {
+ Button b = modes[i];
+ if (b == event.getSource()) {
+ setDevMode(i);
+ }
+ }
+ hide();
+ }
+ };
+ modes[getDevMode()].setActive(true);
+ for (int i = 0; i < modes.length; i++) {
+ Button b = modes[i];
+ b.addClickHandler(modeHandler);
+ mode.add(b);
+ }
+
+ Button reset = new DebugButton(Icon.RESET, "Restore defaults.",
+ " Reset");
+ reset.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ resetStoredState();
+ readStoredState();
+ hide();
+ }
+ });
+ content.add(reset);
+ }
+
+ private void setSize(int size) {
+ for (int i = 0; i < sizes.length; i++) {
+ Button b = sizes[i];
+ b.setStyleDependentName(STYLENAME_ACTIVE, i == size);
+ }
+ setFontSize(size);
+ writeStoredState();
+ }
+
+ @Override
+ public void show() {
+ super.show();
+ setSize(getFontSize());
+ }
+
+ private int getDevMode() {
+ if (Location.getParameter("superdevmode") != null) {
+ return 2;
+ } else if (Location.getParameter("gwt.codesvr") != null) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ private void setDevMode(int mode) {
+ UrlBuilder u = Location.createUrlBuilder();
+ switch (mode) {
+ case 2:
+ u.setParameter("superdevmode", "");
+ u.removeParameter("gwt.codesvr");
+ break;
+ case 1:
+ u.setParameter("gwt.codesvr", "localhost:9997");
+ u.removeParameter("superdevmode");
+ break;
+ default:
+ u.removeParameter("gwt.codesvr");
+ u.removeParameter("superdevmode");
+ }
+ Location.assign(u.buildString());
+ }
+
+ }
+
+ /**
+ * Handler for moving window.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class MoveHandler implements MouseDownHandler,
+ NativePreviewHandler {
+
+ HandlerRegistration handler;
+ int startX;
+ int startY;
+ int startTop;
+ int startLeft;
+
+ // moving stopped, remove handler on next event
+ boolean stop;
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONMOUSEMOVE && !stop
+ && hasMoved(event.getNativeEvent())) {
+ int dx = event.getNativeEvent().getClientX() - startX;
+ int dy = event.getNativeEvent().getClientY() - startY;
+
+ setPopupPosition(startLeft + dx, startTop + dy);
+ event.cancel();
+
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ stop = true;
+ if (hasMoved(event.getNativeEvent())) {
+ event.cancel();
+ }
+
+ } else if (event.getTypeInt() == Event.ONCLICK) {
+ stop = true;
+ if (hasMoved(event.getNativeEvent())) {
+ event.cancel();
+ }
+
+ } else if (stop) {
+ stop = false;
+ handler.removeHandler();
+ handler = null;
+
+ readPositionAndSize();
+ writeStoredState();
+ }
+ }
+
+ private boolean hasMoved(NativeEvent event) {
+ return Math.abs(startX - event.getClientX()) > MOVE_TRESHOLD
+ || Math.abs(startY - event.getClientY()) > MOVE_TRESHOLD;
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ if (handler == null) {
+ handler = Event.addNativePreviewHandler(MoveHandler.this);
+ }
+ startX = event.getClientX();
+ startY = event.getClientY();
+ startLeft = getPopupLeft();
+ startTop = getPopupTop();
+ stop = false;
+ event.preventDefault();
+ }
+
+ }
+
+ /**
+ * Handler for resizing window.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd
+ */
+ protected class ResizeHandler implements MouseDownHandler,
+ MouseMoveHandler, NativePreviewHandler {
+
+ boolean resizeLeft;
+ boolean resizeRight;
+ boolean resizeUp;
+ boolean resizeDown;
+
+ boolean sizing;
+
+ HandlerRegistration dragHandler;
+
+ int startX;
+ int startY;
+ int startW;
+ int startH;
+ int startTop;
+ int startLeft;
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ sizing = updateResizeFlags(event);
+
+ if (sizing) {
+ startX = event.getClientX();
+ startY = event.getClientY();
+
+ startW = content.getOffsetWidth();
+ startH = content.getOffsetHeight();
+
+ startTop = getPopupTop();
+ startLeft = getPopupLeft();
+
+ dragHandler = Event.addNativePreviewHandler(this);
+
+ event.preventDefault();
+ }
+
+ }
+
+ @Override
+ public void onMouseMove(MouseMoveEvent event) {
+ updateResizeFlags(event);
+ updateCursor();
+ }
+
+ private void updateCursor() {
+ Element c = content.getElement();
+ if (resizeLeft) {
+ if (resizeUp) {
+ c.getStyle().setCursor(Cursor.NW_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.SW_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.W_RESIZE);
+ }
+ } else if (resizeRight) {
+ if (resizeUp) {
+ c.getStyle().setCursor(Cursor.NE_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.SE_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.E_RESIZE);
+ }
+ } else if (resizeUp) {
+ c.getStyle().setCursor(Cursor.N_RESIZE);
+ } else if (resizeDown) {
+ c.getStyle().setCursor(Cursor.S_RESIZE);
+ } else {
+ c.getStyle().setCursor(Cursor.AUTO);
+ }
+ }
+
+ private boolean updateResizeFlags(MouseEvent event) {
+ Element c = getElement();
+ int w = c.getOffsetWidth();
+ int h = c.getOffsetHeight() - head.getOffsetHeight();
+ int x = event.getRelativeX(c);
+ int y = event.getRelativeY(c) - head.getOffsetHeight();
+
+ resizeLeft = x < HANDLE_SIZE;
+ resizeRight = x > (w - HANDLE_SIZE);
+ resizeUp = y < HANDLE_SIZE;
+ resizeDown = y > (h - HANDLE_SIZE);
+
+ return resizeLeft || resizeRight || resizeUp || resizeDown;
+
+ }
+
+ @Override
+ public void onPreviewNativeEvent(NativePreviewEvent event) {
+ if (event.getTypeInt() == Event.ONMOUSEMOVE) {
+
+ int dx = event.getNativeEvent().getClientX() - startX;
+ int dy = event.getNativeEvent().getClientY() - startY;
+
+ int minw = tabs.getOffsetWidth() + controls.getOffsetWidth();
+ if (resizeLeft) {
+ int w = startW - dx;
+ if (w >= minw) {
+ content.setWidth(w + "px");
+ setPopupPosition(startLeft + dx, getPopupTop());
+ }
+ } else if (resizeRight) {
+ int w = startW + dx;
+ if (w >= minw) {
+ content.setWidth(w + "px");
+ }
+ }
+ if (resizeUp) {
+ int h = startH - dy;
+ if (h >= MIN_HEIGHT) {
+ content.setHeight(h + "px");
+ setPopupPosition(getPopupLeft(), startTop + dy);
+ }
+ } else if (resizeDown) {
+ int h = startH + dy;
+ if (h >= MIN_HEIGHT) {
+ content.setHeight(h + "px");
+ }
+ }
+
+ } else if (event.getTypeInt() == Event.ONMOUSEUP) {
+ dragHandler.removeHandler();
+ dragHandler = null;
+ content.getElement().getStyle().setCursor(Cursor.AUTO);
+ sizing = false;
+ readPositionAndSize();
+ writeStoredState();
+ }
+
+ event.cancel();
+ }
+
+ }
+
+}
diff --git a/client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java b/client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java
index ce79b4c64c..8e6ad25407 100644
--- a/client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java
+++ b/client/src/com/vaadin/client/extensions/javascriptmanager/JavaScriptManagerConnector.java
@@ -23,8 +23,8 @@ import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.json.client.JSONArray;
import com.vaadin.client.ServerConnector;
-import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.communication.JavaScriptMethodInvocation;
+import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.extensions.AbstractExtensionConnector;
import com.vaadin.shared.extension.javascriptmanager.ExecuteJavaScriptRpc;
import com.vaadin.shared.extension.javascriptmanager.JavaScriptManagerState;
diff --git a/client/src/com/vaadin/client/metadata/Property.java b/client/src/com/vaadin/client/metadata/Property.java
index c0c375c14c..2e0ea49c88 100644
--- a/client/src/com/vaadin/client/metadata/Property.java
+++ b/client/src/com/vaadin/client/metadata/Property.java
@@ -88,4 +88,29 @@ public class Property {
return getSignature();
}
+ /**
+ * Gets the property name formatted for displaying in a user interface. This
+ * returns a string where e.g. "camelCase" has been converted to
+ * "Camel case".
+ *
+ * @return the name of this property, formatted for humans to read
+ */
+ public String getDisplayName() {
+ String camelCase = getName();
+ StringBuilder b = new StringBuilder(camelCase.length());
+ for (int i = 0; i < camelCase.length(); i++) {
+ char charAt = camelCase.charAt(i);
+ if (i == 0) {
+ // First char always upper case
+ b.append(Character.toUpperCase(charAt));
+ } else if (Character.isUpperCase(charAt)) {
+ b.append(' ');
+ b.append(Character.toLowerCase(charAt));
+ } else {
+ b.append(charAt);
+ }
+ }
+ return b.toString();
+ }
+
}
diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java
index dff02749f8..aa37d75dc8 100644
--- a/client/src/com/vaadin/client/metadata/TypeDataStore.java
+++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java
@@ -41,7 +41,6 @@ public class TypeDataStore {
private final FastStringSet delayedMethods = FastStringSet.create();
private final FastStringSet lastOnlyMethods = FastStringSet.create();
- private final FastStringSet hasGetTooltipInfo = FastStringSet.create();
private final FastStringMap<Type> returnTypes = FastStringMap.create();
private final FastStringMap<Invoker> invokers = FastStringMap.create();
@@ -291,22 +290,4 @@ public class TypeDataStore {
public static boolean hasProperties(Type type) {
return get().properties.containsKey(type.getSignature());
}
-
- /**
- * @deprecated As of 7.0.1. This is just a hack to avoid breaking backwards
- * compatibility and will be removed in Vaadin 7.1
- */
- @Deprecated
- public void setHasGetTooltipInfo(Class<?> clazz) {
- hasGetTooltipInfo.add(getType(clazz).getSignature());
- }
-
- /**
- * @deprecated As of 7.0.1. This is just a hack to avoid breaking backwards
- * compatibility and will be removed in Vaadin 7.1
- */
- @Deprecated
- public static boolean getHasGetTooltipInfo(Class clazz) {
- return get().hasGetTooltipInfo.contains(getType(clazz).getSignature());
- }
}
diff --git a/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java b/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java
index 2f97d30ece..e91abe9663 100644
--- a/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java
+++ b/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java
@@ -78,9 +78,8 @@ public abstract class AbstractClickEventHandler implements MouseDownHandler,
&& elementUnderMouse == lastMouseDownTarget) {
mouseUpPreviewMatched = true;
} else {
- VConsole.log("Ignoring mouseup from "
- + elementUnderMouse + " when mousedown was on "
- + lastMouseDownTarget);
+ VConsole.log("Ignoring mouseup from " + elementUnderMouse
+ + " when mousedown was on " + lastMouseDownTarget);
}
}
}
diff --git a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java
index ecd6abae08..13d1e6d56c 100644
--- a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java
+++ b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java
@@ -35,7 +35,6 @@ import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.metadata.NoDataException;
import com.vaadin.client.metadata.Type;
import com.vaadin.client.metadata.TypeData;
-import com.vaadin.client.metadata.TypeDataStore;
import com.vaadin.client.ui.datefield.PopupDateFieldConnector;
import com.vaadin.client.ui.ui.UIConnector;
import com.vaadin.shared.AbstractComponentState;
@@ -205,11 +204,13 @@ public abstract class AbstractComponentConnector extends AbstractConnector
}
}
- private void updateComponentSize() {
- Profiler.enter("AbstractComponentConnector.updateComponentSize");
+ protected void updateComponentSize() {
+ updateComponentSize(getState().width == null ? "" : getState().width,
+ getState().height == null ? "" : getState().height);
+ }
- String newWidth = getState().width == null ? "" : getState().width;
- String newHeight = getState().height == null ? "" : getState().height;
+ protected void updateComponentSize(String newWidth, String newHeight) {
+ Profiler.enter("AbstractComponentConnector.updateComponentSize");
// Parent should be updated if either dimension changed between relative
// and non-relative
@@ -428,40 +429,13 @@ public abstract class AbstractComponentConnector extends AbstractConnector
}
}
- /**
- * {@inheritDoc}
- *
- * <p>
- * When overriding this method, {@link #hasTooltip()} should also be
- * overridden to return true in all situations where this method might
- * return a non-empty result.
- * </p>
- *
- * @see ComponentConnector#getTooltipInfo(Element)
- */
@Override
public TooltipInfo getTooltipInfo(Element element) {
return new TooltipInfo(getState().description, getState().errorMessage);
}
- /**
- * Check whether there might be a tooltip for this component. The framework
- * will only add event listeners for automatically handling tooltips (using
- * {@link #getTooltipInfo(Element)}) if this method returns true.
- *
- * @return <code>true</code> if some part of the component might have a
- * tooltip, otherwise <code>false</code>
- */
- private boolean hasTooltip() {
- /*
- * Hack to avoid breaking backwards compatibility - use a generator to
- * know whether there's a custom implementation of getTooltipInfo, and
- * in that case always assume that there might be tooltip.
- */
- if (TypeDataStore.getHasGetTooltipInfo(getClass())) {
- return true;
- }
-
+ @Override
+ public boolean hasTooltip() {
// Normally, there is a tooltip if description or errorMessage is set
AbstractComponentState state = getState();
if (state.description != null && !state.description.equals("")) {
diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java
index 2c76aa93fe..6855c5cd2d 100644
--- a/client/src/com/vaadin/client/ui/AbstractConnector.java
+++ b/client/src/com/vaadin/client/ui/AbstractConnector.java
@@ -439,6 +439,7 @@ public abstract class AbstractConnector implements ServerConnector,
*
* @see com.vaadin.client.ServerConnector#hasEventListener(java.lang.String)
*/
+ @Override
public boolean hasEventListener(String eventIdentifier) {
Set<String> reg = getState().registeredEventListeners;
return (reg != null && reg.contains(eventIdentifier));
diff --git a/client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java b/client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java
index 4a6aefd082..d833f076e4 100644
--- a/client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java
+++ b/client/src/com/vaadin/client/ui/AbstractHasComponentsConnector.java
@@ -20,9 +20,9 @@ import java.util.List;
import com.google.gwt.event.shared.HandlerRegistration;
import com.vaadin.client.ComponentConnector;
-import com.vaadin.client.HasComponentsConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
+import com.vaadin.client.HasComponentsConnector;
public abstract class AbstractHasComponentsConnector extends
AbstractComponentConnector implements HasComponentsConnector,
diff --git a/client/src/com/vaadin/client/ui/VAbsoluteLayout.java b/client/src/com/vaadin/client/ui/VAbsoluteLayout.java
index 88fbae6e88..dc080bcf7d 100644
--- a/client/src/com/vaadin/client/ui/VAbsoluteLayout.java
+++ b/client/src/com/vaadin/client/ui/VAbsoluteLayout.java
@@ -18,7 +18,6 @@ package com.vaadin.client.ui;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style;
-import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.ComplexPanel;
@@ -304,108 +303,43 @@ public class VAbsoluteLayout extends ComplexPanel {
* is added or removed
*/
public void layoutVertically() {
+ layout();
+ }
+
+ /**
+ * Performs an horizontal layout. Should be called when a widget is add or
+ * removed
+ */
+ public void layoutHorizontally() {
+ layout();
+ }
+
+ private void layout() {
for (Widget widget : getChildren()) {
if (widget instanceof AbsoluteWrapper) {
AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
- /*
- * Cleanup old wrappers which have been left empty by other
- * inner layouts moving the widget from the wrapper into their
- * own hierarchy. This usually happens when a call to
- * setWidget(widget) is done in an inner layout which
- * automatically detaches the widget from the parent, in this
- * case the wrapper, and re-attaches it somewhere else. This has
- * to be done in the layout phase since the order of the
- * hierarchy events are not defined.
- */
- if (wrapper.getWidget() == null) {
- wrapper.destroy();
- super.remove(wrapper);
- continue;
- }
-
- Style wrapperStyle = wrapper.getElement().getStyle();
- Style widgetStyle = wrapper.getWidget().getElement().getStyle();
-
- // Ensure previous heights do not affect the measures
- wrapperStyle.clearHeight();
-
- if (widgetStyle.getHeight() != null
- && widgetStyle.getHeight().endsWith("%")) {
- int h;
- if (wrapper.top != null && wrapper.bottom != null) {
- h = wrapper.getOffsetHeight();
- } else if (wrapper.bottom != null) {
- // top not defined, available space 0... bottom of
- // wrapper
- h = wrapper.getElement().getOffsetTop()
- + wrapper.getOffsetHeight();
- } else {
- // top defined or both undefined, available space ==
- // canvas - top
- h = canvas.getOffsetHeight()
- - wrapper.getElement().getOffsetTop();
- }
- wrapperStyle.setHeight(h, Unit.PX);
- }
-
wrapper.updateCaptionPosition();
}
}
}
/**
- * Performs an horizontal layout. Should be called when a widget is add or
- * removed
+ * Cleanup old wrappers which have been left empty by other inner layouts
+ * moving the widget from the wrapper into their own hierarchy. This usually
+ * happens when a call to setWidget(widget) is done in an inner layout which
+ * automatically detaches the widget from the parent, in this case the
+ * wrapper, and re-attaches it somewhere else. This has to be done in the
+ * layout phase since the order of the hierarchy events are not defined.
*/
- public void layoutHorizontally() {
+ public void cleanupWrappers() {
for (Widget widget : getChildren()) {
if (widget instanceof AbsoluteWrapper) {
AbsoluteWrapper wrapper = (AbsoluteWrapper) widget;
-
- /*
- * Cleanup old wrappers which have been left empty by other
- * inner layouts moving the widget from the wrapper into their
- * own hierarchy. This usually happens when a call to
- * setWidget(widget) is done in an inner layout which
- * automatically detaches the widget from the parent, in this
- * case the wrapper, and re-attaches it somewhere else. This has
- * to be done in the layout phase since the order of the
- * hierarchy events are not defined.
- */
if (wrapper.getWidget() == null) {
wrapper.destroy();
super.remove(wrapper);
continue;
}
-
- Style wrapperStyle = wrapper.getElement().getStyle();
- Style widgetStyle = wrapper.getWidget().getElement().getStyle();
-
- // Ensure previous heights do not affect the measures
- wrapperStyle.clearWidth();
-
- if (widgetStyle.getWidth() != null
- && widgetStyle.getWidth().endsWith("%")) {
- int w;
- if (wrapper.left != null && wrapper.right != null) {
- w = wrapper.getOffsetWidth();
- } else if (wrapper.right != null) {
- // left == null
- // available width == right edge == offsetleft + width
- w = wrapper.getOffsetWidth()
- + wrapper.getElement().getOffsetLeft();
- } else {
- // left != null && right == null || left == null &&
- // right == null
- // available width == canvas width - offset left
- w = canvas.getOffsetWidth()
- - wrapper.getElement().getOffsetLeft();
- }
- wrapperStyle.setWidth(w, Unit.PX);
- }
-
- wrapper.updateCaptionPosition();
}
}
}
diff --git a/client/src/com/vaadin/client/ui/VButton.java b/client/src/com/vaadin/client/ui/VButton.java
index decfb7c0cc..c67a9f8747 100644
--- a/client/src/com/vaadin/client/ui/VButton.java
+++ b/client/src/com/vaadin/client/ui/VButton.java
@@ -16,6 +16,7 @@
package com.vaadin.client.ui;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
@@ -25,7 +26,6 @@ import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
-import com.google.gwt.user.client.ui.Accessibility;
import com.google.gwt.user.client.ui.FocusWidget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
@@ -103,7 +103,7 @@ public class VButton extends FocusWidget implements ClickHandler {
| Event.KEYEVENTS);
// Add a11y role "button"
- Accessibility.setRole(getElement(), Accessibility.ROLE_BUTTON);
+ Roles.getButtonRole().set(getElement());
getElement().appendChild(wrapper);
wrapper.appendChild(captionElement);
@@ -357,14 +357,14 @@ public class VButton extends FocusWidget implements ClickHandler {
this.enabled = enabled;
if (!enabled) {
cleanupCaptureState();
- Accessibility.removeState(getElement(),
- Accessibility.STATE_PRESSED);
+ Roles.getButtonRole().setAriaDisabledState(getElement(),
+ !enabled);
super.setTabIndex(-1);
} else {
- Accessibility.setState(getElement(),
- Accessibility.STATE_PRESSED, "false");
+ Roles.getButtonRole().removeAriaDisabledState(getElement());
super.setTabIndex(tabIndex);
}
+
}
}
diff --git a/client/src/com/vaadin/client/ui/VCalendar.java b/client/src/com/vaadin/client/ui/VCalendar.java
new file mode 100644
index 0000000000..c5c12f2d72
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/VCalendar.java
@@ -0,0 +1,1446 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.DockPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.calendar.schedule.CalendarDay;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.client.ui.calendar.schedule.DayToolbar;
+import com.vaadin.client.ui.calendar.schedule.MonthGrid;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayToolbar;
+import com.vaadin.client.ui.calendar.schedule.SimpleWeekToolbar;
+import com.vaadin.client.ui.calendar.schedule.WeekGrid;
+import com.vaadin.client.ui.calendar.schedule.WeeklyLongEvents;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Client side implementation for Calendar
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class VCalendar extends Composite {
+
+ public static final String ATTR_FIRSTDAYOFWEEK = "firstDay";
+ public static final String ATTR_LASTDAYOFWEEK = "lastDay";
+ public static final String ATTR_FIRSTHOUROFDAY = "firstHour";
+ public static final String ATTR_LASTHOUROFDAY = "lastHour";
+
+ // private boolean hideWeekends;
+ private String[] monthNames;
+ private String[] dayNames;
+ private boolean format;
+ private final DockPanel outer = new DockPanel();
+ private int rows;
+
+ private boolean rangeSelectAllowed = true;
+ private boolean rangeMoveAllowed = true;
+ private boolean eventResizeAllowed = true;
+ private boolean eventMoveAllowed = true;
+
+ private final SimpleDayToolbar nameToolbar = new SimpleDayToolbar();
+
+ private final DayToolbar dayToolbar = new DayToolbar(this);
+ private final SimpleWeekToolbar weekToolbar;
+ private WeeklyLongEvents weeklyLongEvents;
+ private MonthGrid monthGrid;
+ private WeekGrid weekGrid;
+ private int intWidth = 0;
+ private int intHeight = 0;
+
+ protected final DateTimeFormat dateformat_datetime = DateTimeFormat
+ .getFormat("yyyy-MM-dd HH:mm:ss");
+ protected final DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat("yyyy-MM-dd");
+ protected final DateTimeFormat time12format_date = DateTimeFormat
+ .getFormat("h:mm a");
+ protected final DateTimeFormat time24format_date = DateTimeFormat
+ .getFormat("HH:mm");
+
+ private boolean readOnly = false;
+ private boolean disabled = false;
+
+ private boolean isHeightUndefined = false;
+
+ private boolean isWidthUndefined = false;
+ private int firstDay;
+ private int lastDay;
+ private int firstHour;
+ private int lastHour;
+
+ /**
+ * Listener interface for listening to event click events
+ */
+ public interface DateClickListener {
+ /**
+ * Triggered when a date was clicked
+ *
+ * @param date
+ * The date and time that was clicked
+ */
+ void dateClick(String date);
+ }
+
+ /**
+ * Listener interface for listening to week number click events
+ */
+ public interface WeekClickListener {
+ /**
+ * Called when a week number was selected.
+ *
+ * @param event
+ * The format of the vent string is "<year>w<week>"
+ */
+ void weekClick(String event);
+ }
+
+ /**
+ * Listener interface for listening to forward events
+ */
+ public interface ForwardListener {
+
+ /**
+ * Called when the calendar should move one view forward
+ */
+ void forward();
+ }
+
+ /**
+ * Listener interface for listening to backward events
+ */
+ public interface BackwardListener {
+
+ /**
+ * Called when the calendar should move one view backward
+ */
+ void backward();
+ }
+
+ /**
+ * Listener interface for listening to selection events
+ */
+ public interface RangeSelectListener {
+
+ /**
+ * Called when a user selected a new event by highlighting an area of
+ * the calendar.
+ *
+ * FIXME Fix the value nonsense.
+ *
+ * @param value
+ * The format of the value string is
+ * "<year>:<start-minutes>:<end-minutes>" if called from the
+ * {@link SimpleWeekToolbar} and "<yyyy-MM-dd>TO<yyyy-MM-dd>"
+ * if called from {@link MonthGrid}
+ */
+ void rangeSelected(String value);
+ }
+
+ /**
+ * Listener interface for listening to click events
+ */
+ public interface EventClickListener {
+ /**
+ * Called when an event was clicked
+ *
+ * @param event
+ * The event that was clicked
+ */
+ void eventClick(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for listening to event moved events. Occurs when a
+ * user drags an event to a new position
+ */
+ public interface EventMovedListener {
+ /**
+ * Triggered when an event was dragged to a new position and the start
+ * and end dates was changed
+ *
+ * @param event
+ * The event that was moved
+ */
+ void eventMoved(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for when an event gets resized (its start or end date
+ * changes)
+ */
+ public interface EventResizeListener {
+ /**
+ * Triggers when the time limits for the event was changed.
+ *
+ * @param event
+ * The event that was changed. The new time limits have been
+ * updated in the event before calling this method
+ */
+ void eventResized(CalendarEvent event);
+ }
+
+ /**
+ * Listener interface for listening to scroll events.
+ */
+ public interface ScrollListener {
+ /**
+ * Triggered when the calendar is scrolled
+ *
+ * @param scrollPosition
+ * The scroll position in pixels as returned by
+ * {@link ScrollPanel#getScrollPosition()}
+ */
+ void scroll(int scrollPosition);
+ }
+
+ /**
+ * Listener interface for listening to mouse events.
+ */
+ public interface MouseEventListener {
+ /**
+ * Triggered when a user wants an context menu
+ *
+ * @param event
+ * The context menu event
+ *
+ * @param widget
+ * The widget that the context menu should be added to
+ */
+ void contextMenu(ContextMenuEvent event, Widget widget);
+ }
+
+ /**
+ * Default constructor
+ */
+ public VCalendar() {
+ weekToolbar = new SimpleWeekToolbar(this);
+ initWidget(outer);
+ setStylePrimaryName("v-calendar");
+ blockSelect(getElement());
+ }
+
+ /**
+ * Hack for IE to not select text when dragging.
+ *
+ * @param e
+ * The element to apply the hack on
+ */
+ private native void blockSelect(Element e)
+ /*-{
+ e.onselectstart = function() {
+ return false;
+ }
+
+ e.ondragstart = function() {
+ return false;
+ }
+ }-*/;
+
+ private void updateEventsToWeekGrid(CalendarEvent[] events) {
+ List<CalendarEvent> allDayLong = new ArrayList<CalendarEvent>();
+ List<CalendarEvent> belowDayLong = new ArrayList<CalendarEvent>();
+
+ for (CalendarEvent e : events) {
+ if (e.isAllDay()) {
+ // Event is set on one "allDay" event or more than one.
+ allDayLong.add(e);
+
+ } else {
+ // Event is set only on one day.
+ belowDayLong.add(e);
+ }
+ }
+
+ weeklyLongEvents.addEvents(allDayLong);
+
+ for (CalendarEvent e : belowDayLong) {
+ weekGrid.addEvent(e);
+ }
+ }
+
+ /**
+ * Adds events to the month grid
+ *
+ * @param events
+ * The events to add
+ * @param drawImmediately
+ * Should the grid be rendered immediately. (currently not in
+ * use)
+ *
+ */
+ public void updateEventsToMonthGrid(Collection<CalendarEvent> events,
+ boolean drawImmediately) {
+ for (CalendarEvent e : sortEventsByDuration(events)) {
+ // FIXME Why is drawImmediately not used ?????
+ addEventToMonthGrid(e, false);
+ }
+ }
+
+ private void addEventToMonthGrid(CalendarEvent e, boolean renderImmediately) {
+ Date when = e.getStart();
+ Date to = e.getEnd();
+ boolean eventAdded = false;
+ boolean inProgress = false; // Event adding has started
+ boolean eventMoving = false;
+ List<SimpleDayCell> dayCells = new ArrayList<SimpleDayCell>();
+ List<SimpleDayCell> timeCells = new ArrayList<SimpleDayCell>();
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ if (eventAdded) {
+ break;
+ }
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(row,
+ cell);
+ if (isEventInDay(when, to, sdc.getDate())
+ && isEventInDayWithTime(when, to, sdc.getDate(),
+ e.getEndTime(), e.isAllDay())) {
+ if (!eventMoving) {
+ eventMoving = sdc.getMoveEvent() != null;
+ }
+ long d = e.getRangeInMilliseconds();
+ if ((d > 0 && d <= DateConstants.DAYINMILLIS)
+ && !e.isAllDay()) {
+ timeCells.add(sdc);
+ } else {
+ dayCells.add(sdc);
+ }
+ inProgress = true;
+ continue;
+ } else if (inProgress) {
+ eventAdded = true;
+ inProgress = false;
+ break;
+ }
+ }
+ }
+
+ updateEventSlotIndex(e, dayCells);
+ updateEventSlotIndex(e, timeCells);
+
+ for (SimpleDayCell sdc : dayCells) {
+ sdc.addCalendarEvent(e);
+ }
+ for (SimpleDayCell sdc : timeCells) {
+ sdc.addCalendarEvent(e);
+ }
+
+ if (renderImmediately) {
+ reDrawAllMonthEvents(!eventMoving);
+ }
+ }
+
+ /*
+ * We must also handle the special case when the event lasts exactly for 24
+ * hours, thus spanning two days e.g. from 1.1.2001 00:00 to 2.1.2001 00:00.
+ * That special case still should span one day when rendered.
+ */
+ @SuppressWarnings("deprecation")
+ // Date methods are not deprecated in GWT
+ private boolean isEventInDayWithTime(Date from, Date to, Date date,
+ Date endTime, boolean isAllDay) {
+ return (isAllDay || !(to.getDay() == date.getDay()
+ && from.getDay() != to.getDay() && isMidnight(endTime)));
+ }
+
+ private void updateEventSlotIndex(CalendarEvent e, List<SimpleDayCell> cells) {
+ if (cells.isEmpty()) {
+ return;
+ }
+
+ if (e.getSlotIndex() == -1) {
+ // Update slot index
+ int newSlot = -1;
+ for (SimpleDayCell sdc : cells) {
+ int slot = sdc.getEventCount();
+ if (slot > newSlot) {
+ newSlot = slot;
+ }
+ }
+ newSlot++;
+
+ for (int i = 0; i < newSlot; i++) {
+ // check for empty slot
+ if (isSlotEmpty(e, i, cells)) {
+ newSlot = i;
+ break;
+ }
+ }
+ e.setSlotIndex(newSlot);
+ }
+ }
+
+ private void reDrawAllMonthEvents(boolean clearCells) {
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(row,
+ cell);
+ sdc.reDraw(clearCells);
+ }
+ }
+ }
+
+ private boolean isSlotEmpty(CalendarEvent addedEvent, int slotIndex,
+ List<SimpleDayCell> cells) {
+ for (SimpleDayCell sdc : cells) {
+ CalendarEvent e = sdc.getCalendarEvent(slotIndex);
+ if (e != null && !e.equals(addedEvent)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Remove a month event from the view
+ *
+ * @param target
+ * The event to remove
+ *
+ * @param repaintImmediately
+ * Should we repaint after the event was removed?
+ */
+ public void removeMonthEvent(CalendarEvent target,
+ boolean repaintImmediately) {
+ if (target != null && target.getSlotIndex() >= 0) {
+ // Remove event
+ for (int row = 0; row < monthGrid.getRowCount(); row++) {
+ for (int cell = 0; cell < monthGrid.getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) monthGrid.getWidget(
+ row, cell);
+ if (sdc == null) {
+ return;
+ }
+ sdc.removeEvent(target, repaintImmediately);
+ }
+ }
+ }
+ }
+
+ /**
+ * Updates an event in the month grid
+ *
+ * @param changedEvent
+ * The event that has changed
+ */
+ public void updateEventToMonthGrid(CalendarEvent changedEvent) {
+ removeMonthEvent(changedEvent, true);
+ changedEvent.setSlotIndex(-1);
+ addEventToMonthGrid(changedEvent, true);
+ }
+
+ /**
+ * Sort the event by how long they are
+ *
+ * @param events
+ * The events to sort
+ * @return An array where the events has been sorted
+ */
+ public CalendarEvent[] sortEventsByDuration(Collection<CalendarEvent> events) {
+ CalendarEvent[] sorted = events
+ .toArray(new CalendarEvent[events.size()]);
+ Arrays.sort(sorted, getEventComparator());
+ return sorted;
+ }
+
+ /*
+ * Check if the given event occurs at the given date.
+ */
+ private boolean isEventInDay(Date eventWhen, Date eventTo, Date gridDate) {
+ if (eventWhen.compareTo(gridDate) <= 0
+ && eventTo.compareTo(gridDate) >= 0) {
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Re-render the week grid
+ *
+ * @param daysCount
+ * The amount of days to include in the week
+ * @param days
+ * The days
+ * @param today
+ * Todays date
+ * @param realDayNames
+ * The names of the dates
+ */
+ @SuppressWarnings("deprecation")
+ public void updateWeekGrid(int daysCount, List<CalendarDay> days,
+ Date today, String[] realDayNames) {
+ weekGrid.setFirstHour(getFirstHourOfTheDay());
+ weekGrid.setLastHour(getLastHourOfTheDay());
+ weekGrid.getTimeBar().updateTimeBar(is24HFormat());
+
+ dayToolbar.clear();
+ dayToolbar.addBackButton();
+ dayToolbar.setVerticalSized(isHeightUndefined);
+ dayToolbar.setHorizontalSized(isWidthUndefined);
+ weekGrid.clearDates();
+ weekGrid.setDisabled(isDisabledOrReadOnly());
+
+ for (CalendarDay day : days) {
+ String date = day.getDate();
+ String localized_date_format = day.getLocalizedDateFormat();
+ Date d = dateformat_date.parse(date);
+ int dayOfWeek = day.getDayOfWeek();
+ if (dayOfWeek < getFirstDayNumber()
+ || dayOfWeek > getLastDayNumber()) {
+ continue;
+ }
+ boolean isToday = false;
+ int dayOfMonth = d.getDate();
+ if (today.getDate() == dayOfMonth && today.getYear() == d.getYear()
+ && today.getMonth() == d.getMonth()) {
+ isToday = true;
+ }
+ dayToolbar.add(realDayNames[dayOfWeek - 1], date,
+ localized_date_format, isToday ? "today" : null);
+ weeklyLongEvents.addDate(d);
+ weekGrid.addDate(d);
+ if (isToday) {
+ weekGrid.setToday(d, today);
+ }
+ }
+ dayToolbar.addNextButton();
+ }
+
+ /**
+ * Updates the events in the Month view
+ *
+ * @param daysCount
+ * How many days there are
+ * @param daysUidl
+ *
+ * @param today
+ * Todays date
+ */
+ @SuppressWarnings("deprecation")
+ public void updateMonthGrid(int daysCount, List<CalendarDay> days,
+ Date today) {
+ int columns = getLastDayNumber() - getFirstDayNumber() + 1;
+ rows = (int) Math.ceil(daysCount / (double) 7);
+
+ monthGrid = new MonthGrid(this, rows, columns);
+ monthGrid.setEnabled(!isDisabledOrReadOnly());
+ weekToolbar.removeAllRows();
+ int pos = 0;
+ boolean monthNameDrawn = true;
+ boolean firstDayFound = false;
+ boolean lastDayFound = false;
+
+ for (CalendarDay day : days) {
+ String date = day.getDate();
+ Date d = dateformat_date.parse(date);
+ int dayOfWeek = day.getDayOfWeek();
+ int week = day.getWeek();
+
+ int dayOfMonth = d.getDate();
+
+ // reset at start of each month
+ if (dayOfMonth == 1) {
+ monthNameDrawn = false;
+ if (firstDayFound) {
+ lastDayFound = true;
+ }
+ firstDayFound = true;
+ }
+
+ if (dayOfWeek < getFirstDayNumber()
+ || dayOfWeek > getLastDayNumber()) {
+ continue;
+ }
+ int y = (pos / columns);
+ int x = pos - (y * columns);
+ if (x == 0 && daysCount > 7) {
+ // Add week to weekToolbar for navigation
+ weekToolbar.addWeek(week, d.getYear());
+ }
+ final SimpleDayCell cell = new SimpleDayCell(this, y, x);
+ cell.setMonthGrid(monthGrid);
+ cell.setDate(d);
+ cell.addDomHandler(new ContextMenuHandler() {
+ @Override
+ public void onContextMenu(ContextMenuEvent event) {
+ if (mouseEventListener != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ mouseEventListener.contextMenu(event, cell);
+ }
+ }
+ }, ContextMenuEvent.getType());
+
+ if (!firstDayFound) {
+ cell.addStyleDependentName("prev-month");
+ } else if (lastDayFound) {
+ cell.addStyleDependentName("next-month");
+ }
+
+ if (dayOfMonth >= 1 && !monthNameDrawn) {
+ cell.setMonthNameVisible(true);
+ monthNameDrawn = true;
+ }
+
+ if (today.getDate() == dayOfMonth && today.getYear() == d.getYear()
+ && today.getMonth() == d.getMonth()) {
+ cell.setToday(true);
+
+ }
+ monthGrid.setWidget(y, x, cell);
+ pos++;
+ }
+ }
+
+ public void setSizeForChildren(int newWidth, int newHeight) {
+ intWidth = newWidth;
+ intHeight = newHeight;
+ isWidthUndefined = intWidth == -1;
+ dayToolbar.setVerticalSized(isHeightUndefined);
+ dayToolbar.setHorizontalSized(isWidthUndefined);
+ recalculateWidths();
+ recalculateHeights();
+ }
+
+ /**
+ * Recalculates the heights of the sub-components in the calendar
+ */
+ protected void recalculateHeights() {
+ if (monthGrid != null) {
+
+ if (intHeight == -1) {
+ monthGrid.addStyleDependentName("sizedheight");
+ } else {
+ monthGrid.removeStyleDependentName("sizedheight");
+ }
+
+ monthGrid.updateCellSizes(intWidth - weekToolbar.getOffsetWidth(),
+ intHeight - nameToolbar.getOffsetHeight());
+ weekToolbar.setHeightPX((intHeight == -1) ? intHeight : intHeight
+ - nameToolbar.getOffsetHeight());
+
+ } else if (weekGrid != null) {
+ weekGrid.setHeightPX((intHeight == -1) ? intHeight : intHeight
+ - weeklyLongEvents.getOffsetHeight()
+ - dayToolbar.getOffsetHeight());
+ }
+ }
+
+ /**
+ * Recalculates the widths of the sub-components in the calendar
+ */
+ protected void recalculateWidths() {
+ if (!isWidthUndefined) {
+ nameToolbar.setWidthPX(intWidth);
+ dayToolbar.setWidthPX(intWidth);
+
+ if (monthGrid != null) {
+ monthGrid.updateCellSizes(
+ intWidth - weekToolbar.getOffsetWidth(), intHeight
+ - nameToolbar.getOffsetHeight());
+ } else if (weekGrid != null) {
+ weekGrid.setWidthPX(intWidth);
+ weeklyLongEvents.setWidthPX(weekGrid.getInternalWidth());
+ }
+ } else {
+ dayToolbar.setWidthPX(intWidth);
+ nameToolbar.setWidthPX(intWidth);
+
+ if (monthGrid != null) {
+ if (intWidth == -1) {
+ monthGrid.addStyleDependentName("sizedwidth");
+
+ } else {
+ monthGrid.removeStyleDependentName("sizedwidth");
+ }
+ } else if (weekGrid != null) {
+ weekGrid.setWidthPX(intWidth);
+ weeklyLongEvents.setWidthPX(weekGrid.getInternalWidth());
+ }
+ }
+ }
+
+ /**
+ * Get the date format used to format dates only (excludes time)
+ *
+ * @return
+ */
+ public DateTimeFormat getDateFormat() {
+ return dateformat_date;
+ }
+
+ /**
+ * Get the time format used to format time only (excludes date)
+ *
+ * @return
+ */
+ public DateTimeFormat getTimeFormat() {
+ if (is24HFormat()) {
+ return time24format_date;
+ }
+ return time12format_date;
+ }
+
+ /**
+ * Get the date and time format to format the dates (includes both date and
+ * time)
+ *
+ * @return
+ */
+ public DateTimeFormat getDateTimeFormat() {
+ return dateformat_datetime;
+ }
+
+ /**
+ * Is the calendar either disabled or readonly
+ *
+ * @return
+ */
+ public boolean isDisabledOrReadOnly() {
+ return disabled || readOnly;
+ }
+
+ /**
+ * Is the component disabled
+ */
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ /**
+ * Is the component disabled
+ *
+ * @param disabled
+ * True if disabled
+ */
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ /**
+ * Is the component read-only
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Is the component read-only
+ *
+ * @param readOnly
+ * True if component is readonly
+ */
+ public void setReadOnly(boolean readOnly) {
+ this.readOnly = readOnly;
+ }
+
+ /**
+ * Get the month grid component
+ *
+ * @return
+ */
+ public MonthGrid getMonthGrid() {
+ return monthGrid;
+ }
+
+ /**
+ * Get he week grid component
+ *
+ * @return
+ */
+ public WeekGrid getWeekGrid() {
+ return weekGrid;
+ }
+
+ /**
+ * Calculates correct size for all cells (size / amount of cells ) and
+ * distributes any overflow over all the cells.
+ *
+ * @param totalSize
+ * the total amount of size reserved for all cells
+ * @param numberOfCells
+ * the number of cells
+ * @param sizeModifier
+ * a modifier which is applied to all cells before distributing
+ * the overflow
+ * @return an integer array that contains the correct size for each cell
+ */
+ public static int[] distributeSize(int totalSize, int numberOfCells,
+ int sizeModifier) {
+ int[] cellSizes = new int[numberOfCells];
+ int startingSize = totalSize / numberOfCells;
+ int cellSizeOverFlow = totalSize % numberOfCells;
+
+ for (int i = 0; i < numberOfCells; i++) {
+ cellSizes[i] = startingSize + sizeModifier;
+ }
+
+ // distribute size overflow amongst all slots
+ int j = 0;
+ while (cellSizeOverFlow > 0) {
+ cellSizes[j]++;
+ cellSizeOverFlow--;
+ j++;
+ if (j >= numberOfCells) {
+ j = 0;
+ }
+ }
+
+ // cellSizes[numberOfCells - 1] += cellSizeOverFlow;
+
+ return cellSizes;
+ }
+
+ /**
+ * Returns a comparator which can compare calendar events.
+ *
+ * @return
+ */
+ public static Comparator<CalendarEvent> getEventComparator() {
+ return new Comparator<CalendarEvent>() {
+
+ @Override
+ public int compare(CalendarEvent o1, CalendarEvent o2) {
+ if (o1.isAllDay() != o2.isAllDay()) {
+ if (o2.isAllDay()) {
+ return 1;
+ }
+ return -1;
+ }
+
+ Long d1 = o1.getRangeInMilliseconds();
+ Long d2 = o2.getRangeInMilliseconds();
+ int r = 0;
+ if (!d1.equals(0L) && !d2.equals(0L)) {
+ r = d2.compareTo(d1);
+ return (r == 0) ? ((Integer) o2.getIndex()).compareTo(o1
+ .getIndex()) : r;
+ }
+
+ if (d2.equals(0L) && d1.equals(0L)) {
+ return ((Integer) o2.getIndex()).compareTo(o1.getIndex());
+ } else if (d2.equals(0L) && d1 >= DateConstants.DAYINMILLIS) {
+ return -1;
+ } else if (d2.equals(0L) && d1 < DateConstants.DAYINMILLIS) {
+ return 1;
+ } else if (d1.equals(0L) && d2 >= DateConstants.DAYINMILLIS) {
+ return 1;
+ } else if (d1.equals(0L) && d2 < DateConstants.DAYINMILLIS) {
+ return -1;
+ }
+ r = d2.compareTo(d1);
+ return (r == 0) ? ((Integer) o2.getIndex()).compareTo(o1
+ .getIndex()) : r;
+ }
+ };
+ }
+
+ /**
+ * Is the date at midnight
+ *
+ * @param date
+ * The date to check
+ *
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean isMidnight(Date date) {
+ return (date.getHours() == 0 && date.getMinutes() == 0 && date
+ .getSeconds() == 0);
+ }
+
+ /**
+ * Are the dates equal (uses second resolution)
+ *
+ * @param date1
+ * The first the to compare
+ * @param date2
+ * The second date to compare
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean areDatesEqualToSecond(Date date1, Date date2) {
+ return date1.getYear() == date2.getYear()
+ && date1.getMonth() == date2.getMonth()
+ && date1.getDay() == date2.getDay()
+ && date1.getHours() == date2.getHours()
+ && date1.getSeconds() == date2.getSeconds();
+ }
+
+ /**
+ * Is the calendar event zero seconds long and is occurring at midnight
+ *
+ * @param event
+ * The event to check
+ * @return
+ */
+ public static boolean isZeroLengthMidnightEvent(CalendarEvent event) {
+ return areDatesEqualToSecond(event.getStartTime(), event.getEndTime())
+ && isMidnight(event.getEndTime());
+ }
+
+ /**
+ * Should the 24h time format be used
+ *
+ * @param format
+ * True if the 24h format should be used else the 12h format is
+ * used
+ */
+ public void set24HFormat(boolean format) {
+ this.format = format;
+ }
+
+ /**
+ * Is the 24h time format used
+ */
+ public boolean is24HFormat() {
+ return format;
+ }
+
+ /**
+ * Set the names of the week days
+ *
+ * @param names
+ * The names of the days (Monday, Thursday,...)
+ */
+ public void setDayNames(String[] names) {
+ assert (names.length == 7);
+ dayNames = names;
+ }
+
+ /**
+ * Get the names of the week days
+ */
+ public String[] getDayNames() {
+ return dayNames;
+ }
+
+ /**
+ * Set the names of the months
+ *
+ * @param names
+ * The names of the months (January, February,...)
+ */
+ public void setMonthNames(String[] names) {
+ assert (names.length == 12);
+ monthNames = names;
+ }
+
+ /**
+ * Get the month names
+ */
+ public String[] getMonthNames() {
+ return monthNames;
+ }
+
+ /**
+ * Set the number when a week starts
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setFirstDayNumber(int dayNumber) {
+ assert (dayNumber >= 1 && dayNumber <= 7);
+ firstDay = dayNumber;
+ }
+
+ /**
+ * Get the number when a week starts
+ */
+ public int getFirstDayNumber() {
+ return firstDay;
+ }
+
+ /**
+ * Set the number when a week ends
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setLastDayNumber(int dayNumber) {
+ assert (dayNumber >= 1 && dayNumber <= 7);
+ lastDay = dayNumber;
+ }
+
+ /**
+ * Get the number when a week ends
+ */
+ public int getLastDayNumber() {
+ return lastDay;
+ }
+
+ /**
+ * Set the number when a week starts
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setFirstHourOfTheDay(int hour) {
+ assert (hour >= 0 && hour <= 23);
+ firstHour = hour;
+ }
+
+ /**
+ * Get the number when a week starts
+ */
+ public int getFirstHourOfTheDay() {
+ return firstHour;
+ }
+
+ /**
+ * Set the number when a week ends
+ *
+ * @param dayNumber
+ * The number of the day
+ */
+ public void setLastHourOfTheDay(int hour) {
+ assert (hour >= 0 && hour <= 23);
+ lastHour = hour;
+ }
+
+ /**
+ * Get the number when a week ends
+ */
+ public int getLastHourOfTheDay() {
+ return lastHour;
+ }
+
+ /**
+ * Re-renders the whole week view
+ *
+ * @param scroll
+ * The amount of pixels to scroll the week view
+ * @param today
+ * Todays date
+ * @param daysInMonth
+ * How many days are there in the month
+ * @param firstDayOfWeek
+ * The first day of the week
+ * @param events
+ * The events to render
+ */
+ public void updateWeekView(int scroll, Date today, int daysInMonth,
+ int firstDayOfWeek, Collection<CalendarEvent> events,
+ List<CalendarDay> days) {
+
+ while (outer.getWidgetCount() > 0) {
+ outer.remove(0);
+ }
+
+ monthGrid = null;
+ String[] realDayNames = new String[getDayNames().length];
+ int j = 0;
+
+ if (firstDayOfWeek == 2) {
+ for (int i = 1; i < getDayNames().length; i++) {
+ realDayNames[j++] = getDayNames()[i];
+ }
+ realDayNames[j] = getDayNames()[0];
+ } else {
+ for (int i = 0; i < getDayNames().length; i++) {
+ realDayNames[j++] = getDayNames()[i];
+ }
+
+ }
+
+ weeklyLongEvents = new WeeklyLongEvents(this);
+ if (weekGrid == null) {
+ weekGrid = new WeekGrid(this, is24HFormat());
+ }
+ updateWeekGrid(daysInMonth, days, today, realDayNames);
+ updateEventsToWeekGrid(sortEventsByDuration(events));
+ outer.add(dayToolbar, DockPanel.NORTH);
+ outer.add(weeklyLongEvents, DockPanel.NORTH);
+ outer.add(weekGrid, DockPanel.SOUTH);
+ weekGrid.setVerticalScrollPosition(scroll);
+ }
+
+ /**
+ * Re-renders the whole month view
+ *
+ * @param firstDayOfWeek
+ * The first day of the week
+ * @param today
+ * Todays date
+ * @param daysInMonth
+ * Amount of days in the month
+ * @param events
+ * The events to render
+ * @param days
+ * The day information
+ */
+ public void updateMonthView(int firstDayOfWeek, Date today,
+ int daysInMonth, Collection<CalendarEvent> events,
+ List<CalendarDay> days) {
+
+ // Remove all week numbers from bar
+ while (outer.getWidgetCount() > 0) {
+ outer.remove(0);
+ }
+
+ int firstDay = getFirstDayNumber();
+ int lastDay = getLastDayNumber();
+ int daysPerWeek = lastDay - firstDay + 1;
+ int j = 0;
+
+ String[] dayNames = getDayNames();
+ String[] realDayNames = new String[daysPerWeek];
+
+ if (firstDayOfWeek == 2) {
+ for (int i = firstDay; i < lastDay + 1; i++) {
+ if (i == 7) {
+ realDayNames[j++] = dayNames[0];
+ } else {
+ realDayNames[j++] = dayNames[i];
+ }
+ }
+ } else {
+ for (int i = firstDay - 1; i < lastDay; i++) {
+ realDayNames[j++] = dayNames[i];
+ }
+ }
+
+ nameToolbar.setDayNames(realDayNames);
+
+ weeklyLongEvents = null;
+ weekGrid = null;
+
+ updateMonthGrid(daysInMonth, days, today);
+
+ outer.add(nameToolbar, DockPanel.NORTH);
+ outer.add(weekToolbar, DockPanel.WEST);
+ weekToolbar.updateCellHeights();
+ outer.add(monthGrid, DockPanel.CENTER);
+
+ updateEventsToMonthGrid(events, false);
+ }
+
+ private DateClickListener dateClickListener;
+
+ /**
+ * Sets the listener for listening to event clicks
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(DateClickListener listener) {
+ dateClickListener = listener;
+ }
+
+ /**
+ * Gets the listener for listening to event clicks
+ *
+ * @return
+ */
+ public DateClickListener getDateClickListener() {
+ return dateClickListener;
+ }
+
+ private ForwardListener forwardListener;
+
+ /**
+ * Set the listener which listens to forward events from the calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(ForwardListener listener) {
+ forwardListener = listener;
+ }
+
+ /**
+ * Get the listener which listens to forward events from the calendar
+ *
+ * @return
+ */
+ public ForwardListener getForwardListener() {
+ return forwardListener;
+ }
+
+ private BackwardListener backwardListener;
+
+ /**
+ * Set the listener which listens to backward events from the calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(BackwardListener listener) {
+ backwardListener = listener;
+ }
+
+ /**
+ * Set the listener which listens to backward events from the calendar
+ *
+ * @return
+ */
+ public BackwardListener getBackwardListener() {
+ return backwardListener;
+ }
+
+ private WeekClickListener weekClickListener;
+
+ /**
+ * Set the listener that listens to user clicking on the week numbers
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(WeekClickListener listener) {
+ weekClickListener = listener;
+ }
+
+ /**
+ * Get the listener that listens to user clicking on the week numbers
+ *
+ * @return
+ */
+ public WeekClickListener getWeekClickListener() {
+ return weekClickListener;
+ }
+
+ private RangeSelectListener rangeSelectListener;
+
+ /**
+ * Set the listener that listens to the user highlighting a region in the
+ * calendar
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(RangeSelectListener listener) {
+ rangeSelectListener = listener;
+ }
+
+ /**
+ * Get the listener that listens to the user highlighting a region in the
+ * calendar
+ *
+ * @return
+ */
+ public RangeSelectListener getRangeSelectListener() {
+ return rangeSelectListener;
+ }
+
+ private EventClickListener eventClickListener;
+
+ /**
+ * Get the listener that listens to the user clicking on the events
+ */
+ public EventClickListener getEventClickListener() {
+ return eventClickListener;
+ }
+
+ /**
+ * Set the listener that listens to the user clicking on the events
+ *
+ * @param listener
+ * The listener to use
+ */
+ public void setListener(EventClickListener listener) {
+ eventClickListener = listener;
+ }
+
+ private EventMovedListener eventMovedListener;
+
+ /**
+ * Get the listener that listens to when event is dragged to a new location
+ *
+ * @return
+ */
+ public EventMovedListener getEventMovedListener() {
+ return eventMovedListener;
+ }
+
+ /**
+ * Set the listener that listens to when event is dragged to a new location
+ *
+ * @param eventMovedListener
+ * The listener to use
+ */
+ public void setListener(EventMovedListener eventMovedListener) {
+ this.eventMovedListener = eventMovedListener;
+ }
+
+ private ScrollListener scrollListener;
+
+ /**
+ * Get the listener that listens to when the calendar widget is scrolled
+ *
+ * @return
+ */
+ public ScrollListener getScrollListener() {
+ return scrollListener;
+ }
+
+ /**
+ * Set the listener that listens to when the calendar widget is scrolled
+ *
+ * @param scrollListener
+ * The listener to use
+ */
+ public void setListener(ScrollListener scrollListener) {
+ this.scrollListener = scrollListener;
+ }
+
+ private EventResizeListener eventResizeListener;
+
+ /**
+ * Get the listener that listens to when an events time limits are being
+ * adjusted
+ *
+ * @return
+ */
+ public EventResizeListener getEventResizeListener() {
+ return eventResizeListener;
+ }
+
+ /**
+ * Set the listener that listens to when an events time limits are being
+ * adjusted
+ *
+ * @param eventResizeListener
+ * The listener to use
+ */
+ public void setListener(EventResizeListener eventResizeListener) {
+ this.eventResizeListener = eventResizeListener;
+ }
+
+ private MouseEventListener mouseEventListener;
+ private boolean forwardNavigationEnabled = true;
+ private boolean backwardNavigationEnabled = true;
+
+ /**
+ * Get the listener that listen to mouse events
+ *
+ * @return
+ */
+ public MouseEventListener getMouseEventListener() {
+ return mouseEventListener;
+ }
+
+ /**
+ * Set the listener that listen to mouse events
+ *
+ * @param mouseEventListener
+ * The listener to use
+ */
+ public void setListener(MouseEventListener mouseEventListener) {
+ this.mouseEventListener = mouseEventListener;
+ }
+
+ /**
+ * Is selecting a range allowed?
+ */
+ public boolean isRangeSelectAllowed() {
+ return rangeSelectAllowed;
+ }
+
+ /**
+ * Set selecting a range allowed
+ *
+ * @param rangeSelectAllowed
+ * Should selecting a range be allowed
+ */
+ public void setRangeSelectAllowed(boolean rangeSelectAllowed) {
+ this.rangeSelectAllowed = rangeSelectAllowed;
+ }
+
+ /**
+ * Is moving a range allowed
+ *
+ * @return
+ */
+ public boolean isRangeMoveAllowed() {
+ return rangeMoveAllowed;
+ }
+
+ /**
+ * Is moving a range allowed
+ *
+ * @param rangeMoveAllowed
+ * Is it allowed
+ */
+ public void setRangeMoveAllowed(boolean rangeMoveAllowed) {
+ this.rangeMoveAllowed = rangeMoveAllowed;
+ }
+
+ /**
+ * Is resizing an event allowed
+ */
+ public boolean isEventResizeAllowed() {
+ return eventResizeAllowed;
+ }
+
+ /**
+ * Is resizing an event allowed
+ *
+ * @param eventResizeAllowed
+ * True if allowed false if not
+ */
+ public void setEventResizeAllowed(boolean eventResizeAllowed) {
+ this.eventResizeAllowed = eventResizeAllowed;
+ }
+
+ /**
+ * Is moving an event allowed
+ */
+ public boolean isEventMoveAllowed() {
+ return eventMoveAllowed;
+ }
+
+ /**
+ * Is moving an event allowed
+ *
+ * @param eventMoveAllowed
+ * True if moving is allowed, false if not
+ */
+ public void setEventMoveAllowed(boolean eventMoveAllowed) {
+ this.eventMoveAllowed = eventMoveAllowed;
+ }
+
+ public boolean isBackwardNavigationEnabled() {
+ return backwardNavigationEnabled;
+ }
+
+ public void setBackwardNavigationEnabled(boolean enabled) {
+ backwardNavigationEnabled = enabled;
+ }
+
+ public boolean isForwardNavigationEnabled() {
+ return forwardNavigationEnabled;
+ }
+
+ public void setForwardNavigationEnabled(boolean enabled) {
+ forwardNavigationEnabled = enabled;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/VCalendarPanel.java b/client/src/com/vaadin/client/ui/VCalendarPanel.java
index e234cc911c..311932b819 100644
--- a/client/src/com/vaadin/client/ui/VCalendarPanel.java
+++ b/client/src/com/vaadin/client/ui/VCalendarPanel.java
@@ -19,6 +19,8 @@ package com.vaadin.client.ui;
import java.util.Date;
import java.util.Iterator;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.dom.client.Node;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
@@ -40,6 +42,7 @@ import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
@@ -118,6 +121,8 @@ public class VCalendarPanel extends FocusableFlexTable implements
private static final String CN_OFFMONTH = "offmonth";
+ private static final String CN_OUTSIDE_RANGE = "outside-range";
+
/**
* Represents a click handler for when a user selects a value by using the
* mouse
@@ -133,6 +138,9 @@ public class VCalendarPanel extends FocusableFlexTable implements
@Override
public void onClick(ClickEvent event) {
Date newDate = ((Day) event.getSource()).getDate();
+ if (!isDateInsideRange(newDate, Resolution.DAY)) {
+ return;
+ }
if (newDate.getMonth() != displayedMonth.getMonth()
|| newDate.getYear() != displayedMonth.getYear()) {
// If an off-month date was clicked, we must change the
@@ -175,9 +183,9 @@ public class VCalendarPanel extends FocusableFlexTable implements
private boolean showISOWeekNumbers;
- private Date displayedMonth;
+ private FocusedDate displayedMonth;
- private Date focusedDate;
+ private FocusedDate focusedDate;
private Day selectedDay;
@@ -198,8 +206,9 @@ public class VCalendarPanel extends FocusableFlexTable implements
private boolean initialRenderDone = false;
public VCalendarPanel() {
-
+ getElement().setId(DOM.createUniqueId());
setStyleName(VDateField.CLASSNAME + "-calendarpanel");
+ Roles.getGridRole().set(getElement());
/*
* Firefox auto-repeat works correctly only if we use a key press
@@ -267,6 +276,8 @@ public class VCalendarPanel extends FocusableFlexTable implements
private void selectDate(Date date) {
if (selectedDay != null) {
selectedDay.removeStyleDependentName(CN_SELECTED);
+ Roles.getGridcellRole().removeAriaSelectedState(
+ selectedDay.getElement());
}
int rowCount = days.getRowCount();
@@ -279,6 +290,8 @@ public class VCalendarPanel extends FocusableFlexTable implements
if (curday.getDate().equals(date)) {
curday.addStyleDependentName(CN_SELECTED);
selectedDay = curday;
+ Roles.getGridcellRole().setAriaSelectedState(
+ selectedDay.getElement(), SelectedValue.TRUE);
return;
}
}
@@ -290,7 +303,7 @@ public class VCalendarPanel extends FocusableFlexTable implements
* Updates year, month, day from focusedDate to value
*/
private void selectFocused() {
- if (focusedDate != null) {
+ if (focusedDate != null && isDateInsideRange(focusedDate, resolution)) {
if (value == null) {
// No previously selected value (set to null on server side).
// Create a new date using current date and time
@@ -397,10 +410,13 @@ public class VCalendarPanel extends FocusableFlexTable implements
prevMonth = new VEventButton();
prevMonth.setHTML("&lsaquo;");
prevMonth.setStyleName("v-button-prevmonth");
+
prevMonth.setTabIndex(-1);
+
nextMonth = new VEventButton();
nextMonth.setHTML("&rsaquo;");
nextMonth.setStyleName("v-button-nextmonth");
+
nextMonth.setTabIndex(-1);
setWidget(0, 3, nextMonth);
@@ -414,18 +430,23 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
if (prevYear == null) {
+
prevYear = new VEventButton();
prevYear.setHTML("&laquo;");
prevYear.setStyleName("v-button-prevyear");
+
prevYear.setTabIndex(-1);
nextYear = new VEventButton();
nextYear.setHTML("&raquo;");
nextYear.setStyleName("v-button-nextyear");
+
nextYear.setTabIndex(-1);
setWidget(0, 0, prevYear);
setWidget(0, 4, nextYear);
}
+ updateControlButtonRangeStyles(needsMonth);
+
final String monthName = needsMonth ? getDateTimeService().getMonth(
displayedMonth.getMonth()) : "";
final int year = displayedMonth.getYear() + 1900;
@@ -446,6 +467,48 @@ public class VCalendarPanel extends FocusableFlexTable implements
+ "</span>");
}
+ private void updateControlButtonRangeStyles(boolean needsMonth) {
+
+ if (focusedDate == null) {
+ return;
+ }
+
+ if (needsMonth) {
+ Date prevMonthDate = (Date) focusedDate.clone();
+ removeOneMonth(prevMonthDate);
+
+ if (!isDateInsideRange(prevMonthDate, Resolution.MONTH)) {
+ prevMonth.addStyleName(CN_OUTSIDE_RANGE);
+ } else {
+ prevMonth.removeStyleName(CN_OUTSIDE_RANGE);
+ }
+ Date nextMonthDate = (Date) focusedDate.clone();
+ addOneMonth(nextMonthDate);
+ if (!isDateInsideRange(nextMonthDate, Resolution.MONTH)) {
+ nextMonth.addStyleName(CN_OUTSIDE_RANGE);
+ } else {
+ nextMonth.removeStyleName(CN_OUTSIDE_RANGE);
+ }
+ }
+
+ Date prevYearDate = (Date) focusedDate.clone();
+ prevYearDate.setYear(prevYearDate.getYear() - 1);
+ if (!isDateInsideRange(prevYearDate, Resolution.YEAR)) {
+ prevYear.addStyleName(CN_OUTSIDE_RANGE);
+ } else {
+ prevYear.removeStyleName(CN_OUTSIDE_RANGE);
+ }
+
+ Date nextYearDate = (Date) focusedDate.clone();
+ nextYearDate.setYear(nextYearDate.getYear() + 1);
+ if (!isDateInsideRange(nextYearDate, Resolution.YEAR)) {
+ nextYear.addStyleName(CN_OUTSIDE_RANGE);
+ } else {
+ nextYear.removeStyleName(CN_OUTSIDE_RANGE);
+ }
+
+ }
+
private DateTimeService getDateTimeService() {
return dateTimeService;
}
@@ -470,6 +533,107 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
/**
+ * Checks inclusively whether a date is inside a range of dates or not.
+ *
+ * @param date
+ * @return
+ */
+ private boolean isDateInsideRange(Date date, Resolution minResolution) {
+ assert (date != null);
+
+ return isAcceptedByRangeEnd(date, minResolution)
+ && isAcceptedByRangeStart(date, minResolution);
+ }
+
+ /**
+ * Accepts dates greater than or equal to rangeStart, depending on the
+ * resolution. If the resolution is set to DAY, the range will compare on a
+ * day-basis. If the resolution is set to YEAR, only years are compared. So
+ * even if the range is set to one millisecond in next year, also next year
+ * will be included.
+ *
+ * @param date
+ * @param minResolution
+ * @return
+ */
+ private boolean isAcceptedByRangeStart(Date date, Resolution minResolution) {
+ assert (date != null);
+
+ // rangeStart == null means that we accept all values below rangeEnd
+ if (rangeStart == null) {
+ return true;
+ }
+
+ Date valueDuplicate = (Date) date.clone();
+ Date rangeStartDuplicate = (Date) rangeStart.clone();
+
+ if (minResolution == Resolution.YEAR) {
+ return valueDuplicate.getYear() >= rangeStartDuplicate.getYear();
+ }
+ if (minResolution == Resolution.MONTH) {
+ valueDuplicate = clearDateBelowMonth(valueDuplicate);
+ rangeStartDuplicate = clearDateBelowMonth(rangeStartDuplicate);
+ } else {
+ valueDuplicate = clearDateBelowDay(valueDuplicate);
+ rangeStartDuplicate = clearDateBelowDay(rangeStartDuplicate);
+ }
+
+ return !rangeStartDuplicate.after(valueDuplicate);
+ }
+
+ /**
+ * Accepts dates earlier than or equal to rangeStart, depending on the
+ * resolution. If the resolution is set to DAY, the range will compare on a
+ * day-basis. If the resolution is set to YEAR, only years are compared. So
+ * even if the range is set to one millisecond in next year, also next year
+ * will be included.
+ *
+ * @param date
+ * @param minResolution
+ * @return
+ */
+ private boolean isAcceptedByRangeEnd(Date date, Resolution minResolution) {
+ assert (date != null);
+
+ // rangeEnd == null means that we accept all values above rangeStart
+ if (rangeEnd == null) {
+ return true;
+ }
+
+ Date valueDuplicate = (Date) date.clone();
+ Date rangeEndDuplicate = (Date) rangeEnd.clone();
+
+ if (minResolution == Resolution.YEAR) {
+ return valueDuplicate.getYear() <= rangeEndDuplicate.getYear();
+ }
+ if (minResolution == Resolution.MONTH) {
+ valueDuplicate = clearDateBelowMonth(valueDuplicate);
+ rangeEndDuplicate = clearDateBelowMonth(rangeEndDuplicate);
+ } else {
+ valueDuplicate = clearDateBelowDay(valueDuplicate);
+ rangeEndDuplicate = clearDateBelowDay(rangeEndDuplicate);
+ }
+
+ return !rangeEndDuplicate.before(valueDuplicate);
+
+ }
+
+ private static Date clearDateBelowMonth(Date date) {
+ date.setDate(1);
+ return clearDateBelowDay(date);
+ }
+
+ private static Date clearDateBelowDay(Date date) {
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ // Clearing milliseconds
+ long time = date.getTime() / 1000;
+ date = new Date(time * 1000);
+ return date;
+ }
+
+ /**
* Builds the day and time selectors of the calendar.
*/
private void buildCalendarBody() {
@@ -528,6 +692,10 @@ public class VCalendarPanel extends FocusableFlexTable implements
} else {
days.setHTML(headerRow, firstWeekdayColumn + i, "");
}
+
+ Roles.getColumnheaderRole().set(
+ days.getCellFormatter().getElement(headerRow,
+ firstWeekdayColumn + i));
}
// Zero out hours, minutes, seconds, and milliseconds to compare dates
@@ -551,12 +719,20 @@ public class VCalendarPanel extends FocusableFlexTable implements
for (int dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
// Actually write the day of month
- Day day = new Day((Date) curr.clone());
+ Date dayDate = (Date) curr.clone();
+ Day day = new Day(dayDate);
+
day.setStyleName(parent.getStylePrimaryName()
+ "-calendarpanel-day");
+ if (!isDateInsideRange(dayDate, Resolution.DAY)) {
+ day.addStyleDependentName(CN_OUTSIDE_RANGE);
+ }
+
if (curr.equals(selectedDate)) {
day.addStyleDependentName(CN_SELECTED);
+ Roles.getGridcellRole().setAriaSelectedState(
+ day.getElement(), SelectedValue.TRUE);
selectedDay = day;
}
if (curr.equals(today)) {
@@ -574,10 +750,14 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
days.setWidget(weekOfMonth, firstWeekdayColumn + dayOfWeek, day);
+ Roles.getGridcellRole().set(
+ days.getCellFormatter().getElement(weekOfMonth,
+ firstWeekdayColumn + dayOfWeek));
// ISO week numbers if requested
days.getCellFormatter().setVisible(weekOfMonth, weekColumn,
isShowISOWeekNumbers());
+
if (isShowISOWeekNumbers()) {
final String baseCssClass = parent.getStylePrimaryName()
+ "-calendarpanel-weeknumber";
@@ -615,8 +795,9 @@ public class VCalendarPanel extends FocusableFlexTable implements
if (focusedDate == null) {
Date now = new Date();
// focusedDate must have zero hours, mins, secs, millisecs
- focusedDate = new Date(now.getYear(), now.getMonth(), now.getDate());
- displayedMonth = new Date(now.getYear(), now.getMonth(), 1);
+ focusedDate = new FocusedDate(now.getYear(), now.getMonth(),
+ now.getDate());
+ displayedMonth = new FocusedDate(now.getYear(), now.getMonth(), 1);
}
if (getResolution().getCalendarField() <= Resolution.MONTH
@@ -653,6 +834,17 @@ public class VCalendarPanel extends FocusableFlexTable implements
* Moves the focus forward the given number of days.
*/
private void focusNextDay(int days) {
+ if (focusedDate == null) {
+ return;
+ }
+
+ Date focusCopy = ((Date) focusedDate.clone());
+ focusCopy.setDate(focusedDate.getDate() + days);
+ if (!isDateInsideRange(focusCopy, resolution)) {
+ // If not inside allowed range, then do not move anything
+ return;
+ }
+
int oldMonth = focusedDate.getMonth();
int oldYear = focusedDate.getYear();
focusedDate.setDate(focusedDate.getDate() + days);
@@ -662,6 +854,7 @@ public class VCalendarPanel extends FocusableFlexTable implements
// Month did not change, only move the selection
focusDay(focusedDate);
} else {
+
// If the month changed we need to re-render the calendar
displayedMonth.setMonth(focusedDate.getMonth());
displayedMonth.setYear(focusedDate.getYear());
@@ -681,38 +874,83 @@ public class VCalendarPanel extends FocusableFlexTable implements
*/
private void focusNextMonth() {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setMonth(currentMonth + 1);
+ if (focusedDate == null) {
+ return;
+ }
+ // Trying to request next month
+ Date requestedNextMonthDate = (Date) focusedDate.clone();
+ addOneMonth(requestedNextMonthDate);
+
+ if (!isDateInsideRange(requestedNextMonthDate, Resolution.MONTH)) {
+ return;
+ }
+
+ // Now also checking whether the day is inside the range or not. If not
+ // inside,
+ // correct it
+ if (!isDateInsideRange(requestedNextMonthDate, Resolution.DAY)) {
+ requestedNextMonthDate = adjustDateToFitInsideRange(requestedNextMonthDate);
+ }
+ focusedDate.setYear(requestedNextMonthDate.getYear());
+ focusedDate.setMonth(requestedNextMonthDate.getMonth());
+ focusedDate.setDate(requestedNextMonthDate.getDate());
+ displayedMonth.setMonth(displayedMonth.getMonth() + 1);
+
+ renderCalendar();
+ }
+
+ private static void addOneMonth(Date date) {
+ int currentMonth = date.getMonth();
int requestedMonth = (currentMonth + 1) % 12;
+ date.setMonth(date.getMonth() + 1);
+
/*
* If the selected value was e.g. 31.3 the new value would be 31.4 but
* this value is invalid so the new value will be 1.5. This is taken
* care of by decreasing the value until we have the correct month.
*/
- while (focusedDate.getMonth() != requestedMonth) {
- focusedDate.setDate(focusedDate.getDate() - 1);
+ while (date.getMonth() != requestedMonth) {
+ date.setDate(date.getDate() - 1);
}
- displayedMonth.setMonth(displayedMonth.getMonth() + 1);
-
- renderCalendar();
}
- /**
- * Selects the previous month
- */
- private void focusPreviousMonth() {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setMonth(currentMonth - 1);
+ private static void removeOneMonth(Date date) {
+ int currentMonth = date.getMonth();
+
+ date.setMonth(date.getMonth() - 1);
/*
* If the selected value was e.g. 31.12 the new value would be 31.11 but
* this value is invalid so the new value will be 1.12. This is taken
* care of by decreasing the value until we have the correct month.
*/
- while (focusedDate.getMonth() == currentMonth) {
- focusedDate.setDate(focusedDate.getDate() - 1);
+ while (date.getMonth() == currentMonth) {
+ date.setDate(date.getDate() - 1);
+ }
+ }
+
+ /**
+ * Selects the previous month
+ */
+ private void focusPreviousMonth() {
+
+ if (focusedDate == null) {
+ return;
}
+ Date requestedPreviousMonthDate = (Date) focusedDate.clone();
+ removeOneMonth(requestedPreviousMonthDate);
+
+ if (!isDateInsideRange(requestedPreviousMonthDate, Resolution.MONTH)) {
+ return;
+ }
+
+ if (!isDateInsideRange(requestedPreviousMonthDate, Resolution.DAY)) {
+ requestedPreviousMonthDate = adjustDateToFitInsideRange(requestedPreviousMonthDate);
+ }
+ focusedDate.setYear(requestedPreviousMonthDate.getYear());
+ focusedDate.setMonth(requestedPreviousMonthDate.getMonth());
+ focusedDate.setDate(requestedPreviousMonthDate.getDate());
displayedMonth.setMonth(displayedMonth.getMonth() - 1);
renderCalendar();
@@ -722,16 +960,41 @@ public class VCalendarPanel extends FocusableFlexTable implements
* Selects the previous year
*/
private void focusPreviousYear(int years) {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setYear(focusedDate.getYear() - years);
- displayedMonth.setYear(displayedMonth.getYear() - years);
- /*
- * If the focused date was a leap day (Feb 29), the new date becomes Mar
- * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
- */
- if (focusedDate.getMonth() != currentMonth) {
- focusedDate.setDate(0);
+
+ if (focusedDate == null) {
+ return;
+ }
+ Date previousYearDate = (Date) focusedDate.clone();
+ previousYearDate.setYear(previousYearDate.getYear() - years);
+ // Do not focus if not inside range
+ if (!isDateInsideRange(previousYearDate, Resolution.YEAR)) {
+ return;
+ }
+ // If we remove one year, but have to roll back a bit, fit it
+ // into the calendar. Also the months have to be changed
+ if (!isDateInsideRange(previousYearDate, Resolution.DAY)) {
+ previousYearDate = adjustDateToFitInsideRange(previousYearDate);
+
+ focusedDate.setYear(previousYearDate.getYear());
+ focusedDate.setMonth(previousYearDate.getMonth());
+ focusedDate.setDate(previousYearDate.getDate());
+ displayedMonth.setYear(previousYearDate.getYear());
+ displayedMonth.setMonth(previousYearDate.getMonth());
+ } else {
+
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setYear(focusedDate.getYear() - years);
+ displayedMonth.setYear(displayedMonth.getYear() - years);
+ /*
+ * If the focused date was a leap day (Feb 29), the new date becomes
+ * Mar 1 if the new year is not also a leap year. Set it to Feb 28
+ * instead.
+ */
+ if (focusedDate.getMonth() != currentMonth) {
+ focusedDate.setDate(0);
+ }
}
+
renderCalendar();
}
@@ -739,16 +1002,41 @@ public class VCalendarPanel extends FocusableFlexTable implements
* Selects the next year
*/
private void focusNextYear(int years) {
- int currentMonth = focusedDate.getMonth();
- focusedDate.setYear(focusedDate.getYear() + years);
- displayedMonth.setYear(displayedMonth.getYear() + years);
- /*
- * If the focused date was a leap day (Feb 29), the new date becomes Mar
- * 1 if the new year is not also a leap year. Set it to Feb 28 instead.
- */
- if (focusedDate.getMonth() != currentMonth) {
- focusedDate.setDate(0);
+
+ if (focusedDate == null) {
+ return;
}
+ Date nextYearDate = (Date) focusedDate.clone();
+ nextYearDate.setYear(nextYearDate.getYear() + years);
+ // Do not focus if not inside range
+ if (!isDateInsideRange(nextYearDate, Resolution.YEAR)) {
+ return;
+ }
+ // If we add one year, but have to roll back a bit, fit it
+ // into the calendar. Also the months have to be changed
+ if (!isDateInsideRange(nextYearDate, Resolution.DAY)) {
+ nextYearDate = adjustDateToFitInsideRange(nextYearDate);
+
+ focusedDate.setYear(nextYearDate.getYear());
+ focusedDate.setMonth(nextYearDate.getMonth());
+ focusedDate.setDate(nextYearDate.getDate());
+ displayedMonth.setYear(nextYearDate.getYear());
+ displayedMonth.setMonth(nextYearDate.getMonth());
+ } else {
+
+ int currentMonth = focusedDate.getMonth();
+ focusedDate.setYear(focusedDate.getYear() + years);
+ displayedMonth.setYear(displayedMonth.getYear() + years);
+ /*
+ * If the focused date was a leap day (Feb 29), the new date becomes
+ * Mar 1 if the new year is not also a leap year. Set it to Feb 28
+ * instead.
+ */
+ if (focusedDate.getMonth() != currentMonth) {
+ focusedDate.setDate(0);
+ }
+ }
+
renderCalendar();
}
@@ -1062,9 +1350,10 @@ public class VCalendarPanel extends FocusableFlexTable implements
*/
} else if (keycode == getResetKey() && !shift) {
// Restore showing value the selected value
- focusedDate = new Date(value.getYear(), value.getMonth(),
+ focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
+ 1);
renderCalendar();
return true;
}
@@ -1246,6 +1535,20 @@ public class VCalendarPanel extends FocusableFlexTable implements
}
/**
+ * Adjusts a date to fit inside the range, only if outside
+ *
+ * @param date
+ */
+ private Date adjustDateToFitInsideRange(Date date) {
+ if (rangeStart != null && rangeStart.after(date)) {
+ date = (Date) rangeStart.clone();
+ } else if (rangeEnd != null && rangeEnd.before(date)) {
+ date = (Date) rangeEnd.clone();
+ }
+ return date;
+ }
+
+ /**
* Sets the data of the Panel.
*
* @param currentDate
@@ -1257,16 +1560,47 @@ public class VCalendarPanel extends FocusableFlexTable implements
if (currentDate == value && currentDate != null) {
return;
}
+ boolean currentDateWasAdjusted = false;
+ // Check that selected date is inside the allowed range
+ if (currentDate != null && !isDateInsideRange(currentDate, resolution)) {
+ currentDate = adjustDateToFitInsideRange(currentDate);
+ currentDateWasAdjusted = true;
+ }
Date oldDisplayedMonth = displayedMonth;
value = currentDate;
- if (value == null) {
- focusedDate = displayedMonth = null;
+ // If current date was adjusted, we will not select any date,
+ // since that will look like a date is selected. Instead we
+ // only focus on the adjusted value
+ if (value == null || currentDateWasAdjusted) {
+ // If ranges enabled, we may need to focus on a different view to
+ // potentially not get stuck
+ if (rangeStart != null || rangeEnd != null) {
+ Date dateThatFitsInsideRange = adjustDateToFitInsideRange(new Date());
+ focusedDate = new FocusedDate(
+ dateThatFitsInsideRange.getYear(),
+ dateThatFitsInsideRange.getMonth(),
+ dateThatFitsInsideRange.getDate());
+ displayedMonth = new FocusedDate(
+ dateThatFitsInsideRange.getYear(),
+ dateThatFitsInsideRange.getMonth(), 1);
+ // value was adjusted. Set selected to null to not cause
+ // confusion, but this is only needed (and allowed) when we have
+ // a day
+ // resolution
+ if (getResolution().getCalendarField() >= Resolution.DAY
+ .getCalendarField()) {
+ value = null;
+ }
+ } else {
+ focusedDate = displayedMonth = null;
+ }
} else {
- focusedDate = new Date(value.getYear(), value.getMonth(),
+ focusedDate = new FocusedDate(value.getYear(), value.getMonth(),
value.getDate());
- displayedMonth = new Date(value.getYear(), value.getMonth(), 1);
+ displayedMonth = new FocusedDate(value.getYear(), value.getMonth(),
+ 1);
}
// Re-render calendar if the displayed month is changed,
@@ -1704,6 +2038,10 @@ public class VCalendarPanel extends FocusableFlexTable implements
private static final String SUBPART_DAY = "day";
private static final String SUBPART_MONTH_YEAR_HEADER = "header";
+ private Date rangeStart;
+
+ private Date rangeEnd;
+
@Override
public String getSubPartName(Element subElement) {
if (contains(nextMonth, subElement)) {
@@ -1821,4 +2159,75 @@ public class VCalendarPanel extends FocusableFlexTable implements
mouseTimer.cancel();
}
}
+
+ /**
+ * Helper class to inform the screen reader that the user changed the
+ * selected date. It sets the value of a field that is outside the view, and
+ * is defined as a live area. That way the screen reader recognizes the
+ * change and reads it to the user.
+ */
+ public class FocusedDate extends Date {
+
+ public FocusedDate(int year, int month, int date) {
+ super(year, month, date);
+ }
+
+ @Override
+ public void setTime(long time) {
+ super.setTime(time);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setDate(int date) {
+ super.setDate(date);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setMonth(int month) {
+ super.setMonth(month);
+ setLabel();
+ }
+
+ @Override
+ @Deprecated
+ public void setYear(int year) {
+ super.setYear(year);
+ setLabel();
+ }
+
+ private void setLabel() {
+ if (parent instanceof VPopupCalendar) {
+ ((VPopupCalendar) parent).setFocusedDate(this);
+ }
+ }
+ }
+
+ /**
+ * Sets the start range for this component. The start range is inclusive,
+ * and it depends on the current resolution, what is considered inside the
+ * range.
+ *
+ * @param startDate
+ * - the allowed range's start date
+ */
+ public void setRangeStart(Date rangeStart) {
+ this.rangeStart = rangeStart;
+
+ }
+
+ /**
+ * Sets the end range for this component. The end range is inclusive, and it
+ * depends on the current resolution, what is considered inside the range.
+ *
+ * @param endDate
+ * - the allowed range's end date
+ */
+ public void setRangeEnd(Date rangeEnd) {
+ this.rangeEnd = rangeEnd;
+
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VCheckBox.java b/client/src/com/vaadin/client/ui/VCheckBox.java
index ca1e3ebcdb..bb49dd7f0a 100644
--- a/client/src/com/vaadin/client/ui/VCheckBox.java
+++ b/client/src/com/vaadin/client/ui/VCheckBox.java
@@ -22,9 +22,12 @@ import com.google.gwt.user.client.Event;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.Util;
import com.vaadin.client.VTooltip;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.client.ui.aria.HandlesAriaInvalid;
+import com.vaadin.client.ui.aria.HandlesAriaRequired;
public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
- Field {
+ Field, HandlesAriaInvalid, HandlesAriaRequired {
public static final String CLASSNAME = "v-checkbox";
@@ -69,4 +72,23 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements
}
}
+ /**
+ * Gives access to the input element.
+ *
+ * @return Element of the CheckBox itself
+ */
+ private Element getCheckBoxElement() {
+ // FIXME: Would love to use a better way to access the checkbox element
+ return (Element) getElement().getFirstChildElement();
+ }
+
+ @Override
+ public void setAriaRequired(boolean required) {
+ AriaHelper.handleInputRequired(getCheckBoxElement(), required);
+ }
+
+ @Override
+ public void setAriaInvalid(boolean invalid) {
+ AriaHelper.handleInputInvalid(getCheckBoxElement(), invalid);
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VColorPickerArea.java b/client/src/com/vaadin/client/ui/VColorPickerArea.java
index bdae65438f..81f2c8fcc7 100644
--- a/client/src/com/vaadin/client/ui/VColorPickerArea.java
+++ b/client/src/com/vaadin/client/ui/VColorPickerArea.java
@@ -67,6 +67,7 @@ public class VColorPickerArea extends Widget implements ClickHandler, HasHTML,
* @param handler
* @return HandlerRegistration used to remove the handler
*/
+ @Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
diff --git a/client/src/com/vaadin/client/ui/VContextMenu.java b/client/src/com/vaadin/client/ui/VContextMenu.java
index 80751652df..02ee5b07c5 100644
--- a/client/src/com/vaadin/client/ui/VContextMenu.java
+++ b/client/src/com/vaadin/client/ui/VContextMenu.java
@@ -29,19 +29,26 @@ 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.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.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.dom.client.LoadEvent;
import com.google.gwt.event.dom.client.LoadHandler;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Command;
+import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.vaadin.client.Focusable;
import com.vaadin.client.Util;
@@ -56,6 +63,8 @@ public class VContextMenu extends VOverlay implements SubPartAware {
private int top;
+ private Element focusedElement;
+
private VLazyExecutor delayedImageLoadExecutioner = new VLazyExecutor(100,
new ScheduledCommand() {
@Override
@@ -75,6 +84,21 @@ public class VContextMenu extends VOverlay implements SubPartAware {
super(true, false, true);
setWidget(menu);
setStyleName("v-contextmenu");
+ getElement().setId(DOM.createUniqueId());
+
+ addCloseHandler(new CloseHandler<PopupPanel>() {
+ @Override
+ public void onClose(CloseEvent<PopupPanel> event) {
+ Element currentFocus = Util.getFocusedElement();
+ if (focusedElement != null
+ && (currentFocus == null
+ || menu.getElement().isOrHasChild(currentFocus) || RootPanel
+ .getBodyElement().equals(currentFocus))) {
+ focusedElement.focus();
+ focusedElement = null;
+ }
+ }
+ });
}
protected void imagesLoaded() {
@@ -115,6 +139,10 @@ public class VContextMenu extends VOverlay implements SubPartAware {
// Attach onload listeners to all images
Util.sinkOnloadForImages(menu.getElement());
+ // Store the currently focused element, which will be re-focused when
+ // context menu is closed
+ focusedElement = Util.getFocusedElement();
+
setPopupPositionAndShow(new PositionCallback() {
@Override
public void setPosition(int offsetWidth, int offsetHeight) {
@@ -168,10 +196,11 @@ public class VContextMenu extends VOverlay implements SubPartAware {
*/
class CMenuBar extends MenuBar implements HasFocusHandlers,
HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers,
- Focusable, LoadHandler {
+ Focusable, LoadHandler, KeyUpHandler {
public CMenuBar() {
super(true);
addDomHandler(this, LoadEvent.getType());
+ addDomHandler(this, KeyUpEvent.getType());
}
@Override
@@ -240,6 +269,13 @@ public class VContextMenu extends VOverlay implements SubPartAware {
delayedImageLoadExecutioner.trigger();
}
+ @Override
+ public void onKeyUp(KeyUpEvent event) {
+ // Allow to close context menu with ESC
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+ hide();
+ }
+ }
}
@Override
diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java
index a3156221b9..e08fbf8ab6 100644
--- a/client/src/com/vaadin/client/ui/VFilterSelect.java
+++ b/client/src/com/vaadin/client/ui/VFilterSelect.java
@@ -24,6 +24,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Set;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style;
@@ -66,6 +67,10 @@ import com.vaadin.client.Focusable;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.client.ui.aria.HandlesAriaCaption;
+import com.vaadin.client.ui.aria.HandlesAriaInvalid;
+import com.vaadin.client.ui.aria.HandlesAriaRequired;
import com.vaadin.client.ui.menubar.MenuBar;
import com.vaadin.client.ui.menubar.MenuItem;
import com.vaadin.shared.AbstractComponentState;
@@ -81,7 +86,8 @@ import com.vaadin.shared.ui.combobox.FilteringMode;
@SuppressWarnings("deprecation")
public class VFilterSelect extends Composite implements Field, KeyDownHandler,
KeyUpHandler, ClickHandler, FocusHandler, BlurHandler, Focusable,
- SubPartAware {
+ SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
+ HandlesAriaRequired {
/**
* Represents a suggestion in the suggestion popup box
@@ -241,6 +247,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
DOM.sinkEvents(root, Event.ONMOUSEDOWN | Event.ONMOUSEWHEEL);
addCloseHandler(this);
+
+ Roles.getListRole().set(getElement());
}
/**
@@ -736,6 +744,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
while (it.hasNext()) {
final FilterSelectSuggestion s = it.next();
final MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
+ Roles.getListitemRole().set(mi.getElement());
Util.sinkOnloadForImages(mi.getElement());
@@ -1106,9 +1115,15 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
});
popupOpener.sinkEvents(Event.ONMOUSEDOWN);
+ Roles.getButtonRole()
+ .setAriaHiddenState(popupOpener.getElement(), true);
+ Roles.getButtonRole().set(popupOpener.getElement());
+
panel.add(tb);
panel.add(popupOpener);
initWidget(panel);
+ Roles.getComboboxRole().set(panel.getElement());
+
tb.addKeyDownHandler(this);
tb.addKeyUpHandler(this);
@@ -1226,8 +1241,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
// Always update styles as they might have been overwritten
if (textInputEnabled) {
removeStyleDependentName(STYLE_NO_INPUT);
+ Roles.getTextboxRole().removeAriaReadonlyProperty(tb.getElement());
} else {
addStyleDependentName(STYLE_NO_INPUT);
+ Roles.getTextboxRole().setAriaReadonlyProperty(tb.getElement(),
+ true);
}
if (this.textInputEnabled == textInputEnabled) {
@@ -1937,4 +1955,19 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler,
}
return null;
}
+
+ @Override
+ public void setAriaRequired(boolean required) {
+ AriaHelper.handleInputRequired(tb, required);
+ }
+
+ @Override
+ public void setAriaInvalid(boolean invalid) {
+ AriaHelper.handleInputInvalid(tb, invalid);
+ }
+
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ AriaHelper.bindCaption(tb, captionElement);
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java
index 495e842bfd..6c2661d1d8 100644
--- a/client/src/com/vaadin/client/ui/VFormLayout.java
+++ b/client/src/com/vaadin/client/ui/VFormLayout.java
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
@@ -34,6 +35,7 @@ import com.vaadin.client.ComponentConnector;
import com.vaadin.client.Focusable;
import com.vaadin.client.StyleConstants;
import com.vaadin.client.VTooltip;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ComponentConstants;
import com.vaadin.shared.ui.ComponentStateUtil;
@@ -93,6 +95,8 @@ public class VFormLayout extends SimplePanel {
public VFormLayoutTable() {
DOM.setElementProperty(getElement(), "cellPadding", "0");
DOM.setElementProperty(getElement(), "cellSpacing", "0");
+
+ Roles.getPresentationRole().set(getElement());
}
/*
@@ -276,6 +280,9 @@ public class VFormLayout extends SimplePanel {
if (state.caption != null) {
if (captionText == null) {
captionText = DOM.createSpan();
+
+ AriaHelper.bindCaption(owner.getWidget(), captionText);
+
DOM.insertChild(getElement(), captionText, icon == null ? 0
: 1);
}
@@ -298,6 +305,9 @@ public class VFormLayout extends SimplePanel {
boolean required = owner instanceof AbstractFieldConnector
&& ((AbstractFieldConnector) owner).isRequired();
+
+ AriaHelper.handleInputRequired(owner.getWidget(), required);
+
if (required) {
if (requiredFieldIndicator == null) {
requiredFieldIndicator = DOM.createSpan();
@@ -305,6 +315,11 @@ public class VFormLayout extends SimplePanel {
DOM.setElementProperty(requiredFieldIndicator, "className",
"v-required-field-indicator");
DOM.appendChild(getElement(), requiredFieldIndicator);
+
+ // Hide the required indicator from screen reader, as this
+ // information is set directly at the input field
+ Roles.getTextboxRole().setAriaHiddenState(
+ requiredFieldIndicator, true);
}
} else {
if (requiredFieldIndicator != null) {
@@ -364,6 +379,8 @@ public class VFormLayout extends SimplePanel {
showError = false;
}
+ AriaHelper.handleInputInvalid(owner.getWidget(), showError);
+
if (showError) {
if (errorIndicatorElement == null) {
errorIndicatorElement = DOM.createDiv();
@@ -371,6 +388,11 @@ public class VFormLayout extends SimplePanel {
DOM.setElementProperty(errorIndicatorElement, "className",
"v-errorindicator");
DOM.appendChild(getElement(), errorIndicatorElement);
+
+ // Hide the error indicator from screen reader, as this
+ // information is set directly at the input field
+ Roles.getFormRole().setAriaHiddenState(
+ errorIndicatorElement, true);
}
} else if (errorIndicatorElement != null) {
diff --git a/client/src/com/vaadin/client/ui/VLabel.java b/client/src/com/vaadin/client/ui/VLabel.java
index 83fc8e207e..8acd653778 100644
--- a/client/src/com/vaadin/client/ui/VLabel.java
+++ b/client/src/com/vaadin/client/ui/VLabel.java
@@ -16,7 +16,6 @@
package com.vaadin.client.ui;
-import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HTML;
import com.vaadin.client.ApplicationConnection;
@@ -57,10 +56,8 @@ public class VLabel extends HTML {
super.setWidth(width);
if (width == null || width.equals("")) {
setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, true);
- getElement().getStyle().setDisplay(Display.INLINE_BLOCK);
} else {
setStyleName(getElement(), CLASSNAME_UNDEFINED_WIDTH, false);
- getElement().getStyle().clearDisplay();
}
}
diff --git a/client/src/com/vaadin/client/ui/VNativeButton.java b/client/src/com/vaadin/client/ui/VNativeButton.java
index 6e1c5bae77..71413a76e6 100644
--- a/client/src/com/vaadin/client/ui/VNativeButton.java
+++ b/client/src/com/vaadin/client/ui/VNativeButton.java
@@ -16,14 +16,9 @@
package com.vaadin.client.ui;
-import com.google.gwt.core.client.Scheduler;
-import com.google.gwt.core.client.Scheduler.ScheduledCommand;
-import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.MouseEvent;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Button;
@@ -31,7 +26,6 @@ import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.Util;
-import com.vaadin.client.VConsole;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.button.ButtonServerRpc;
diff --git a/client/src/com/vaadin/client/ui/VOptionGroup.java b/client/src/com/vaadin/client/ui/VOptionGroup.java
index 2ba8a9e729..eed5549e39 100644
--- a/client/src/com/vaadin/client/ui/VOptionGroup.java
+++ b/client/src/com/vaadin/client/ui/VOptionGroup.java
@@ -22,6 +22,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
@@ -99,6 +100,13 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler,
public void buildOptions(UIDL uidl) {
panel.clear();
optionsEnabled.clear();
+
+ if (isMultiselect()) {
+ Roles.getGroupRole().set(getElement());
+ } else {
+ Roles.getRadiogroupRole().set(getElement());
+ }
+
for (final Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
final UIDL opUidl = (UIDL) it.next();
CheckBox op;
diff --git a/client/src/com/vaadin/client/ui/VOptionGroupBase.java b/client/src/com/vaadin/client/ui/VOptionGroupBase.java
index 4d60b2eba8..cc691130ad 100644
--- a/client/src/com/vaadin/client/ui/VOptionGroupBase.java
+++ b/client/src/com/vaadin/client/ui/VOptionGroupBase.java
@@ -118,6 +118,7 @@ public abstract class VOptionGroupBase extends Composite implements Field,
return multiselect;
}
+ @Override
public boolean isEnabled() {
return enabled;
}
@@ -190,6 +191,7 @@ public abstract class VOptionGroupBase extends Composite implements Field,
}
}
+ @Override
public void setEnabled(boolean enabled) {
if (this.enabled != enabled) {
this.enabled = enabled;
diff --git a/client/src/com/vaadin/client/ui/VPopupCalendar.java b/client/src/com/vaadin/client/ui/VPopupCalendar.java
index 2a2578aa16..e431da127d 100644
--- a/client/src/com/vaadin/client/ui/VPopupCalendar.java
+++ b/client/src/com/vaadin/client/ui/VPopupCalendar.java
@@ -18,6 +18,9 @@ package com.vaadin.client.ui;
import java.util.Date;
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.LiveValue;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
@@ -25,18 +28,25 @@ import com.google.gwt.event.dom.client.DomEvent;
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.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
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;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.VConsole;
import com.vaadin.client.ui.VCalendarPanel.FocusOutListener;
import com.vaadin.client.ui.VCalendarPanel.SubmitListener;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.shared.ui.datefield.PopupDateFieldState;
import com.vaadin.shared.ui.datefield.Resolution;
/**
@@ -68,6 +78,12 @@ public class VPopupCalendar extends VTextualDate implements Field,
private boolean textFieldEnabled = true;
+ private String captionId;
+
+ private Label selectedDate;
+
+ private Element descriptionForAssisitveDevicesElement;
+
public VPopupCalendar() {
super();
@@ -75,8 +91,23 @@ public class VPopupCalendar extends VTextualDate implements Field,
calendarToggle.addClickHandler(this);
// -2 instead of -1 to avoid FocusWidget.onAttach to reset it
calendarToggle.getElement().setTabIndex(-2);
+
+ Roles.getButtonRole().set(calendarToggle.getElement());
+ Roles.getButtonRole().setAriaHiddenState(calendarToggle.getElement(),
+ true);
+
add(calendarToggle);
+ // Description of the usage of the widget for assisitve device users
+ descriptionForAssisitveDevicesElement = DOM.createDiv();
+ descriptionForAssisitveDevicesElement
+ .setInnerText(PopupDateFieldState.DESCRIPTION_FOR_ASSISTIVE_DEVICES);
+ AriaHelper.ensureHasId(descriptionForAssisitveDevicesElement);
+ Roles.getTextboxRole().setAriaDescribedbyProperty(text.getElement(),
+ Id.of(descriptionForAssisitveDevicesElement));
+ AriaHelper.setVisibleForAssistiveDevicesOnly(
+ descriptionForAssisitveDevicesElement, true);
+
calendar = GWT.create(VCalendarPanel.class);
calendar.setParentField(this);
calendar.setFocusOutListener(new FocusOutListener() {
@@ -88,6 +119,14 @@ public class VPopupCalendar extends VTextualDate implements Field,
}
});
+ // FIXME: Problem is, that the element with the provided id does not
+ // exist yet in html. This is the same problem as with the context menu.
+ // Apply here the same fix (#11795)
+ Roles.getTextboxRole().setAriaControlsProperty(text.getElement(),
+ Id.of(calendar.getElement()));
+ Roles.getButtonRole().setAriaControlsProperty(
+ calendarToggle.getElement(), Id.of(calendar.getElement()));
+
calendar.setSubmitListener(new SubmitListener() {
@Override
public void onSubmit() {
@@ -109,7 +148,20 @@ public class VPopupCalendar extends VTextualDate implements Field,
popup = new VOverlay(true, true, true);
popup.setOwner(this);
- popup.setWidget(calendar);
+ FlowPanel wrapper = new FlowPanel();
+ selectedDate = new Label();
+ selectedDate.setStyleName(getStylePrimaryName() + "-selecteddate");
+ AriaHelper.setVisibleForAssistiveDevicesOnly(selectedDate.getElement(),
+ true);
+
+ Roles.getTextboxRole().setAriaLiveProperty(selectedDate.getElement(),
+ LiveValue.ASSERTIVE);
+ Roles.getTextboxRole().setAriaAtomicProperty(selectedDate.getElement(),
+ true);
+ wrapper.add(selectedDate);
+ wrapper.add(calendar);
+
+ popup.setWidget(wrapper);
popup.addCloseHandler(this);
DOM.setElementProperty(calendar.getElement(), "id",
@@ -120,6 +172,19 @@ public class VPopupCalendar extends VTextualDate implements Field,
updateStyleNames();
}
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ DOM.appendChild(RootPanel.get().getElement(),
+ descriptionForAssisitveDevicesElement);
+ }
+
+ @Override
+ protected void onDetach() {
+ super.onDetach();
+ descriptionForAssisitveDevicesElement.removeFromParent();
+ }
+
@SuppressWarnings("deprecation")
public void updateValue(Date newDate) {
Date currentDate = getCurrentDate();
@@ -181,8 +246,54 @@ public class VPopupCalendar extends VTextualDate implements Field,
text.setEnabled(textFieldEnabled);
if (textFieldEnabled) {
calendarToggle.setTabIndex(-1);
+ Roles.getButtonRole().setAriaHiddenState(
+ calendarToggle.getElement(), true);
} else {
calendarToggle.setTabIndex(0);
+ Roles.getButtonRole().setAriaHiddenState(
+ calendarToggle.getElement(), false);
+ }
+
+ handleAriaAttributes();
+ }
+
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ if (captionElement == null) {
+ captionId = null;
+ } else {
+ captionId = captionElement.getId();
+ }
+
+ if (isTextFieldEnabled()) {
+ super.bindAriaCaption(captionElement);
+ } else {
+ AriaHelper.bindCaption(calendarToggle, captionElement);
+ }
+
+ handleAriaAttributes();
+ }
+
+ private void handleAriaAttributes() {
+ Widget removeFromWidget;
+ Widget setForWidget;
+
+ if (isTextFieldEnabled()) {
+ setForWidget = text;
+ removeFromWidget = calendarToggle;
+ } else {
+ setForWidget = calendarToggle;
+ removeFromWidget = text;
+ }
+
+ Roles.getFormRole().removeAriaLabelledbyProperty(
+ removeFromWidget.getElement());
+ if (captionId == null) {
+ Roles.getFormRole().removeAriaLabelledbyProperty(
+ setForWidget.getElement());
+ } else {
+ Roles.getFormRole().setAriaLabelledbyProperty(
+ setForWidget.getElement(), Id.of(captionId));
}
}
@@ -270,10 +381,6 @@ public class VPopupCalendar extends VTextualDate implements Field,
}
}
- // fix size
- popup.setWidth(w + "px");
- popup.setHeight(h + "px");
-
popup.setPopupPosition(l,
t + calendarToggle.getOffsetHeight() + 2);
@@ -350,6 +457,32 @@ public class VPopupCalendar extends VTextualDate implements Field,
calendar.setFocus(focus);
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ if (enabled) {
+ Roles.getButtonRole().setAriaDisabledState(
+ calendarToggle.getElement(), true);
+ } else {
+ Roles.getButtonRole().setAriaDisabledState(
+ calendarToggle.getElement(), false);
+ }
+ }
+
+ /**
+ * Sets the content of a special field for assistive devices, so that they
+ * can recognize the change and inform the user (reading out in case of
+ * screen reader)
+ *
+ * @param selectedDate
+ * Date that is currently selected
+ */
+ public void setFocusedDate(Date selectedDate) {
+ this.selectedDate.setText(DateTimeFormat.getFormat("dd, MMMM, yyyy")
+ .format(selectedDate));
+ }
+
/**
* For internal use only. May be removed or replaced in the future.
*
@@ -439,4 +572,50 @@ public class VPopupCalendar extends VTextualDate implements Field,
return super.getSubPartName(subElement);
}
+ /**
+ * Set a description that explains the usage of the Widget for users of
+ * assistive devices.
+ *
+ * @param descriptionForAssistiveDevices
+ * String with the description
+ */
+ public void setDescriptionForAssistiveDevices(
+ String descriptionForAssistiveDevices) {
+ descriptionForAssisitveDevicesElement
+ .setInnerText(descriptionForAssistiveDevices);
+ }
+
+ /**
+ * Get the description that explains the usage of the Widget for users of
+ * assistive devices.
+ *
+ * @return String with the description
+ */
+ public String getDescriptionForAssistiveDevices() {
+ return descriptionForAssisitveDevicesElement.getInnerText();
+ }
+
+ /**
+ * Sets the start range for this component. The start range is inclusive,
+ * and it depends on the current resolution, what is considered inside the
+ * range.
+ *
+ * @param startDate
+ * - the allowed range's start date
+ */
+ public void setRangeStart(Date rangeStart) {
+ calendar.setRangeStart(rangeStart);
+ }
+
+ /**
+ * Sets the end range for this component. The end range is inclusive, and it
+ * depends on the current resolution, what is considered inside the range.
+ *
+ * @param endDate
+ * - the allowed range's end date
+ */
+ public void setRangeEnd(Date rangeEnd) {
+ calendar.setRangeEnd(rangeEnd);
+ }
+
}
diff --git a/client/src/com/vaadin/client/ui/VPopupView.java b/client/src/com/vaadin/client/ui/VPopupView.java
index d983da2b62..05fbd2c073 100644
--- a/client/src/com/vaadin/client/ui/VPopupView.java
+++ b/client/src/com/vaadin/client/ui/VPopupView.java
@@ -202,7 +202,6 @@ public class VPopupView extends HTML implements Iterable<Widget> {
private boolean hasHadMouseOver = false;
private boolean hideOnMouseOut = true;
private final Set<Element> activeChildren = new HashSet<Element>();
- private boolean hiding = false;
private ShortcutActionHandler shortcutActionHandler;
@@ -264,7 +263,6 @@ public class VPopupView extends HTML implements Iterable<Widget> {
@Override
public void hide(boolean autoClosed) {
VConsole.log("Hiding popupview");
- hiding = true;
syncChildren();
if (popupComponentWidget != null && popupComponentWidget != loading) {
remove(popupComponentWidget);
@@ -276,8 +274,6 @@ public class VPopupView extends HTML implements Iterable<Widget> {
@Override
public void show() {
- hiding = false;
-
// Find the shortcut action handler that should handle keyboard
// events from the popup. The events do not propagate automatically
// because the popup is directly attached to the RootPanel.
@@ -353,31 +349,6 @@ public class VPopupView extends HTML implements Iterable<Widget> {
this.hideOnMouseOut = hideOnMouseOut;
}
- /*
- *
- * We need a hack make popup act as a child of VPopupView in Vaadin's
- * component tree, but work in default GWT manner when closing or
- * opening.
- *
- * (non-Javadoc)
- *
- * @see com.google.gwt.user.client.ui.Widget#getParent()
- */
- @Override
- public Widget getParent() {
- if (!isAttached() || hiding) {
- return super.getParent();
- } else {
- return VPopupView.this;
- }
- }
-
- @Override
- protected void onDetach() {
- super.onDetach();
- hiding = false;
- }
-
@Override
public Element getContainerElement() {
return super.getContainerElement();
diff --git a/client/src/com/vaadin/client/ui/VTextualDate.java b/client/src/com/vaadin/client/ui/VTextualDate.java
index 2f444a8587..9d7e31faab 100644
--- a/client/src/com/vaadin/client/ui/VTextualDate.java
+++ b/client/src/com/vaadin/client/ui/VTextualDate.java
@@ -18,6 +18,7 @@ package com.vaadin.client.ui;
import java.util.Date;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -30,11 +31,16 @@ import com.vaadin.client.Focusable;
import com.vaadin.client.LocaleNotLoadedException;
import com.vaadin.client.LocaleService;
import com.vaadin.client.VConsole;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.client.ui.aria.HandlesAriaCaption;
+import com.vaadin.client.ui.aria.HandlesAriaInvalid;
+import com.vaadin.client.ui.aria.HandlesAriaRequired;
import com.vaadin.shared.EventId;
import com.vaadin.shared.ui.datefield.Resolution;
public class VTextualDate extends VDateField implements Field, ChangeHandler,
- Focusable, SubPartAware {
+ Focusable, SubPartAware, HandlesAriaCaption, HandlesAriaInvalid,
+ HandlesAriaRequired {
private static final String PARSE_ERROR_CLASSNAME = "-parseerror";
@@ -76,6 +82,9 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
getClient()
.updateVariable(getId(), EventId.FOCUS, "", true);
}
+
+ // Needed for tooltip event handling
+ VTextualDate.this.fireEvent(event);
}
});
text.addBlurHandler(new BlurHandler() {
@@ -94,8 +103,12 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
EventId.BLUR)) {
getClient().updateVariable(getId(), EventId.BLUR, "", true);
}
+
+ // Needed for tooltip event handling
+ VTextualDate.this.fireEvent(event);
}
});
+
add(text);
}
@@ -150,6 +163,21 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
return formatStr;
}
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ AriaHelper.bindCaption(text, captionElement);
+ }
+
+ @Override
+ public void setAriaRequired(boolean required) {
+ AriaHelper.handleInputRequired(text, required);
+ }
+
+ @Override
+ public void setAriaInvalid(boolean invalid) {
+ AriaHelper.handleInputInvalid(text, invalid);
+ }
+
/**
* Updates the text field according to the current date (provided by
* {@link #getDate()}). Takes care of updating text, enabling and disabling
@@ -178,8 +206,12 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
if (readonly) {
text.addStyleName("v-readonly");
+ Roles.getTextboxRole().setAriaReadonlyProperty(text.getElement(),
+ true);
} else {
text.removeStyleName("v-readonly");
+ Roles.getTextboxRole()
+ .removeAriaReadonlyProperty(text.getElement());
}
}
@@ -348,5 +380,4 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler,
return null;
}
-
}
diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java
index 624dce4f13..51c00ca310 100644
--- a/client/src/com/vaadin/client/ui/VTree.java
+++ b/client/src/com/vaadin/client/ui/VTree.java
@@ -24,6 +24,10 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import com.google.gwt.aria.client.ExpandedValue;
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.aria.client.SelectedValue;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -56,6 +60,8 @@ import com.vaadin.client.ConnectorMap;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
+import com.vaadin.client.ui.aria.AriaHelper;
+import com.vaadin.client.ui.aria.HandlesAriaCaption;
import com.vaadin.client.ui.dd.DDUtil;
import com.vaadin.client.ui.dd.VAbstractDropHandler;
import com.vaadin.client.ui.dd.VAcceptCallback;
@@ -75,7 +81,8 @@ import com.vaadin.shared.ui.tree.TreeConstants;
*/
public class VTree extends FocusElementPanel implements VHasDropHandler,
FocusHandler, BlurHandler, KeyPressHandler, KeyDownHandler,
- SubPartAware, ActionOwner {
+ SubPartAware, ActionOwner, HandlesAriaCaption {
+ private String lastNodeKey = "";
public static final String CLASSNAME = "v-tree";
@@ -168,6 +175,8 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
public VTree() {
super();
setStyleName(CLASSNAME);
+
+ Roles.getTreeRole().set(body.getElement());
add(body);
addFocusHandler(this);
@@ -865,12 +874,24 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
}
protected void constructDom() {
+ String labelId = DOM.createUniqueId();
+
addStyleName(CLASSNAME);
+ String treeItemId = DOM.createUniqueId();
+ getElement().setId(treeItemId);
+ Roles.getTreeitemRole().set(getElement());
+ Roles.getTreeitemRole().setAriaSelectedState(getElement(),
+ SelectedValue.FALSE);
+ Roles.getTreeitemRole().setAriaLabelledbyProperty(getElement(),
+ Id.of(labelId));
nodeCaptionDiv = DOM.createDiv();
DOM.setElementProperty(nodeCaptionDiv, "className", CLASSNAME
+ "-caption");
Element wrapper = DOM.createDiv();
+ wrapper.setId(labelId);
+ wrapper.setAttribute("for", treeItemId);
+
nodeCaptionSpan = DOM.createSpan();
DOM.appendChild(getElement(), nodeCaptionDiv);
DOM.appendChild(nodeCaptionDiv, wrapper);
@@ -886,6 +907,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
childNodeContainer = new FlowPanel();
childNodeContainer.setStyleName(CLASSNAME + "-children");
+ Roles.getGroupRole().set(childNodeContainer.getElement());
setWidget(childNodeContainer);
}
@@ -914,10 +936,13 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
new String[] { key }, true);
}
addStyleName(CLASSNAME + "-expanded");
+ Roles.getTreeitemRole().setAriaExpandedState(getElement(),
+ ExpandedValue.TRUE);
childNodeContainer.setVisible(true);
-
} else {
removeStyleName(CLASSNAME + "-expanded");
+ Roles.getTreeitemRole().setAriaExpandedState(getElement(),
+ ExpandedValue.FALSE);
childNodeContainer.setVisible(false);
if (notifyServer) {
client.updateVariable(paintableId, "collapse",
@@ -1094,15 +1119,17 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
Util.scrollIntoViewVertically(nodeCaptionDiv);
}
- public void setIcon(String iconUrl) {
+ public void setIcon(String iconUrl, String altText) {
if (iconUrl != null) {
// Add icon if not present
if (icon == null) {
icon = new Icon(client);
+ Roles.getImgRole().set(icon.getElement());
DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv),
icon.getElement(), nodeCaptionSpan);
}
icon.setUri(iconUrl);
+ icon.getElement().setAttribute("alt", altText);
} else {
// Remove icon if present
if (icon != null) {
@@ -1517,10 +1544,34 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
// Unfocus previously focused node
if (focusedNode != null) {
focusedNode.setFocused(false);
+
+ Roles.getTreeRole().removeAriaActivedescendantProperty(
+ focusedNode.getElement());
}
if (node != null) {
node.setFocused(true);
+ Roles.getTreeitemRole().setAriaSelectedState(node.getElement(),
+ SelectedValue.TRUE);
+
+ /*
+ * FIXME: This code needs to be changed when the keyboard navigation
+ * doesn't immediately trigger a selection change anymore.
+ *
+ * Right now this function is called before and after the Tree is
+ * rebuilt when up/down arrow keys are pressed. This leads to the
+ * problem, that the newly selected item is announced too often with
+ * a screen reader.
+ *
+ * Behaviour is different when using the Tree with and without
+ * screen reader.
+ */
+ if (node.key.equals(lastNodeKey)) {
+ Roles.getTreeRole().setAriaActivedescendantProperty(
+ getFocusElement(), Id.of(node.getElement()));
+ } else {
+ lastNodeKey = node.key;
+ }
}
focusedNode = node;
@@ -2161,4 +2212,8 @@ public class VTree extends FocusElementPanel implements VHasDropHandler,
keyToNode.clear();
}
+ @Override
+ public void bindAriaCaption(Element captionElement) {
+ AriaHelper.bindCaption(body, captionElement);
+ }
}
diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java
index 51a775cb7e..38dfdba1b8 100644
--- a/client/src/com/vaadin/client/ui/VWindow.java
+++ b/client/src/com/vaadin/client/ui/VWindow.java
@@ -43,12 +43,13 @@ import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.ConnectorMap;
-import com.vaadin.client.Console;
import com.vaadin.client.Focusable;
import com.vaadin.client.LayoutManager;
import com.vaadin.client.Util;
+import com.vaadin.client.debug.internal.VDebugWindow;
import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
import com.vaadin.shared.EventId;
+import com.vaadin.shared.ui.window.WindowMode;
/**
* "Sub window" component.
@@ -58,18 +59,6 @@ import com.vaadin.shared.EventId;
public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
- /**
- * Minimum allowed height of a window. This refers to the content area, not
- * the outer borders.
- */
- private static final int MIN_CONTENT_AREA_HEIGHT = 100;
-
- /**
- * Minimum allowed width of a window. This refers to the content area, not
- * the outer borders.
- */
- private static final int MIN_CONTENT_AREA_WIDTH = 150;
-
private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
private static boolean orderingDefered;
@@ -114,6 +103,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
public Element closeBox;
/** For internal use only. May be removed or replaced in the future. */
+ public Element maximizeRestoreBox;
+
+ /** For internal use only. May be removed or replaced in the future. */
public ApplicationConnection client;
/** For internal use only. May be removed or replaced in the future. */
@@ -262,6 +254,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
resizeBox = DOM.createDiv();
DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
closeBox = DOM.createDiv();
+ maximizeRestoreBox = DOM.createDiv();
+ DOM.setElementProperty(maximizeRestoreBox, "className", CLASSNAME
+ + "-maximizebox");
DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
DOM.appendChild(footer, resizeBox);
@@ -269,14 +264,15 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
DOM.appendChild(wrapper, header);
+ DOM.appendChild(wrapper, maximizeRestoreBox);
DOM.appendChild(wrapper, closeBox);
DOM.appendChild(header, headerText);
DOM.appendChild(wrapper, contents);
DOM.appendChild(wrapper, footer);
DOM.appendChild(super.getContainerElement(), wrapper);
- sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
- | Event.ONLOSECAPTURE);
+ sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS
+ | Event.ONCLICK | Event.ONLOSECAPTURE);
setWidget(contentPanel);
@@ -575,6 +571,31 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
}
+ public void updateMaximizeRestoreClassName(boolean visible,
+ WindowMode windowMode) {
+ String className;
+ if (windowMode == WindowMode.MAXIMIZED) {
+ className = CLASSNAME + "-restorebox";
+ } else {
+ className = CLASSNAME + "-maximizebox";
+ }
+ if (!visible) {
+ className = className + " " + className + "-disabled";
+ }
+ maximizeRestoreBox.setClassName(className);
+ }
+
+ // TODO this will eventually be removed, currently used to avoid updating to
+ // server side.
+ public void setPopupPositionNoUpdate(int left, int top) {
+ if (top < 0) {
+ // ensure window is not moved out of browser window from top of the
+ // screen
+ top = 0;
+ }
+ super.setPopupPosition(left, top);
+ }
+
@Override
public void setPopupPosition(int left, int top) {
if (top < 0) {
@@ -616,6 +637,8 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
return contents;
}
+ private Event headerDragPending;
+
@Override
public void onBrowserEvent(final Event event) {
boolean bubble = true;
@@ -632,6 +655,28 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
onCloseClick();
}
bubble = false;
+ } else if (target == maximizeRestoreBox) {
+ // handled in connector
+ if (type != Event.ONCLICK) {
+ bubble = false;
+ }
+ } else if (header.isOrHasChild(target) && !dragging) {
+ // dblclick handled in connector
+ if (type != Event.ONDBLCLICK && draggable) {
+ if (type == Event.ONMOUSEDOWN) {
+ headerDragPending = event;
+ } else if (type == Event.ONMOUSEMOVE
+ && headerDragPending != null) {
+ // ie won't work unless this is set here
+ dragging = true;
+ onDragEvent(headerDragPending);
+ onDragEvent(event);
+ headerDragPending = null;
+ } else {
+ headerDragPending = null;
+ }
+ bubble = false;
+ }
} else if (dragging || !contents.isOrHasChild(target)) {
onDragEvent(event);
bubble = false;
@@ -648,7 +693,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
*/
if (type == Event.ONMOUSEDOWN
&& !contentPanel.getElement().isOrHasChild(target)
- && target != closeBox) {
+ && target != closeBox && target != maximizeRestoreBox) {
contentPanel.focus();
}
@@ -746,16 +791,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
int w = Util.getTouchOrMouseClientX(event) - startX + origW;
- int minWidth = getMinWidth();
- if (w < minWidth) {
- w = minWidth;
- }
-
int h = Util.getTouchOrMouseClientY(event) - startY + origH;
- int minHeight = getMinHeight();
- if (h < minHeight) {
- h = minHeight;
- }
setWidth(w + "px");
setHeight(h + "px");
@@ -775,7 +811,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
}
}
- private void updateContentsSize() {
+ public void updateContentsSize() {
LayoutManager layoutManager = getLayoutManager();
layoutManager.setNeedsMeasure(ConnectorMap.get(client).getConnector(
this));
@@ -896,7 +932,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
// debug window
Widget w = Util.findWidget(target, null);
while (w != null) {
- if (w instanceof Console) {
+ if (w instanceof VDebugWindow) {
return true; // allow debug-window clicks
} else if (ConnectorMap.get(client).isConnector(w)) {
return false;
@@ -959,10 +995,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
contentPanel.focus();
}
- public int getMinHeight() {
- return MIN_CONTENT_AREA_HEIGHT + getDecorationHeight();
- }
-
private int getDecorationHeight() {
LayoutManager lm = getLayoutManager();
int headerHeight = lm.getOuterHeight(header);
@@ -974,10 +1006,6 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
return LayoutManager.get(client);
}
- public int getMinWidth() {
- return MIN_CONTENT_AREA_WIDTH + getDecorationWidth();
- }
-
private int getDecorationWidth() {
LayoutManager layoutManager = getLayoutManager();
return layoutManager.getOuterWidth(getElement())
diff --git a/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java b/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java
index 868c14f742..da79639dcd 100644
--- a/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java
+++ b/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java
@@ -100,8 +100,7 @@ public class AbsoluteLayoutConnector extends
/*
* (non-Javadoc)
*
- * @see
- * com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin
+ * @see com.vaadin.client.HasComponentsConnector#updateCaption(com.vaadin
* .client.ComponentConnector)
*/
@Override
@@ -188,6 +187,8 @@ public class AbsoluteLayoutConnector extends
oldChild.removeStateChangeHandler(childStateChangeHandler);
}
}
+
+ getWidget().cleanupWrappers();
}
/*
diff --git a/client/src/com/vaadin/client/ui/aria/AriaHelper.java b/client/src/com/vaadin/client/ui/aria/AriaHelper.java
new file mode 100644
index 0000000000..0ff58cf510
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/aria/AriaHelper.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui.aria;
+
+import com.google.gwt.aria.client.Id;
+import com.google.gwt.aria.client.InvalidValue;
+import com.google.gwt.aria.client.Roles;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * Helper class that helps to implement the WAI-ARIA functionality.
+ */
+public class AriaHelper {
+ public static final String ASSISTIVE_DEVICE_ONLY_STYLE = "v-assistive-device-only";
+
+ /**
+ * Binds a caption (label in HTML speak) to the form element as required by
+ * WAI-ARIA specification.
+ *
+ * @param widget
+ * Widget, that should be bound to the caption
+ * @param captionElements
+ * Element with of caption to bind
+ */
+ public static void bindCaption(Widget widget, Element captionElement) {
+ assert widget != null : "Valid Widget required";
+
+ if (widget instanceof HandlesAriaCaption) {
+ // Let the widget handle special cases itself
+ if (captionElement == null) {
+ ((HandlesAriaCaption) widget).bindAriaCaption(null);
+ } else {
+ ensureHasId(captionElement);
+ ((HandlesAriaCaption) widget).bindAriaCaption(captionElement);
+ }
+ } else if (captionElement != null) {
+ // Handle the default case
+ ensureHasId(captionElement);
+ String ownerId = ensureHasId(widget.getElement());
+ captionElement.setAttribute("for", ownerId);
+
+ Roles.getTextboxRole().setAriaLabelledbyProperty(
+ widget.getElement(), Id.of(captionElement));
+ } else {
+ clearCaption(widget);
+ }
+ }
+
+ /**
+ * Removes a binding to a caption added with bindCaption() from the provided
+ * Widget.
+ *
+ * @param widget
+ * Widget, that was bound to a caption before
+ */
+ private static void clearCaption(Widget widget) {
+ Roles.getTextboxRole()
+ .removeAriaLabelledbyProperty(widget.getElement());
+ }
+
+ /**
+ * Handles the required actions depending of the input Widget being required
+ * or not.
+ *
+ * @param widget
+ * Widget, typically an input Widget like TextField
+ * @param required
+ * boolean, true when the element is required
+ */
+ public static void handleInputRequired(Widget widget, boolean required) {
+ assert widget != null : "Valid Widget required";
+
+ if (widget instanceof HandlesAriaRequired) {
+ ((HandlesAriaRequired) widget).setAriaRequired(required);
+ } else {
+ handleInputRequired(widget.getElement(), required);
+ }
+ }
+
+ /**
+ * Handles the required actions depending of the input element being
+ * required or not.
+ *
+ * @param element
+ * Element, typically from an input Widget like TextField
+ * @param required
+ * boolean, true when the element is required
+ */
+ public static void handleInputRequired(Element element, boolean required) {
+ if (required) {
+ Roles.getTextboxRole().setAriaRequiredProperty(element, required);
+ } else {
+ Roles.getTextboxRole().removeAriaRequiredProperty(element);
+ }
+ }
+
+ /**
+ * Handles the required actions depending of the input Widget contains
+ * unaccepted input.
+ *
+ * @param widget
+ * Widget, typically an input Widget like TextField
+ * @param invalid
+ * boolean, true when the Widget input has an error
+ */
+ public static void handleInputInvalid(Widget widget, boolean invalid) {
+ assert widget != null : "Valid Widget required";
+
+ if (widget instanceof HandlesAriaInvalid) {
+ ((HandlesAriaInvalid) widget).setAriaInvalid(invalid);
+ } else {
+ handleInputInvalid(widget.getElement(), invalid);
+ }
+ }
+
+ /**
+ * Handles the required actions depending of the input element contains
+ * unaccepted input.
+ *
+ * @param element
+ * Element, typically an input Widget like TextField
+ * @param invalid
+ * boolean, true when the element input has an error
+ */
+ public static void handleInputInvalid(Element element, boolean invalid) {
+ if (invalid) {
+ Roles.getTextboxRole().setAriaInvalidState(element,
+ InvalidValue.TRUE);
+ } else {
+ Roles.getTextboxRole().removeAriaInvalidState(element);
+ }
+ }
+
+ /**
+ * Makes sure that the provided element has an id attribute. Adds a new
+ * unique id if not.
+ *
+ * @param element
+ * Element to check
+ * @return String with the id of the element
+ */
+ public static String ensureHasId(Element element) {
+ assert element != null : "Valid Element required";
+
+ String id = element.getId();
+ if (null == id || id.isEmpty()) {
+ id = DOM.createUniqueId();
+ element.setId(id);
+ }
+ return id;
+ }
+
+ /**
+ * Allows to move an element out of the visible area of the browser window.
+ *
+ * This makes it possible to have additional information for an assistive
+ * device, that is not in the way for visual users.
+ *
+ * @param element
+ * Element to move out of sight
+ * @param boolean assistiveOnly true when element should only be visible for
+ * assistive devices, false to make the element visible for all
+ */
+ public static void setVisibleForAssistiveDevicesOnly(Element element,
+ boolean assistiveOnly) {
+ if (assistiveOnly) {
+ element.addClassName(ASSISTIVE_DEVICE_ONLY_STYLE);
+ } else {
+ element.removeClassName(ASSISTIVE_DEVICE_ONLY_STYLE);
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java b/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java
new file mode 100644
index 0000000000..50f83fdede
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui.aria;
+
+import com.google.gwt.user.client.Element;
+
+/**
+ * Some Widgets need to handle the caption handling for WAI-ARIA themselfs, as
+ * for example the required ids need to be set in a specific way. In such a
+ * case, the Widget needs to implement this interface.
+ */
+public interface HandlesAriaCaption {
+
+ /**
+ * Called to bind the provided caption (label in HTML speak) element to the
+ * main input element of the Widget.
+ *
+ * Binding should be removed from the main input field when captionElement
+ * is null.
+ *
+ * @param captionElement
+ * Element of the caption
+ */
+ void bindAriaCaption(Element captionElement);
+}
diff --git a/client/src/com/vaadin/client/SynchronousXHR.java b/client/src/com/vaadin/client/ui/aria/HandlesAriaInvalid.java
index a19c9bad16..05cb82b0d6 100644
--- a/client/src/com/vaadin/client/SynchronousXHR.java
+++ b/client/src/com/vaadin/client/ui/aria/HandlesAriaInvalid.java
@@ -13,24 +13,21 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.vaadin.client;
-import com.google.gwt.xhr.client.XMLHttpRequest;
-
-public class SynchronousXHR extends XMLHttpRequest {
-
- protected SynchronousXHR() {
- }
-
- public native final void synchronousPost(String uri, String requestData)
- /*-{
- try {
- this.open("POST", uri, false);
- this.setRequestHeader("Content-Type", "text/plain;charset=utf-8");
- this.send(requestData);
- } catch (e) {
- // No errors are managed as this is synchronous forceful send that can just fail
- }
- }-*/;
+package com.vaadin.client.ui.aria;
+/**
+ * Some Widgets need to handle the required handling for WAI-ARIA themselfs, as
+ * this attribute needs to be set to the input element itself. In such a case,
+ * the Widget needs to implement this interface.
+ */
+public interface HandlesAriaInvalid {
+ /**
+ * Called to set the element, typically an input element, as invalid.
+ *
+ * @param invalid
+ * boolean, true when the element should be marked invalid, false
+ * otherwise
+ */
+ void setAriaInvalid(boolean invalid);
}
diff --git a/client/src/com/vaadin/client/ui/aria/HandlesAriaRequired.java b/client/src/com/vaadin/client/ui/aria/HandlesAriaRequired.java
new file mode 100644
index 0000000000..9b18bfb4de
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/aria/HandlesAriaRequired.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.ui.aria;
+
+/**
+ * Some Widgets need to handle the required handling for WAI-ARIA themselfs, as
+ * this attribute needs to be set to the input element itself. In such a case,
+ * the Widget needs to implement this interface.
+ */
+public interface HandlesAriaRequired {
+ /**
+ * Called to set the element, typically an input element, as required.
+ *
+ * @param required
+ * boolean true when the element needs to be set as required
+ */
+ void setAriaRequired(boolean required);
+}
diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
index 9733d206c7..fff983c168 100644
--- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java
+++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java
@@ -24,6 +24,7 @@ import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
import com.vaadin.client.EventHelper;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.communication.StateChangeEvent;
@@ -83,8 +84,10 @@ public class ButtonConnector extends AbstractComponentConnector implements
if (getIcon() != null) {
if (getWidget().icon == null) {
getWidget().icon = new Icon(getConnection());
- getWidget().wrapper.insertBefore(
- getWidget().icon.getElement(),
+ Element iconElement = getWidget().icon.getElement();
+ iconElement.setAttribute("alt", getState().iconAltText);
+
+ getWidget().wrapper.insertBefore(iconElement,
getWidget().captionElement);
}
getWidget().icon.setUri(getIcon());
diff --git a/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java
new file mode 100644
index 0000000000..285d15792b
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java
@@ -0,0 +1,662 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar;
+
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.TooltipInfo;
+import com.vaadin.client.UIDL;
+import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
+import com.vaadin.client.communication.RpcProxy;
+import com.vaadin.client.communication.StateChangeEvent;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.Action;
+import com.vaadin.client.ui.ActionOwner;
+import com.vaadin.client.ui.SimpleManagedLayout;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.client.ui.VCalendar.BackwardListener;
+import com.vaadin.client.ui.VCalendar.DateClickListener;
+import com.vaadin.client.ui.VCalendar.EventClickListener;
+import com.vaadin.client.ui.VCalendar.EventMovedListener;
+import com.vaadin.client.ui.VCalendar.EventResizeListener;
+import com.vaadin.client.ui.VCalendar.ForwardListener;
+import com.vaadin.client.ui.VCalendar.MouseEventListener;
+import com.vaadin.client.ui.VCalendar.RangeSelectListener;
+import com.vaadin.client.ui.VCalendar.WeekClickListener;
+import com.vaadin.client.ui.calendar.schedule.CalendarDay;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.client.ui.calendar.schedule.DateCell;
+import com.vaadin.client.ui.calendar.schedule.DateCell.DateCellSlot;
+import com.vaadin.client.ui.calendar.schedule.DateCellDayEvent;
+import com.vaadin.client.ui.calendar.schedule.DateUtil;
+import com.vaadin.client.ui.calendar.schedule.HasTooltipKey;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.calendar.schedule.dd.CalendarDropHandler;
+import com.vaadin.client.ui.dd.VHasDropHandler;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.Connect.LoadStyle;
+import com.vaadin.shared.ui.calendar.CalendarClientRpc;
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.CalendarState;
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.Calendar;
+
+/**
+ * Handles communication between Calendar on the server side and
+ * {@link VCalendar} on the client side.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@Connect(value = Calendar.class, loadStyle = LoadStyle.LAZY)
+public class CalendarConnector extends AbstractComponentConnector implements
+ VHasDropHandler, ActionOwner, SimpleManagedLayout {
+
+ private CalendarServerRpc rpc = RpcProxy.create(CalendarServerRpc.class,
+ this);
+
+ private CalendarDropHandler dropHandler;
+
+ private final HashMap<String, String> actionMap = new HashMap<String, String>();
+ private HashMap<Object, String> tooltips = new HashMap<Object, String>();
+
+ /**
+ *
+ */
+ public CalendarConnector() {
+
+ // Listen to events
+ registerListeners();
+ }
+
+ @Override
+ protected void init() {
+ super.init();
+ registerRpc(CalendarClientRpc.class, new CalendarClientRpc() {
+ @Override
+ public void scroll(int scrollPosition) {
+ // TODO widget scroll
+ }
+ });
+ getLayoutManager().registerDependency(this, getWidget().getElement());
+ }
+
+ @Override
+ public void onUnregister() {
+ super.onUnregister();
+ getLayoutManager().unregisterDependency(this, getWidget().getElement());
+ }
+
+ @Override
+ public VCalendar getWidget() {
+ return (VCalendar) super.getWidget();
+ }
+
+ @Override
+ public CalendarState getState() {
+ return (CalendarState) super.getState();
+ }
+
+ /**
+ * Registers listeners on the calendar so server can be notified of the
+ * events
+ */
+ protected void registerListeners() {
+ getWidget().setListener(new DateClickListener() {
+ @Override
+ public void dateClick(String date) {
+ if (!getWidget().isDisabledOrReadOnly()
+ && hasEventListener(CalendarEventId.DATECLICK)) {
+ rpc.dateClick(date);
+ }
+ }
+ });
+ getWidget().setListener(new ForwardListener() {
+ @Override
+ public void forward() {
+ if (hasEventListener(CalendarEventId.FORWARD)) {
+ rpc.forward();
+ }
+ }
+ });
+ getWidget().setListener(new BackwardListener() {
+ @Override
+ public void backward() {
+ if (hasEventListener(CalendarEventId.BACKWARD)) {
+ rpc.backward();
+ }
+ }
+ });
+ getWidget().setListener(new RangeSelectListener() {
+ @Override
+ public void rangeSelected(String value) {
+ if (hasEventListener(CalendarEventId.RANGESELECT)) {
+ rpc.rangeSelect(value);
+ }
+ }
+ });
+ getWidget().setListener(new WeekClickListener() {
+ @Override
+ public void weekClick(String event) {
+ if (!getWidget().isDisabledOrReadOnly()
+ && hasEventListener(CalendarEventId.WEEKCLICK)) {
+ rpc.weekClick(event);
+ }
+ }
+ });
+ getWidget().setListener(new EventMovedListener() {
+ @Override
+ public void eventMoved(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTMOVE)) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DateUtil.formatClientSideDate(event.getStart()));
+ sb.append("-");
+ sb.append(DateUtil.formatClientSideTime(event
+ .getStartTime()));
+ rpc.eventMove(event.getIndex(), sb.toString());
+ }
+ }
+ });
+ getWidget().setListener(new EventResizeListener() {
+ @Override
+ public void eventResized(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTRESIZE)) {
+ StringBuilder buffer = new StringBuilder();
+
+ buffer.append(DateUtil.formatClientSideDate(event
+ .getStart()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event
+ .getStartTime()));
+
+ String newStartDate = buffer.toString();
+
+ buffer = new StringBuilder();
+ buffer.append(DateUtil.formatClientSideDate(event.getEnd()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event
+ .getEndTime()));
+
+ String newEndDate = buffer.toString();
+
+ rpc.eventResize(event.getIndex(), newStartDate, newEndDate);
+ }
+ }
+ });
+ getWidget().setListener(new VCalendar.ScrollListener() {
+ @Override
+ public void scroll(int scrollPosition) {
+ // This call is @Delayed (== non-immediate)
+ rpc.scroll(scrollPosition);
+ }
+ });
+ getWidget().setListener(new EventClickListener() {
+ @Override
+ public void eventClick(CalendarEvent event) {
+ if (hasEventListener(CalendarEventId.EVENTCLICK)) {
+ rpc.eventClick(event.getIndex());
+ }
+ }
+ });
+ getWidget().setListener(new MouseEventListener() {
+ @Override
+ public void contextMenu(ContextMenuEvent event, final Widget widget) {
+ final NativeEvent ne = event.getNativeEvent();
+ int left = ne.getClientX();
+ int top = ne.getClientY();
+ top += Window.getScrollTop();
+ left += Window.getScrollLeft();
+ getClient().getContextMenu().showAt(new ActionOwner() {
+ @Override
+ public String getPaintableId() {
+ return CalendarConnector.this.getPaintableId();
+ }
+
+ @Override
+ public ApplicationConnection getClient() {
+ return CalendarConnector.this.getClient();
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public Action[] getActions() {
+ if (widget instanceof SimpleDayCell) {
+ /*
+ * Month view
+ */
+ SimpleDayCell cell = (SimpleDayCell) widget;
+ Date start = new Date(cell.getDate().getYear(),
+ cell.getDate().getMonth(), cell.getDate()
+ .getDate(), 0, 0, 0);
+
+ Date end = new Date(cell.getDate().getYear(), cell
+ .getDate().getMonth(), cell.getDate()
+ .getDate(), 23, 59, 59);
+
+ return CalendarConnector.this.getActionsBetween(
+ start, end);
+ } else if (widget instanceof DateCell) {
+ /*
+ * Week and Day view
+ */
+ DateCell cell = (DateCell) widget;
+ int slotIndex = DOM.getChildIndex(
+ cell.getElement(), (Element) ne
+ .getEventTarget().cast());
+ DateCellSlot slot = cell.getSlot(slotIndex);
+ return CalendarConnector.this.getActionsBetween(
+ slot.getFrom(), slot.getTo());
+ } else if (widget instanceof DateCellDayEvent) {
+ /*
+ * Context menu on event
+ */
+ DateCellDayEvent dayEvent = (DateCellDayEvent) widget;
+ CalendarEvent event = dayEvent.getCalendarEvent();
+ Action[] actions = CalendarConnector.this
+ .getActionsBetween(event.getStartTime(),
+ event.getEndTime());
+ for (Action action : actions) {
+ ((VCalendarAction) action).setEvent(event);
+ }
+ return actions;
+
+ }
+ return null;
+ }
+ }, left, top);
+ }
+ });
+ }
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ CalendarState state = getState();
+ VCalendar widget = getWidget();
+ boolean monthView = state.days.size() > 7;
+
+ // Enable or disable the forward and backward navigation buttons
+ widget.setForwardNavigationEnabled(hasEventListener(CalendarEventId.FORWARD));
+ widget.setBackwardNavigationEnabled(hasEventListener(CalendarEventId.BACKWARD));
+
+ widget.set24HFormat(state.format24H);
+ widget.setDayNames(state.dayNames);
+ widget.setMonthNames(state.monthNames);
+ widget.setFirstDayNumber(state.firstVisibleDayOfWeek);
+ widget.setLastDayNumber(state.lastVisibleDayOfWeek);
+ widget.setFirstHourOfTheDay(state.firstHourOfDay);
+ widget.setLastHourOfTheDay(state.lastHourOfDay);
+ widget.setReadOnly(state.readOnly);
+ widget.setDisabled(!state.enabled);
+
+ widget.setRangeSelectAllowed(hasEventListener(CalendarEventId.RANGESELECT));
+ widget.setRangeMoveAllowed(hasEventListener(CalendarEventId.EVENTMOVE));
+ widget.setEventMoveAllowed(hasEventListener(CalendarEventId.EVENTMOVE));
+ widget.setEventResizeAllowed(hasEventListener(CalendarEventId.EVENTRESIZE));
+
+ List<CalendarState.Day> days = state.days;
+ List<CalendarState.Event> events = state.events;
+
+ if (monthView) {
+ updateMonthView(days, events);
+ } else {
+ updateWeekView(days, events);
+ }
+
+ updateSizes();
+
+ registerEventToolTips(state.events);
+ updateActionMap(state.actions);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal
+ * .gwt.client.UIDL, com.vaadin.terminal.gwt.client.ApplicationConnection)
+ */
+ public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
+
+ // check for DD -related access criteria
+ // Iterator<Object> childIterator = uidl.getChildIterator();
+ // while (childIterator.hasNext()) {
+ // UIDL child = (UIDL) childIterator.next();
+ //
+ // // Drag&drop
+ // if (ACCESSCRITERIA.equals(child.getTag())) {
+ // if (monthView
+ // && !(getDropHandler() instanceof CalendarMonthDropHandler)) {
+ // setDropHandler(new CalendarMonthDropHandler());
+ //
+ // } else if (!monthView
+ // && !(getDropHandler() instanceof CalendarWeekDropHandler)) {
+ // setDropHandler(new CalendarWeekDropHandler());
+ // }
+ //
+ // getDropHandler().setCalendarPaintable(this);
+ // getDropHandler().updateAcceptRules(child);
+ //
+ // } else {
+ // setDropHandler(null);
+ // }
+ //
+ // }
+ }
+
+ /**
+ * Returns the ApplicationConnection used to connect to the server side
+ */
+ @Override
+ public ApplicationConnection getClient() {
+ return getConnection();
+ }
+
+ /**
+ * Register the description of the events as tooltips. This way, any event
+ * displaying widget can use the event index as a key to display the
+ * tooltip.
+ */
+ private void registerEventToolTips(List<CalendarState.Event> events) {
+ for (CalendarState.Event e : events) {
+ if (e.description != null && !"".equals(e.description)) {
+ tooltips.put(e.index, e.description);
+ } else {
+ tooltips.remove(e.index);
+ }
+ }
+ }
+
+ @Override
+ public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) {
+ TooltipInfo tooltipInfo = null;
+ Widget w = Util.findWidget((Element) element, null);
+ if (w instanceof HasTooltipKey) {
+ tooltipInfo = GWT.create(TooltipInfo.class);
+ String title = tooltips.get(((HasTooltipKey) w).getTooltipKey());
+ tooltipInfo.setTitle(title != null ? title : "");
+ }
+ if (tooltipInfo == null) {
+ tooltipInfo = super.getTooltipInfo(element);
+ }
+ return tooltipInfo;
+ }
+
+ @Override
+ public boolean hasTooltip() {
+ /*
+ * Tooltips are not processed until updateFromUIDL, so we can't be sure
+ * that there are no tooltips during onStateChange when this is used.
+ */
+ return true;
+ }
+
+ private void updateMonthView(List<CalendarState.Day> days,
+ List<CalendarState.Event> events) {
+ CalendarState state = getState();
+ getWidget().updateMonthView(state.firstDayOfWeek,
+ getWidget().getDateTimeFormat().parse(state.now), days.size(),
+ calendarEventListOf(events, state.format24H),
+ calendarDayListOf(days));
+ }
+
+ private void updateWeekView(List<CalendarState.Day> days,
+ List<CalendarState.Event> events) {
+ CalendarState state = getState();
+ getWidget().updateWeekView(state.scroll,
+ getWidget().getDateTimeFormat().parse(state.now), days.size(),
+ state.firstDayOfWeek,
+ calendarEventListOf(events, state.format24H),
+ calendarDayListOf(days));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VHasDropHandler#getDropHandler()
+ */
+ @Override
+ public CalendarDropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ /**
+ * Set the drop handler
+ *
+ * @param dropHandler
+ * The drophandler to use
+ */
+ public void setDropHandler(CalendarDropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ private Action[] getActionsBetween(Date start, Date end) {
+ List<Action> actions = new ArrayList<Action>();
+ for (int i = 0; i < actionKeys.size(); i++) {
+ final String actionKey = actionKeys.get(i);
+ Date actionStartDate;
+ Date actionEndDate;
+ try {
+ actionStartDate = getActionStartDate(actionKey);
+ actionEndDate = getActionEndDate(actionKey);
+ } catch (ParseException pe) {
+ VConsole.error("Failed to parse action date");
+ continue;
+ }
+
+ boolean startIsValid = start.compareTo(actionStartDate) >= 0;
+ boolean endIsValid = end.compareTo(actionEndDate) <= 0;
+ if (startIsValid && endIsValid) {
+ VCalendarAction a = new VCalendarAction(this, rpc, actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+ a.setActionStartDate(start);
+ a.setActionEndDate(end);
+ actions.add(a);
+ }
+ }
+
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ private List<String> actionKeys = new ArrayList<String>();
+
+ private void updateActionMap(List<CalendarState.Action> actions) {
+ actionMap.clear();
+ actionKeys.clear();
+
+ if (actions == null) {
+ return;
+ }
+
+ for (CalendarState.Action action : actions) {
+ String id = action.actionKey + "-" + action.startDate + "-"
+ + action.endDate;
+ actionMap.put(id + "_c", action.caption);
+ actionMap.put(id + "_s", action.startDate);
+ actionMap.put(id + "_e", action.endDate);
+ actionKeys.add(id);
+ if (action.iconKey != null) {
+ actionMap.put(id + "_i", getResourceUrl(action.iconKey));
+
+ } else {
+ actionMap.remove(id + "_i");
+ }
+ }
+ }
+
+ /**
+ * Get the text that is displayed for a context menu item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ */
+ public String getActionCaption(String actionKey) {
+ return actionMap.get(actionKey + "_c");
+ }
+
+ /**
+ * Get the icon url for a context menu item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ */
+ public String getActionIcon(String actionKey) {
+ return actionMap.get(actionKey + "_i");
+ }
+
+ /**
+ * Get the start date for an action item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ * @throws ParseException
+ */
+ public Date getActionStartDate(String actionKey) throws ParseException {
+ String dateStr = actionMap.get(actionKey + "_s");
+ DateTimeFormat formatter = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ return formatter.parse(dateStr);
+ }
+
+ /**
+ * Get the end date for an action item
+ *
+ * @param actionKey
+ * The unique action key
+ * @return
+ * @throws ParseException
+ */
+ public Date getActionEndDate(String actionKey) throws ParseException {
+ String dateStr = actionMap.get(actionKey + "_e");
+ DateTimeFormat formatter = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ return formatter.parse(dateStr);
+ }
+
+ /**
+ * Returns ALL currently registered events. Use {@link #getActions(Date)} to
+ * get the actions for a specific date
+ */
+ @Override
+ public Action[] getActions() {
+ List<Action> actions = new ArrayList<Action>();
+ for (int i = 0; i < actionKeys.size(); i++) {
+ final String actionKey = actionKeys.get(i);
+ final VCalendarAction a = new VCalendarAction(this, rpc, actionKey);
+ a.setCaption(getActionCaption(actionKey));
+ a.setIconUrl(getActionIcon(actionKey));
+
+ try {
+ a.setActionStartDate(getActionStartDate(actionKey));
+ a.setActionEndDate(getActionEndDate(actionKey));
+ } catch (ParseException pe) {
+ VConsole.error(pe);
+ }
+
+ actions.add(a);
+ }
+ return actions.toArray(new Action[actions.size()]);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.ActionOwner#getPaintableId()
+ */
+ @Override
+ public String getPaintableId() {
+ return getConnectorId();
+ }
+
+ private List<CalendarEvent> calendarEventListOf(
+ List<CalendarState.Event> events, boolean format24h) {
+ List<CalendarEvent> list = new ArrayList<CalendarEvent>(events.size());
+ for (CalendarState.Event event : events) {
+ final String dateFrom = event.dateFrom;
+ final String dateTo = event.dateTo;
+ final String timeFrom = event.timeFrom;
+ final String timeTo = event.timeTo;
+ CalendarEvent calendarEvent = new CalendarEvent();
+ calendarEvent.setAllDay(event.allDay);
+ calendarEvent.setCaption(event.caption);
+ calendarEvent.setDescription(event.description);
+ calendarEvent.setStart(getWidget().getDateFormat().parse(dateFrom));
+ calendarEvent.setEnd(getWidget().getDateFormat().parse(dateTo));
+ calendarEvent.setFormat24h(format24h);
+ calendarEvent.setStartTime(getWidget().getDateTimeFormat().parse(
+ dateFrom + " " + timeFrom));
+ calendarEvent.setEndTime(getWidget().getDateTimeFormat().parse(
+ dateTo + " " + timeTo));
+ calendarEvent.setStyleName(event.styleName);
+ calendarEvent.setIndex(event.index);
+ list.add(calendarEvent);
+ }
+ return list;
+ }
+
+ private List<CalendarDay> calendarDayListOf(List<CalendarState.Day> days) {
+ List<CalendarDay> list = new ArrayList<CalendarDay>(days.size());
+ for (CalendarState.Day day : days) {
+ CalendarDay d = new CalendarDay(day.date, day.localizedDateFormat,
+ day.dayOfWeek, day.week);
+
+ list.add(d);
+ }
+ return list;
+ }
+
+ @Override
+ public void layout() {
+ updateSizes();
+ }
+
+ private void updateSizes() {
+ int height = getLayoutManager()
+ .getOuterHeight(getWidget().getElement());
+ int width = getLayoutManager().getOuterWidth(getWidget().getElement());
+
+ if (isUndefinedWidth()) {
+ width = -1;
+ }
+ if (isUndefinedHeight()) {
+ height = -1;
+ }
+
+ getWidget().setSizeForChildren(width, height);
+
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java b/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java
new file mode 100644
index 0000000000..2a529354e5
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/VCalendarAction.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.client.ui.Action;
+import com.vaadin.client.ui.calendar.schedule.CalendarEvent;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Action performed by the calendar
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class VCalendarAction extends Action {
+
+ private CalendarServerRpc rpc;
+
+ private String actionKey = "";
+
+ private Date actionStartDate;
+
+ private Date actionEndDate;
+
+ private CalendarEvent event;
+
+ private final DateTimeFormat dateformat_datetime = DateTimeFormat
+ .getFormat(DateConstants.ACTION_DATE_FORMAT_PATTERN);
+
+ /**
+ *
+ * @param owner
+ */
+ public VCalendarAction(CalendarConnector owner) {
+ super(owner);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param owner
+ * The owner who trigger this kinds of events
+ * @param rpc
+ * The CalendarRpc which is used for executing actions
+ * @param key
+ * The unique action key which identifies this particular action
+ */
+ public VCalendarAction(CalendarConnector owner, CalendarServerRpc rpc,
+ String key) {
+ this(owner);
+ this.rpc = rpc;
+ actionKey = key;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.terminal.gwt.client.ui.Action#execute()
+ */
+ @Override
+ public void execute() {
+ String startDate = dateformat_datetime.format(actionStartDate);
+ String endDate = dateformat_datetime.format(actionEndDate);
+
+ if (event == null) {
+ rpc.actionOnEmptyCell(actionKey.split("-")[0], startDate, endDate);
+ } else {
+ rpc.actionOnEvent(actionKey.split("-")[0], startDate, endDate,
+ event.getIndex());
+ }
+
+ owner.getClient().getContextMenu().hide();
+ }
+
+ /**
+ * Get the date and time when the action starts
+ *
+ * @return
+ */
+ public Date getActionStartDate() {
+ return actionStartDate;
+ }
+
+ /**
+ * Set the date when the actions start
+ *
+ * @param actionStartDate
+ * The date and time when the action starts
+ */
+ public void setActionStartDate(Date actionStartDate) {
+ this.actionStartDate = actionStartDate;
+ }
+
+ /**
+ * Get the date and time when the action ends
+ *
+ * @return
+ */
+ public Date getActionEndDate() {
+ return actionEndDate;
+ }
+
+ /**
+ * Set the date and time when the action ends
+ *
+ * @param actionEndDate
+ * The date and time when the action ends
+ */
+ public void setActionEndDate(Date actionEndDate) {
+ this.actionEndDate = actionEndDate;
+ }
+
+ public CalendarEvent getEvent() {
+ return event;
+ }
+
+ public void setEvent(CalendarEvent event) {
+ this.event = event;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java
new file mode 100644
index 0000000000..ca176c08c1
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarDay.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+/**
+ * Utility class used to represent a day when updating views. Only used
+ * internally.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarDay {
+ private String date;
+ private String localizedDateFormat;
+ private int dayOfWeek;
+ private int week;
+
+ public CalendarDay(String date, String localizedDateFormat, int dayOfWeek,
+ int week) {
+ super();
+ this.date = date;
+ this.localizedDateFormat = localizedDateFormat;
+ this.dayOfWeek = dayOfWeek;
+ this.week = week;
+ }
+
+ public String getDate() {
+ return date;
+ }
+
+ public String getLocalizedDateFormat() {
+ return localizedDateFormat;
+ }
+
+ public int getDayOfWeek() {
+ return dayOfWeek;
+ }
+
+ public int getWeek() {
+ return week;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java
new file mode 100644
index 0000000000..e2c06d41ea
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/CalendarEvent.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * A client side implementation of a calendar event
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarEvent {
+ private int index;
+ private String caption;
+ private Date start, end;
+ private String styleName;
+ private Date startTime, endTime;
+ private String description;
+ private int slotIndex = -1;
+ private boolean format24h;
+
+ DateTimeFormat dateformat_date = DateTimeFormat.getFormat("h:mm a");
+ DateTimeFormat dateformat_date24 = DateTimeFormat.getFormat("H:mm");
+ private boolean allDay;
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ */
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ * @param style
+ */
+ public void setStyleName(String style) {
+ styleName = style;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ * @param start
+ */
+ public void setStart(Date start) {
+ this.start = start;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ * @return
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ * @param end
+ */
+ public void setEnd(Date end) {
+ this.end = end;
+ }
+
+ /**
+ * Returns the start time of the event
+ *
+ * @return Time embedded in the {@link Date} object
+ */
+ public Date getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Set the start time of the event
+ *
+ * @param startTime
+ * The time of the event. Use the time fields in the {@link Date}
+ * object
+ */
+ public void setStartTime(Date startTime) {
+ this.startTime = startTime;
+ }
+
+ /**
+ * Get the end time of the event
+ *
+ * @return Time embedded in the {@link Date} object
+ */
+ public Date getEndTime() {
+ return endTime;
+ }
+
+ /**
+ * Set the end time of the event
+ *
+ * @param endTime
+ * Time embedded in the {@link Date} object
+ */
+ public void setEndTime(Date endTime) {
+ this.endTime = endTime;
+ }
+
+ /**
+ * Get the (server side) index of the event
+ *
+ * @return
+ */
+ public int getIndex() {
+ return index;
+ }
+
+ /**
+ * Get the index of the slot where the event in rendered
+ *
+ * @return
+ */
+ public int getSlotIndex() {
+ return slotIndex;
+ }
+
+ /**
+ * Set the index of the slot where the event in rendered
+ *
+ * @param index
+ * The index of the slot
+ */
+ public void setSlotIndex(int index) {
+ slotIndex = index;
+ }
+
+ /**
+ * Set the (server side) index of the event
+ *
+ * @param index
+ * The index
+ */
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Get the caption of the event. The caption is the text displayed in the
+ * calendar on the event.
+ *
+ * @return
+ */
+ public String getCaption() {
+ return caption;
+ }
+
+ /**
+ * Set the caption of the event. The caption is the text displayed in the
+ * calendar on the event.
+ *
+ * @param caption
+ * The visible caption of the event
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ }
+
+ /**
+ * Get the description of the event. The description is the text displayed
+ * when hoovering over the event with the mouse
+ *
+ * @return
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Set the description of the event. The description is the text displayed
+ * when hoovering over the event with the mouse
+ *
+ * @param description
+ */
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ /**
+ * Does the event use the 24h time format
+ *
+ * @param format24h
+ * True if it uses the 24h format, false if it uses the 12h time
+ * format
+ */
+ public void setFormat24h(boolean format24h) {
+ this.format24h = format24h;
+ }
+
+ /**
+ * Is the event an all day event.
+ *
+ * @param allDay
+ * True if the event should be rendered all day
+ */
+ public void setAllDay(boolean allDay) {
+ this.allDay = allDay;
+ }
+
+ /**
+ * Is the event an all day event.
+ *
+ * @return
+ */
+ public boolean isAllDay() {
+ return allDay;
+ }
+
+ /**
+ * Get the time as a formatted string
+ *
+ * @return
+ */
+ public String getTimeAsText() {
+ if (format24h) {
+ return dateformat_date24.format(startTime);
+ } else {
+ return dateformat_date.format(startTime);
+ }
+ }
+
+ /**
+ * Get the amount of milliseconds between the start and end of the event
+ *
+ * @return
+ */
+ public long getRangeInMilliseconds() {
+ return getEndTime().getTime() - getStartTime().getTime();
+ }
+
+ /**
+ * Get the amount of minutes between the start and end of the event
+ *
+ * @return
+ */
+ public long getRangeInMinutes() {
+ return (getRangeInMilliseconds() / DateConstants.MINUTEINMILLIS);
+ }
+
+ /**
+ * Get the amount of minutes for the event on a specific day. This is useful
+ * if the event spans several days.
+ *
+ * @param targetDay
+ * The date to check
+ * @return
+ */
+ public long getRangeInMinutesForDay(Date targetDay) {
+ if (isTimeOnDifferentDays()) {
+ // Time range is on different days. Calculate the second day's
+ // range.
+ long range = (getEndTime().getTime() - getEnd().getTime())
+ / DateConstants.MINUTEINMILLIS;
+
+ if (getEnd().compareTo(targetDay) != 0) {
+ // Calculate first day's range.
+ return getRangeInMinutes() - range;
+ }
+
+ return range;
+ } else {
+ return getRangeInMinutes();
+ }
+ }
+
+ /**
+ * Does the event span several days
+ *
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public boolean isTimeOnDifferentDays() {
+ if (getEndTime().getTime() - getStart().getTime() > DateConstants.DAYINMILLIS) {
+ return true;
+ }
+
+ if (getStart().compareTo(getEnd()) != 0) {
+ if (getEndTime().getHours() == 0 && getEndTime().getMinutes() == 0) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java
new file mode 100644
index 0000000000..516447153e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java
@@ -0,0 +1,810 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.NodeList;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+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.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.Util;
+
+public class DateCell extends FocusableComplexPanel implements
+ MouseDownHandler, MouseMoveHandler, MouseUpHandler, KeyDownHandler,
+ ContextMenuHandler {
+ private static final String DRAGEMPHASISSTYLE = " dragemphasis";
+ private Date date;
+ private int width;
+ private int eventRangeStart = -1;
+ private int eventRangeStop = -1;
+ final WeekGrid weekgrid;
+ private boolean disabled = false;
+ private int height;
+ private final Element[] slotElements;
+ private final List<DateCellSlot> slots = new ArrayList<DateCell.DateCellSlot>();
+ private int[] slotElementHeights;
+ private int startingSlotHeight;
+ private Date today;
+ private Element todaybar;
+ private final List<HandlerRegistration> handlers;
+ private final int numberOfSlots;
+ private final int firstHour;
+ private final int lastHour;
+
+ public class DateCellSlot extends Widget {
+
+ private final DateCell cell;
+
+ private final Date from;
+
+ private final Date to;
+
+ public DateCellSlot(DateCell cell, Date from, Date to) {
+ setElement(DOM.createDiv());
+ getElement().setInnerHTML("&nbsp;");
+ this.cell = cell;
+ this.from = from;
+ this.to = to;
+ }
+
+ public Date getFrom() {
+ return from;
+ }
+
+ public Date getTo() {
+ return to;
+ }
+
+ public DateCell getParentCell() {
+ return cell;
+ }
+ }
+
+ public DateCell(WeekGrid parent, Date date) {
+ weekgrid = parent;
+ Element mainElement = DOM.createDiv();
+ setElement(mainElement);
+ makeFocusable();
+ setDate(date);
+
+ addStyleName("v-calendar-day-times");
+
+ handlers = new LinkedList<HandlerRegistration>();
+
+ // 2 slots / hour
+ firstHour = weekgrid.getFirstHour();
+ lastHour = weekgrid.getLastHour();
+ numberOfSlots = (lastHour - firstHour + 1) * 2;
+ long slotTime = Math.round(((lastHour - firstHour + 1) * 3600000.0)
+ / numberOfSlots);
+
+ slotElements = new Element[numberOfSlots];
+ slotElementHeights = new int[numberOfSlots];
+
+ slots.clear();
+ long start = getDate().getTime() + firstHour * 3600000;
+ long end = start + slotTime;
+ for (int i = 0; i < numberOfSlots; i++) {
+ DateCellSlot slot = new DateCellSlot(this, new Date(start),
+ new Date(end));
+ if (i % 2 == 0) {
+ slot.setStyleName("v-datecellslot-even");
+ } else {
+ slot.setStyleName("v-datecellslot");
+ }
+ Event.sinkEvents(slot.getElement(), Event.MOUSEEVENTS);
+ mainElement.appendChild(slot.getElement());
+ slotElements[i] = slot.getElement();
+ slots.add(slot);
+ start = end;
+ end = start + slotTime;
+ }
+
+ // Sink events for tooltip handling
+ Event.sinkEvents(mainElement, Event.MOUSEEVENTS);
+ }
+
+ public int getFirstHour() {
+ return firstHour;
+ }
+
+ public int getLastHour() {
+ return lastHour;
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+
+ handlers.add(addHandler(this, MouseDownEvent.getType()));
+ handlers.add(addHandler(this, MouseUpEvent.getType()));
+ handlers.add(addHandler(this, MouseMoveEvent.getType()));
+ handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
+ handlers.add(addKeyDownHandler(this));
+ }
+
+ @Override
+ protected void onDetach() {
+ for (HandlerRegistration handler : handlers) {
+ handler.removeHandler();
+ }
+ handlers.clear();
+
+ super.onDetach();
+ }
+
+ public int getSlotIndex(Element slotElement) {
+ for (int i = 0; i < slotElements.length; i++) {
+ if (slotElement == slotElements[i]) {
+ return i;
+ }
+ }
+
+ throw new IllegalArgumentException("Element not found in this DateCell");
+ }
+
+ public DateCellSlot getSlot(int index) {
+ return slots.get(index);
+ }
+
+ public int getNumberOfSlots() {
+ return numberOfSlots;
+ }
+
+ public void setTimeBarWidth(int timebarWidth) {
+ todaybar.getStyle().setWidth(timebarWidth, Unit.PX);
+ }
+
+ /**
+ * @param isHorizontalSized
+ * if true, this DateCell is sized with CSS and not via
+ * {@link #setWidthPX(int)}
+ */
+ public void setHorizontalSized(boolean isHorizontalSized) {
+ if (isHorizontalSized) {
+ addStyleDependentName("Hsized");
+
+ width = getOffsetWidth()
+ - Util.measureHorizontalBorder(getElement());
+ recalculateEventWidths();
+ } else {
+ removeStyleDependentName("Hsized");
+ }
+ }
+
+ /**
+ * @param isVerticalSized
+ * if true, this DateCell is sized with CSS and not via
+ * {@link #setHeightPX(int)}
+ */
+ public void setVerticalSized(boolean isVerticalSized) {
+ if (isVerticalSized) {
+ addStyleDependentName("Vsized");
+
+ // recalc heights&size for events. all other height sizes come
+ // from css
+ startingSlotHeight = slotElements[0].getOffsetHeight();
+ recalculateEventPositions();
+
+ if (isToday()) {
+ recalculateTimeBarPosition();
+ }
+
+ } else {
+ removeStyleDependentName("Vsized");
+ }
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setWidthPX(int cellWidth) {
+ width = cellWidth;
+ setWidth(cellWidth + "px");
+ recalculateEventWidths();
+ }
+
+ public void setHeightPX(int height, int[] cellHeights) {
+ this.height = height;
+ slotElementHeights = cellHeights;
+ setHeight(height + "px");
+ recalculateCellHeights();
+ recalculateEventPositions();
+ if (today != null) {
+ recalculateTimeBarPosition();
+ }
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private void recalculateTimeBarPosition() {
+ int h = today.getHours();
+ int m = today.getMinutes();
+ if (h >= firstHour && h <= lastHour) {
+ int pixelTop = weekgrid.getPixelTopFor(m + 60 * h);
+ todaybar.getStyle().clearDisplay();
+ todaybar.getStyle().setTop(pixelTop, Unit.PX);
+ } else {
+ todaybar.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ private void recalculateEventPositions() {
+ for (int i = 0; i < getWidgetCount(); i++) {
+ DateCellDayEvent dayEvent = (DateCellDayEvent) getWidget(i);
+ updatePositionFor(dayEvent, getDate(), dayEvent.getCalendarEvent());
+ }
+ }
+
+ public void recalculateEventWidths() {
+ List<DateCellGroup> groups = new ArrayList<DateCellGroup>();
+
+ int count = getWidgetCount();
+
+ List<Integer> handled = new ArrayList<Integer>();
+
+ // Iterate through all events and group them. Events that overlaps
+ // with each other, are added to the same group.
+ for (int i = 0; i < count; i++) {
+ if (handled.contains(i)) {
+ continue;
+ }
+
+ DateCellGroup curGroup = getOverlappingEvents(i);
+ handled.addAll(curGroup.getItems());
+
+ boolean newGroup = true;
+ // No need to check other groups, if size equals the count
+ if (curGroup.getItems().size() != count) {
+ // Check other groups. When the whole group overlaps with
+ // other group, the group is merged to the other.
+ for (DateCellGroup g : groups) {
+
+ if (WeekGridMinuteTimeRange.doesOverlap(
+ curGroup.getDateRange(), g.getDateRange())) {
+ newGroup = false;
+ updateGroup(g, curGroup);
+ }
+ }
+ } else {
+ if (newGroup) {
+ groups.add(curGroup);
+ }
+ break;
+ }
+
+ if (newGroup) {
+ groups.add(curGroup);
+ }
+ }
+
+ drawDayEvents(groups);
+ }
+
+ private void recalculateCellHeights() {
+ startingSlotHeight = height / numberOfSlots;
+
+ for (int i = 0; i < slotElements.length; i++) {
+ slotElements[i].getStyle()
+ .setHeight(slotElementHeights[i], Unit.PX);
+ }
+
+ Iterator<Widget> it = iterator();
+ while (it.hasNext()) {
+ Widget child = it.next();
+ if (child instanceof DateCellDayEvent) {
+ ((DateCellDayEvent) child).setSlotHeightInPX(getSlotHeight());
+ }
+
+ }
+ }
+
+ public int getSlotHeight() {
+ return startingSlotHeight;
+ }
+
+ public int getSlotBorder() {
+ return Util
+ .measureVerticalBorder((com.google.gwt.user.client.Element) slotElements[0]);
+ }
+
+ private void drawDayEvents(List<DateCellGroup> groups) {
+ for (DateCellGroup g : groups) {
+ int col = 0;
+ int colCount = 0;
+ List<Integer> order = new ArrayList<Integer>();
+ Map<Integer, Integer> columns = new HashMap<Integer, Integer>();
+ for (Integer eventIndex : g.getItems()) {
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(eventIndex);
+ d.setMoveWidth(width);
+
+ int freeSpaceCol = findFreeColumnSpaceOnLeft(
+ new WeekGridMinuteTimeRange(d.getCalendarEvent()
+ .getStartTime(), d.getCalendarEvent()
+ .getEndTime()), order, columns);
+ if (freeSpaceCol >= 0) {
+ col = freeSpaceCol;
+ columns.put(eventIndex, col);
+ int newOrderindex = 0;
+ for (Integer i : order) {
+ if (columns.get(i) >= col) {
+ newOrderindex = order.indexOf(i);
+ break;
+ }
+ }
+ order.add(newOrderindex, eventIndex);
+ } else {
+ // New column
+ col = colCount++;
+ columns.put(eventIndex, col);
+ order.add(eventIndex);
+ }
+ }
+
+ // Update widths and left position
+ int eventWidth = (width / colCount);
+ for (Integer index : g.getItems()) {
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(index);
+ d.getElement()
+ .getStyle()
+ .setMarginLeft((eventWidth * columns.get(index)),
+ Unit.PX);
+ d.setWidth(eventWidth + "px");
+ d.setSlotHeightInPX(getSlotHeight());
+ }
+ }
+ }
+
+ private int findFreeColumnSpaceOnLeft(WeekGridMinuteTimeRange dateRange,
+ List<Integer> order, Map<Integer, Integer> columns) {
+ int freeSpot = -1;
+ int skipIndex = -1;
+ for (Integer eventIndex : order) {
+ int col = columns.get(eventIndex);
+ if (col == skipIndex) {
+ continue;
+ }
+
+ if (freeSpot != -1 && freeSpot != col) {
+ // Free spot found
+ return freeSpot;
+ }
+
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(eventIndex);
+ WeekGridMinuteTimeRange nextRange = new WeekGridMinuteTimeRange(d
+ .getCalendarEvent().getStartTime(), d.getCalendarEvent()
+ .getEndTime());
+
+ if (WeekGridMinuteTimeRange.doesOverlap(dateRange, nextRange)) {
+ skipIndex = col;
+ freeSpot = -1;
+ } else {
+ freeSpot = col;
+ }
+ }
+
+ return freeSpot;
+ }
+
+ /* Update top and bottom date range values. Add new index to the group. */
+ private void updateGroup(DateCellGroup targetGroup, DateCellGroup byGroup) {
+ Date newStart = targetGroup.getStart();
+ Date newEnd = targetGroup.getEnd();
+ if (byGroup.getStart().before(targetGroup.getStart())) {
+ newStart = byGroup.getEnd();
+ }
+ if (byGroup.getStart().after(targetGroup.getEnd())) {
+ newStart = byGroup.getStart();
+ }
+
+ targetGroup.setDateRange(new WeekGridMinuteTimeRange(newStart, newEnd));
+
+ for (Integer index : byGroup.getItems()) {
+ if (!targetGroup.getItems().contains(index)) {
+ targetGroup.add(index);
+ }
+ }
+ }
+
+ /**
+ * Returns all overlapping DayEvent indexes in the Group. Including the
+ * target.
+ *
+ * @param targetIndex
+ * Index of DayEvent in the current DateCell widget.
+ * @return Group that contains all Overlapping DayEvent indexes
+ */
+ public DateCellGroup getOverlappingEvents(int targetIndex) {
+ DateCellGroup g = new DateCellGroup(targetIndex);
+
+ int count = getWidgetCount();
+ DateCellDayEvent target = (DateCellDayEvent) getWidget(targetIndex);
+ WeekGridMinuteTimeRange targetRange = new WeekGridMinuteTimeRange(
+ target.getCalendarEvent().getStartTime(), target
+ .getCalendarEvent().getEndTime());
+ Date groupStart = targetRange.getStart();
+ Date groupEnd = targetRange.getEnd();
+
+ for (int i = 0; i < count; i++) {
+ if (targetIndex == i) {
+ continue;
+ }
+
+ DateCellDayEvent d = (DateCellDayEvent) getWidget(i);
+ WeekGridMinuteTimeRange nextRange = new WeekGridMinuteTimeRange(d
+ .getCalendarEvent().getStartTime(), d.getCalendarEvent()
+ .getEndTime());
+ if (WeekGridMinuteTimeRange.doesOverlap(targetRange, nextRange)) {
+ g.add(i);
+
+ // Update top & bottom values to the greatest
+ if (nextRange.getStart().before(targetRange.getStart())) {
+ groupStart = targetRange.getStart();
+ }
+ if (nextRange.getEnd().after(targetRange.getEnd())) {
+ groupEnd = targetRange.getEnd();
+ }
+ }
+ }
+
+ g.setDateRange(new WeekGridMinuteTimeRange(groupStart, groupEnd));
+ return g;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void addEvent(Date targetDay, CalendarEvent calendarEvent) {
+ Element main = getElement();
+ DateCellDayEvent dayEvent = new DateCellDayEvent(this, weekgrid,
+ calendarEvent);
+ dayEvent.setSlotHeightInPX(getSlotHeight());
+ dayEvent.setDisabled(isDisabled());
+
+ if (startingSlotHeight > 0) {
+ updatePositionFor(dayEvent, targetDay, calendarEvent);
+ }
+
+ add(dayEvent, (com.google.gwt.user.client.Element) main);
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private void updatePositionFor(DateCellDayEvent dayEvent, Date targetDay,
+ CalendarEvent calendarEvent) {
+ if (canDisplay(calendarEvent)) {
+
+ dayEvent.getElement().getStyle().clearDisplay();
+
+ Date fromDt = calendarEvent.getStartTime();
+ int h = fromDt.getHours();
+ int m = fromDt.getMinutes();
+ long range = calendarEvent.getRangeInMinutesForDay(targetDay);
+
+ boolean onDifferentDays = calendarEvent.isTimeOnDifferentDays();
+ if (onDifferentDays) {
+ if (calendarEvent.getStart().compareTo(targetDay) != 0) {
+ // Current day slot is for the end date. Lets fix also
+ // the
+ // start & end times.
+ h = 0;
+ m = 0;
+ }
+ }
+
+ int startFromMinutes = (h * 60) + m;
+ dayEvent.updatePosition(startFromMinutes, range);
+
+ } else {
+ dayEvent.getElement().getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ public void addEvent(DateCellDayEvent dayEvent) {
+ Element main = getElement();
+ int index = 0;
+ List<CalendarEvent> events = new ArrayList<CalendarEvent>();
+
+ // events are the only widgets in this panel
+ // slots are just elements
+ for (; index < getWidgetCount(); index++) {
+ DateCellDayEvent dc = (DateCellDayEvent) getWidget(index);
+ dc.setDisabled(isDisabled());
+ events.add(dc.getCalendarEvent());
+ }
+ events.add(dayEvent.getCalendarEvent());
+
+ index = 0;
+ for (CalendarEvent e : weekgrid.getCalendar().sortEventsByDuration(
+ events)) {
+ if (e.equals(dayEvent.getCalendarEvent())) {
+ break;
+ }
+ index++;
+ }
+ this.insert(dayEvent, (com.google.gwt.user.client.Element) main, index,
+ true);
+ }
+
+ public void removeEvent(DateCellDayEvent dayEvent) {
+ remove(dayEvent);
+ }
+
+ /**
+ *
+ * @param event
+ * @return
+ */
+ // Date methods not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private boolean canDisplay(CalendarEvent event) {
+ Date eventStart = event.getStartTime();
+ Date eventEnd = event.getEndTime();
+
+ int eventStartHours = eventStart.getHours();
+ int eventEndHours = eventEnd.getHours();
+
+ return (eventStartHours <= lastHour) && (eventEndHours >= firstHour);
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode == KeyCodes.KEY_ESCAPE && eventRangeStart > -1) {
+ cancelRangeSelect();
+ }
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ if (event.getNativeButton() == NativeEvent.BUTTON_LEFT) {
+ Element e = Element.as(event.getNativeEvent().getEventTarget());
+ if (e.getClassName().contains("reserved") || isDisabled()
+ || !weekgrid.getParentCalendar().isRangeSelectAllowed()) {
+ eventRangeStart = -1;
+ } else {
+ eventRangeStart = event.getY();
+ eventRangeStop = eventRangeStart;
+ Event.setCapture(getElement());
+ setFocus(true);
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+ Event.releaseCapture(getElement());
+ setFocus(false);
+ int dragDistance = Math.abs(eventRangeStart - event.getY());
+ if (dragDistance > 0 && eventRangeStart >= 0) {
+ Element main = getElement();
+ if (eventRangeStart > eventRangeStop) {
+ if (eventRangeStop <= -1) {
+ eventRangeStop = 0;
+ }
+ int temp = eventRangeStart;
+ eventRangeStart = eventRangeStop;
+ eventRangeStop = temp;
+ }
+
+ NodeList<Node> nodes = main.getChildNodes();
+
+ int slotStart = -1;
+ int slotEnd = -1;
+
+ // iterate over all child nodes, until we find first the start,
+ // and then the end
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.getItem(i);
+ boolean isRangeElement = element.getClassName().contains(
+ "v-daterange");
+
+ if (isRangeElement && slotStart == -1) {
+ slotStart = i;
+ slotEnd = i; // to catch one-slot selections
+
+ } else if (isRangeElement) {
+ slotEnd = i;
+
+ } else if (slotStart != -1 && slotEnd != -1) {
+ break;
+ }
+ }
+
+ clearSelectionRange();
+
+ int startMinutes = firstHour * 60 + slotStart * 30;
+ int endMinutes = (firstHour * 60) + (slotEnd + 1) * 30;
+ Date currentDate = getDate();
+ String yr = (currentDate.getYear() + 1900) + "-"
+ + (currentDate.getMonth() + 1) + "-"
+ + currentDate.getDate();
+ if (weekgrid.getCalendar().getRangeSelectListener() != null) {
+ weekgrid.getCalendar()
+ .getRangeSelectListener()
+ .rangeSelected(
+ yr + ":" + startMinutes + ":" + endMinutes);
+ }
+ eventRangeStart = -1;
+ } else {
+ // Click event
+ eventRangeStart = -1;
+ cancelRangeSelect();
+
+ }
+ }
+
+ @Override
+ public void onMouseMove(MouseMoveEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ if (eventRangeStart >= 0) {
+ int newY = event.getY();
+ int fromY = 0;
+ int toY = 0;
+ if (newY < eventRangeStart) {
+ fromY = newY;
+ toY = eventRangeStart;
+ } else {
+ fromY = eventRangeStart;
+ toY = newY;
+ }
+ Element main = getElement();
+ eventRangeStop = newY;
+ NodeList<Node> nodes = main.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element c = (Element) nodes.getItem(i);
+
+ if (todaybar != c) {
+
+ int elemStart = c.getOffsetTop();
+ int elemStop = elemStart + getSlotHeight();
+ if (elemStart >= fromY && elemStart <= toY) {
+ c.addClassName("v-daterange");
+ } else if (elemStop >= fromY && elemStop <= toY) {
+ c.addClassName("v-daterange");
+ } else if (elemStop >= fromY && elemStart <= toY) {
+ c.addClassName("v-daterange");
+ } else {
+ c.removeClassName("v-daterange");
+ }
+ }
+ }
+ }
+
+ event.preventDefault();
+ }
+
+ public void cancelRangeSelect() {
+ Event.releaseCapture(getElement());
+ setFocus(false);
+
+ clearSelectionRange();
+ }
+
+ private void clearSelectionRange() {
+ if (eventRangeStart > -1) {
+ // clear all "selected" class names
+ Element main = getElement();
+ NodeList<Node> nodes = main.getChildNodes();
+
+ for (int i = 0; i <= 47; i++) {
+ Element c = (Element) nodes.getItem(i);
+ if (c == null) {
+ continue;
+ }
+ c.removeClassName("v-daterange");
+ }
+
+ eventRangeStart = -1;
+ }
+ }
+
+ public void setToday(Date today, int width) {
+ this.today = today;
+ addStyleDependentName("today");
+ Element lastChild = (Element) getElement().getLastChild();
+ if (lastChild.getClassName().equals("v-calendar-current-time")) {
+ todaybar = lastChild;
+ } else {
+ todaybar = DOM.createDiv();
+ todaybar.setClassName("v-calendar-current-time");
+ getElement().appendChild(todaybar);
+ }
+
+ if (width != -1) {
+ todaybar.getStyle().setWidth(width, Unit.PX);
+ }
+
+ // position is calculated later, when we know the cell heights
+ }
+
+ public Element getTodaybarElement() {
+ return todaybar;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public void setDateColor(String styleName) {
+ this.setStyleName("v-calendar-datecell " + styleName);
+ }
+
+ public boolean isToday() {
+ return today != null;
+ }
+
+ public void addEmphasisStyle(com.google.gwt.user.client.Element elementOver) {
+ String originalStylename = getStyleName(elementOver);
+ setStyleName(elementOver, originalStylename + DRAGEMPHASISSTYLE);
+ }
+
+ public void removeEmphasisStyle(
+ com.google.gwt.user.client.Element elementOver) {
+ String originalStylename = getStyleName(elementOver);
+ setStyleName(
+ elementOver,
+ originalStylename.substring(0, originalStylename.length()
+ - DRAGEMPHASISSTYLE.length()));
+ }
+
+ @Override
+ public void onContextMenu(ContextMenuEvent event) {
+ if (weekgrid.getCalendar().getMouseEventListener() != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ weekgrid.getCalendar().getMouseEventListener()
+ .contextMenu(event, DateCell.this);
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java
new file mode 100644
index 0000000000..04e6bb7df6
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellContainer.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.event.dom.client.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * Internally used class by the Calendar
+ *
+ * since 7.1
+ */
+public class DateCellContainer extends FlowPanel implements MouseDownHandler,
+ MouseUpHandler {
+
+ private Date date;
+
+ private Widget clickTargetWidget;
+
+ private VCalendar calendar;
+
+ private static int borderWidth = -1;
+
+ public DateCellContainer() {
+ setStylePrimaryName("v-calendar-datecell");
+ }
+
+ public static int measureBorderWidth(DateCellContainer dc) {
+ if (borderWidth == -1) {
+ borderWidth = Util.measureHorizontalBorder(dc.getElement());
+ }
+ return borderWidth;
+ }
+
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public boolean hasEvent(int slotIndex) {
+ return hasDateCell(slotIndex)
+ && ((WeeklyLongEventsDateCell) getChildren().get(slotIndex))
+ .getEvent() != null;
+ }
+
+ public boolean hasDateCell(int slotIndex) {
+ return (getChildren().size() - 1) >= slotIndex;
+ }
+
+ public WeeklyLongEventsDateCell getDateCell(int slotIndex) {
+ if (!hasDateCell(slotIndex)) {
+ addEmptyEventCells(slotIndex - (getChildren().size() - 1));
+ }
+ return (WeeklyLongEventsDateCell) getChildren().get(slotIndex);
+ }
+
+ public void addEmptyEventCells(int eventCount) {
+ for (int i = 0; i < eventCount; i++) {
+ addEmptyEventCell();
+ }
+ }
+
+ public void addEmptyEventCell() {
+ WeeklyLongEventsDateCell dateCell = new WeeklyLongEventsDateCell();
+ dateCell.addMouseDownHandler(this);
+ dateCell.addMouseUpHandler(this);
+ add(dateCell);
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ clickTargetWidget = (Widget) event.getSource();
+
+ event.stopPropagation();
+ }
+
+ @Override
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getSource() == clickTargetWidget
+ && clickTargetWidget instanceof WeeklyLongEventsDateCell
+ && !calendar.isDisabledOrReadOnly()) {
+ CalendarEvent calendarEvent = ((WeeklyLongEventsDateCell) clickTargetWidget)
+ .getEvent();
+ if (calendar.getEventClickListener() != null) {
+ calendar.getEventClickListener().eventClick(calendarEvent);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
new file mode 100644
index 0000000000..c56566bf25
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.EventTarget;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ContextMenuEvent;
+import com.google.gwt.event.dom.client.ContextMenuHandler;
+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.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class DateCellDayEvent extends FocusableHTML implements
+ MouseDownHandler, MouseUpHandler, MouseMoveHandler, KeyDownHandler,
+ ContextMenuHandler, HasTooltipKey {
+
+ private final DateCell dateCell;
+ private Element caption = null;
+ private final Element eventContent;
+ private CalendarEvent calendarEvent = null;
+ private HandlerRegistration moveRegistration;
+ private int startY = -1;
+ private int startX = -1;
+ private String moveWidth;
+ public static final int halfHourInMilliSeconds = 1800 * 1000;
+ private Date startDatetimeFrom;
+ private Date startDatetimeTo;
+ private boolean mouseMoveStarted;
+ private int top;
+ private int startYrelative;
+ private int startXrelative;
+ private boolean disabled;
+ private final WeekGrid weekGrid;
+ private com.google.gwt.user.client.Element topResizeBar;
+ private com.google.gwt.user.client.Element bottomResizeBar;
+ private Element clickTarget;
+ private final Integer eventIndex;
+ private int slotHeight;
+ private final List<HandlerRegistration> handlers;
+ private boolean mouseMoveCanceled;
+
+ public DateCellDayEvent(DateCell dateCell, WeekGrid parent,
+ CalendarEvent event) {
+ super();
+ this.dateCell = dateCell;
+
+ handlers = new LinkedList<HandlerRegistration>();
+
+ setStylePrimaryName("v-calendar-event");
+ setCalendarEvent(event);
+
+ weekGrid = parent;
+
+ Style s = getElement().getStyle();
+ if (event.getStyleName().length() > 0) {
+ addStyleDependentName(event.getStyleName());
+ }
+ s.setPosition(Position.ABSOLUTE);
+
+ caption = DOM.createDiv();
+ caption.addClassName("v-calendar-event-caption");
+ getElement().appendChild(caption);
+
+ eventContent = DOM.createDiv();
+ eventContent.addClassName("v-calendar-event-content");
+ getElement().appendChild(eventContent);
+
+ VCalendar calendar = weekGrid.getCalendar();
+ if (weekGrid.getCalendar().isEventResizeAllowed()) {
+ topResizeBar = DOM.createDiv();
+ bottomResizeBar = DOM.createDiv();
+
+ topResizeBar.addClassName("v-calendar-event-resizetop");
+ bottomResizeBar.addClassName("v-calendar-event-resizebottom");
+
+ getElement().appendChild(topResizeBar);
+ getElement().appendChild(bottomResizeBar);
+ }
+
+ eventIndex = event.getIndex();
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ handlers.add(addMouseDownHandler(this));
+ handlers.add(addMouseUpHandler(this));
+ handlers.add(addKeyDownHandler(this));
+ handlers.add(addDomHandler(this, ContextMenuEvent.getType()));
+ }
+
+ @Override
+ protected void onDetach() {
+ for (HandlerRegistration handler : handlers) {
+ handler.removeHandler();
+ }
+ handlers.clear();
+ super.onDetach();
+ }
+
+ public void setSlotHeightInPX(int slotHeight) {
+ this.slotHeight = slotHeight;
+ }
+
+ public void updatePosition(long startFromMinutes, long durationInMinutes) {
+ if (startFromMinutes < 0) {
+ startFromMinutes = 0;
+ }
+ top = weekGrid.getPixelTopFor((int) startFromMinutes);
+
+ getElement().getStyle().setTop(top, Unit.PX);
+ if (durationInMinutes > 0) {
+ int heightMinutes = weekGrid.getPixelLengthFor(
+ (int) startFromMinutes, (int) durationInMinutes);
+ setHeight(heightMinutes);
+ } else {
+ setHeight(-1);
+ }
+
+ boolean multiRowCaption = (durationInMinutes > 30);
+ updateCaptions(multiRowCaption);
+ }
+
+ public int getTop() {
+ return top;
+ }
+
+ public void setMoveWidth(int width) {
+ moveWidth = width + "px";
+ }
+
+ public void setHeight(int h) {
+ if (h == -1) {
+ getElement().getStyle().setProperty("height", "");
+ eventContent.getStyle().setProperty("height", "");
+ } else {
+ getElement().getStyle().setHeight(h, Unit.PX);
+ // FIXME measure the border height (2px) from the DOM
+ eventContent.getStyle().setHeight(h - 2, Unit.PX);
+ }
+ }
+
+ /**
+ * @param bigMode
+ * If false, event is so small that caption must be in time-row
+ */
+ private void updateCaptions(boolean bigMode) {
+ String separator = bigMode ? "<br />" : ": ";
+ caption.setInnerHTML("<span>" + calendarEvent.getTimeAsText()
+ + "</span>" + separator
+ + Util.escapeHTML(calendarEvent.getCaption()));
+ eventContent.setInnerHTML("");
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeEvent().getKeyCode();
+ if (keycode == KeyCodes.KEY_ESCAPE && mouseMoveStarted) {
+ cancelMouseMove();
+ }
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ startX = event.getClientX();
+ startY = event.getClientY();
+ if (isDisabled() || event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ clickTarget = Element.as(event.getNativeEvent().getEventTarget());
+ mouseMoveCanceled = false;
+
+ if (weekGrid.getCalendar().isEventMoveAllowed() || clickTargetsResize()) {
+ moveRegistration = addMouseMoveHandler(this);
+ setFocus(true);
+ try {
+ startYrelative = (int) ((double) event.getRelativeY(caption) % slotHeight);
+ startXrelative = (event.getRelativeX(weekGrid.getElement()) - weekGrid.timebar
+ .getOffsetWidth()) % getDateCellWidth();
+ } catch (Exception e) {
+ GWT.log("Exception calculating relative start position", e);
+ }
+ mouseMoveStarted = false;
+ Style s = getElement().getStyle();
+ s.setZIndex(1000);
+ startDatetimeFrom = (Date) calendarEvent.getStartTime().clone();
+ startDatetimeTo = (Date) calendarEvent.getEndTime().clone();
+ Event.setCapture(getElement());
+ }
+
+ // make sure the right cursor is always displayed
+ if (clickTargetsResize()) {
+ addGlobalResizeStyle();
+ }
+
+ /*
+ * We need to stop the event propagation or else the WeekGrid range
+ * select will kick in
+ */
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ @Override
+ public void onMouseUp(MouseUpEvent event) {
+ if (mouseMoveCanceled) {
+ return;
+ }
+
+ Event.releaseCapture(getElement());
+ setFocus(false);
+ if (moveRegistration != null) {
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ }
+ int endX = event.getClientX();
+ int endY = event.getClientY();
+ int xDiff = startX - endX;
+ int yDiff = startY - endY;
+ startX = -1;
+ startY = -1;
+ mouseMoveStarted = false;
+ Style s = getElement().getStyle();
+ s.setZIndex(1);
+ if (!clickTargetsResize()) {
+ // check if mouse has moved over threshold of 3 pixels
+ boolean mouseMoved = (xDiff < -3 || xDiff > 3 || yDiff < -3 || yDiff > 3);
+
+ if (!weekGrid.getCalendar().isDisabledOrReadOnly() && mouseMoved) {
+ // Event Move:
+ // - calendar must be enabled
+ // - calendar must not be in read-only mode
+ weekGrid.eventMoved(this);
+ } else if (!weekGrid.getCalendar().isDisabled()) {
+ // Event Click:
+ // - calendar must be enabled (read-only is allowed)
+ EventTarget et = event.getNativeEvent().getEventTarget();
+ Element e = Element.as(et);
+ if (e == caption || e == eventContent
+ || e.getParentElement() == caption) {
+ if (weekGrid.getCalendar().getEventClickListener() != null) {
+ weekGrid.getCalendar().getEventClickListener()
+ .eventClick(calendarEvent);
+ }
+ }
+ }
+
+ } else { // click targeted resize bar
+ removeGlobalResizeStyle();
+ if (weekGrid.getCalendar().getEventResizeListener() != null) {
+ weekGrid.getCalendar().getEventResizeListener()
+ .eventResized(calendarEvent);
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onMouseMove(MouseMoveEvent event) {
+ if (startY < 0 && startX < 0) {
+ return;
+ }
+ if (isDisabled()) {
+ Event.releaseCapture(getElement());
+ mouseMoveStarted = false;
+ startY = -1;
+ startX = -1;
+ removeGlobalResizeStyle();
+ return;
+ }
+ int currentY = event.getClientY();
+ int currentX = event.getClientX();
+ int moveY = (currentY - startY);
+ int moveX = (currentX - startX);
+ if ((moveY < 5 && moveY > -6) && (moveX < 5 && moveX > -6)) {
+ return;
+ }
+ if (!mouseMoveStarted) {
+ setWidth(moveWidth);
+ getElement().getStyle().setMarginLeft(0, Unit.PX);
+ mouseMoveStarted = true;
+ }
+
+ HorizontalPanel parent = (HorizontalPanel) getParent().getParent();
+ int relativeX = event.getRelativeX(parent.getElement())
+ - weekGrid.timebar.getOffsetWidth();
+ int halfHourDiff = 0;
+ if (moveY > 0) {
+ halfHourDiff = (startYrelative + moveY) / slotHeight;
+ } else {
+ halfHourDiff = (moveY - startYrelative) / slotHeight;
+ }
+
+ int dateCellWidth = getDateCellWidth();
+ long dayDiff = 0;
+ if (moveX >= 0) {
+ dayDiff = (startXrelative + moveX) / dateCellWidth;
+ } else {
+ dayDiff = (moveX - (dateCellWidth - startXrelative))
+ / dateCellWidth;
+ }
+
+ int dayOffset = relativeX / dateCellWidth;
+
+ // sanity check for right side overflow
+ int dateCellCount = weekGrid.getDateCellCount();
+ if (dayOffset >= dateCellCount) {
+ dayOffset--;
+ dayDiff--;
+ }
+
+ int dayOffsetPx = calculateDateCellOffsetPx(dayOffset)
+ + weekGrid.timebar.getOffsetWidth();
+
+ GWT.log("DateCellWidth: " + dateCellWidth + " dayDiff: " + dayDiff
+ + " dayOffset: " + dayOffset + " dayOffsetPx: " + dayOffsetPx
+ + " startXrelative: " + startXrelative + " moveX: " + moveX);
+
+ if (relativeX < 0 || relativeX >= getDatesWidth()) {
+ return;
+ }
+
+ Style s = getElement().getStyle();
+
+ Date from = calendarEvent.getStartTime();
+ Date to = calendarEvent.getEndTime();
+ long duration = to.getTime() - from.getTime();
+
+ if (!clickTargetsResize()
+ && weekGrid.getCalendar().isEventMoveAllowed()) {
+ long daysMs = dayDiff * DateConstants.DAYINMILLIS;
+ from.setTime(startDatetimeFrom.getTime() + daysMs);
+ from.setTime(from.getTime()
+ + ((long) halfHourInMilliSeconds * halfHourDiff));
+ to.setTime((from.getTime() + duration));
+
+ calendarEvent.setStartTime(from);
+ calendarEvent.setEndTime(to);
+ calendarEvent.setStart(new Date(from.getTime()));
+ calendarEvent.setEnd(new Date(to.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (from.getHours() * 60) + from.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+ startFromMinutes = calculateStartFromMinute(startFromMinutes, from,
+ to, dayOffsetPx);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+ updatePosition(startFromMinutes, range);
+
+ s.setLeft(dayOffsetPx, Unit.PX);
+
+ if (weekGrid.getDateCellWidths() != null) {
+ s.setWidth(weekGrid.getDateCellWidths()[dayOffset], Unit.PX);
+ } else {
+ setWidth(moveWidth);
+ }
+
+ } else if (clickTarget == topResizeBar) {
+ long oldStartTime = startDatetimeFrom.getTime();
+ long newStartTime = oldStartTime
+ + ((long) halfHourInMilliSeconds * halfHourDiff);
+
+ if (!isTimeRangeTooSmall(newStartTime, startDatetimeTo.getTime())) {
+ newStartTime = startDatetimeTo.getTime() - getMinTimeRange();
+ }
+
+ from.setTime(newStartTime);
+
+ calendarEvent.setStartTime(from);
+ calendarEvent.setStart(new Date(from.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (from.getHours() * 60) + from.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+
+ updatePosition(startFromMinutes, range);
+
+ } else if (clickTarget == bottomResizeBar) {
+ long oldEndTime = startDatetimeTo.getTime();
+ long newEndTime = oldEndTime
+ + ((long) halfHourInMilliSeconds * halfHourDiff);
+
+ if (!isTimeRangeTooSmall(startDatetimeFrom.getTime(), newEndTime)) {
+ newEndTime = startDatetimeFrom.getTime() + getMinTimeRange();
+ }
+
+ to.setTime(newEndTime);
+
+ calendarEvent.setEndTime(to);
+ calendarEvent.setEnd(new Date(to.getTime()));
+
+ // Set new position for the event
+ long startFromMinutes = (startDatetimeFrom.getHours() * 60)
+ + startDatetimeFrom.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+ startFromMinutes = calculateStartFromMinute(startFromMinutes, from,
+ to, dayOffsetPx);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+ updatePosition(startFromMinutes, range);
+ }
+ }
+
+ private void cancelMouseMove() {
+ mouseMoveCanceled = true;
+
+ // reset and remove everything related to the event handling
+ Event.releaseCapture(getElement());
+ setFocus(false);
+
+ if (moveRegistration != null) {
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ }
+
+ mouseMoveStarted = false;
+ removeGlobalResizeStyle();
+
+ Style s = getElement().getStyle();
+ s.setZIndex(1);
+
+ // reset the position of the event
+ int dateCellWidth = getDateCellWidth();
+ int dayOffset = startXrelative / dateCellWidth;
+ s.clearLeft();
+
+ calendarEvent.setStartTime(startDatetimeFrom);
+ calendarEvent.setEndTime(startDatetimeTo);
+
+ long startFromMinutes = (startDatetimeFrom.getHours() * 60)
+ + startDatetimeFrom.getMinutes();
+ long range = calendarEvent.getRangeInMinutes();
+
+ startFromMinutes = calculateStartFromMinute(startFromMinutes,
+ startDatetimeFrom, startDatetimeTo, dayOffset);
+ if (startFromMinutes < 0) {
+ range += startFromMinutes;
+ }
+
+ updatePosition(startFromMinutes, range);
+
+ startY = -1;
+ startX = -1;
+
+ // to reset the event width
+ ((DateCell) getParent()).recalculateEventWidths();
+ }
+
+ // date methods are not deprecated in GWT
+ @SuppressWarnings("deprecation")
+ private long calculateStartFromMinute(long startFromMinutes, Date from,
+ Date to, int dayOffset) {
+ boolean eventStartAtDifferentDay = from.getDate() != to.getDate();
+ if (eventStartAtDifferentDay) {
+ long minutesOnPrevDay = (getTargetDateByCurrentPosition(dayOffset)
+ .getTime() - from.getTime()) / DateConstants.MINUTEINMILLIS;
+ startFromMinutes = -1 * minutesOnPrevDay;
+ }
+
+ return startFromMinutes;
+ }
+
+ /**
+ * @param dateOffset
+ * @return the amount of pixels the given date is from the left side
+ */
+ private int calculateDateCellOffsetPx(int dateOffset) {
+ int dateCellOffset = 0;
+ int[] dateWidths = weekGrid.getDateCellWidths();
+
+ if (dateWidths != null) {
+ for (int i = 0; i < dateOffset; i++) {
+ dateCellOffset += dateWidths[i] + 1;
+ }
+ } else {
+ dateCellOffset = dateOffset * weekGrid.getDateCellWidth();
+ }
+
+ return dateCellOffset;
+ }
+
+ /**
+ * Check if the given time range is too small for events
+ *
+ * @param start
+ * @param end
+ * @return
+ */
+ private boolean isTimeRangeTooSmall(long start, long end) {
+ return (end - start) >= getMinTimeRange();
+ }
+
+ /**
+ * @return the minimum amount of ms that an event must last when resized
+ */
+ private long getMinTimeRange() {
+ return DateConstants.MINUTEINMILLIS * 30;
+ }
+
+ /**
+ * Build the string for sending resize events to server
+ *
+ * @param event
+ * @return
+ */
+ private String buildResizeString(CalendarEvent event) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append(event.getIndex());
+ buffer.append(",");
+ buffer.append(DateUtil.formatClientSideDate(event.getStart()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event.getStartTime()));
+ buffer.append(",");
+ buffer.append(DateUtil.formatClientSideDate(event.getEnd()));
+ buffer.append("-");
+ buffer.append(DateUtil.formatClientSideTime(event.getEndTime()));
+
+ return buffer.toString();
+ }
+
+ private Date getTargetDateByCurrentPosition(int left) {
+ DateCell newParent = (DateCell) weekGrid.content
+ .getWidget((left / getDateCellWidth()) + 1);
+ Date targetDate = newParent.getDate();
+ return targetDate;
+ }
+
+ private int getDateCellWidth() {
+ return weekGrid.getDateCellWidth();
+ }
+
+ /* Returns total width of all date cells. */
+ private int getDatesWidth() {
+ if (weekGrid.width == -1) {
+ // Undefined width. Needs to be calculated by the known cell
+ // widths.
+ int count = weekGrid.content.getWidgetCount() - 1;
+ return count * getDateCellWidth();
+ }
+
+ return weekGrid.getInternalWidth();
+ }
+
+ /**
+ * @return true if the current mouse movement is resizing
+ */
+ private boolean clickTargetsResize() {
+ return weekGrid.getCalendar().isEventResizeAllowed()
+ && (clickTarget == topResizeBar || clickTarget == bottomResizeBar);
+ }
+
+ private void addGlobalResizeStyle() {
+ if (clickTarget == topResizeBar) {
+ weekGrid.getCalendar().addStyleDependentName("nresize");
+ } else if (clickTarget == bottomResizeBar) {
+ weekGrid.getCalendar().addStyleDependentName("sresize");
+ }
+ }
+
+ private void removeGlobalResizeStyle() {
+ weekGrid.getCalendar().removeStyleDependentName("nresize");
+ weekGrid.getCalendar().removeStyleDependentName("sresize");
+ }
+
+ public void setCalendarEvent(CalendarEvent calendarEvent) {
+ this.calendarEvent = calendarEvent;
+ }
+
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ @Override
+ public void onContextMenu(ContextMenuEvent event) {
+ if (dateCell.weekgrid.getCalendar().getMouseEventListener() != null) {
+ event.preventDefault();
+ event.stopPropagation();
+ dateCell.weekgrid.getCalendar().getMouseEventListener()
+ .contextMenu(event, this);
+ }
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ return eventIndex;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java
new file mode 100644
index 0000000000..79276eab7b
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellGroup.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class DateCellGroup {
+ private WeekGridMinuteTimeRange range;
+ private final List<Integer> items;
+
+ public DateCellGroup(Integer index) {
+ items = new ArrayList<Integer>();
+ items.add(index);
+ }
+
+ public WeekGridMinuteTimeRange getDateRange() {
+ return range;
+ }
+
+ public Date getStart() {
+ return range.getStart();
+ }
+
+ public Date getEnd() {
+ return range.getEnd();
+ }
+
+ public void setDateRange(WeekGridMinuteTimeRange range) {
+ this.range = range;
+ }
+
+ public List<Integer> getItems() {
+ return items;
+ }
+
+ public void add(Integer index) {
+ items.add(index);
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java
new file mode 100644
index 0000000000..84726327e2
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateUtil.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.i18n.client.DateTimeFormat;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * Utility class for {@link Date} operations
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class DateUtil {
+
+ /**
+ * Checks if dates are same day without checking datetimes.
+ *
+ * @param date1
+ * @param date2
+ * @return
+ */
+ @SuppressWarnings("deprecation")
+ public static boolean compareDate(Date date1, Date date2) {
+ if (date1.getDate() == date2.getDate()
+ && date1.getYear() == date2.getYear()
+ && date1.getMonth() == date2.getMonth()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @param date
+ * the date to format
+ *
+ * @return given Date as String, for communicating to server-side
+ */
+ public static String formatClientSideDate(Date date) {
+ DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat(DateConstants.CLIENT_DATE_FORMAT);
+ return dateformat_date.format(date);
+ }
+
+ /**
+ * @param date
+ * the date to format
+ * @return given Date as String, for communicating to server-side
+ */
+ public static String formatClientSideTime(Date date) {
+ DateTimeFormat dateformat_date = DateTimeFormat
+ .getFormat(DateConstants.CLIENT_TIME_FORMAT);
+ return dateformat_date.format(date);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
new file mode 100644
index 0000000000..6233e8111e
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Iterator;
+
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class DayToolbar extends HorizontalPanel implements ClickHandler {
+ private int width = 0;
+ protected static final int MARGINLEFT = 50;
+ protected static final int MARGINRIGHT = 20;
+ protected Button backLabel;
+ protected Button nextLabel;
+ private boolean verticalSized;
+ private boolean horizontalSized;
+ private VCalendar calendar;
+
+ public DayToolbar(VCalendar vcalendar) {
+ calendar = vcalendar;
+
+ setStylePrimaryName("v-calendar-header-week");
+ backLabel = new Button();
+ backLabel.setStylePrimaryName("v-calendar-back");
+ nextLabel = new Button();
+ nextLabel.addClickHandler(this);
+ nextLabel.setStylePrimaryName("v-calendar-next");
+ backLabel.addClickHandler(this);
+ setBorderWidth(0);
+ setSpacing(0);
+ }
+
+ public void setWidthPX(int width) {
+ this.width = (width - MARGINLEFT) - MARGINRIGHT;
+ // super.setWidth(this.width + "px");
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ updateCellWidths();
+ }
+
+ public void updateCellWidths() {
+ int count = getWidgetCount();
+ if (count > 0) {
+ setCellWidth(backLabel, MARGINLEFT + "px");
+ setCellWidth(nextLabel, MARGINRIGHT + "px");
+ setCellHorizontalAlignment(nextLabel, ALIGN_RIGHT);
+ int cellw = width / (count - 2);
+ int remain = width % (count - 2);
+ int cellw2 = cellw + 1;
+ if (cellw > 0) {
+ int[] cellWidths = VCalendar
+ .distributeSize(width, count - 2, 0);
+ for (int i = 1; i < count - 1; i++) {
+ Widget widget = getWidget(i);
+ // if (remain > 0) {
+ // setCellWidth(widget, cellw2 + "px");
+ // remain--;
+ // } else {
+ // setCellWidth(widget, cellw + "px");
+ // }
+ setCellWidth(widget, cellWidths[i - 1] + "px");
+ widget.setWidth(cellWidths[i - 1] + "px");
+ }
+ }
+ }
+ }
+
+ public void add(String dayName, final String date,
+ String localized_date_format, String extraClass) {
+ Label l = new Label(dayName + " " + localized_date_format);
+ l.setStylePrimaryName("v-calendar-header-day");
+
+ if (extraClass != null) {
+ l.addStyleDependentName(extraClass);
+ }
+
+ if (verticalSized) {
+ l.addStyleDependentName("Vsized");
+ }
+ if (horizontalSized) {
+ l.addStyleDependentName("Hsized");
+ }
+
+ l.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ if (calendar.getDateClickListener() != null) {
+ calendar.getDateClickListener().dateClick(date);
+ }
+ }
+ });
+
+ add(l);
+ }
+
+ public void addBackButton() {
+ if (!calendar.isBackwardNavigationEnabled()) {
+ nextLabel.getElement().getStyle().setHeight(0, Unit.PX);
+ }
+ add(backLabel);
+ }
+
+ public void addNextButton() {
+ if (!calendar.isForwardNavigationEnabled()) {
+ backLabel.getElement().getStyle().setHeight(0, Unit.PX);
+ }
+ add(nextLabel);
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ if (!calendar.isDisabledOrReadOnly()) {
+ if (event.getSource() == nextLabel) {
+ if (calendar.getForwardListener() != null) {
+ calendar.getForwardListener().forward();
+ }
+ } else if (event.getSource() == backLabel) {
+ if (calendar.getBackwardListener() != null) {
+ calendar.getBackwardListener().backward();
+ }
+ }
+ }
+ }
+
+ public void setVerticalSized(boolean sized) {
+ verticalSized = sized;
+ updateDayLabelSizedStyleNames();
+ }
+
+ public void setHorizontalSized(boolean sized) {
+ horizontalSized = sized;
+ updateDayLabelSizedStyleNames();
+ }
+
+ private void updateDayLabelSizedStyleNames() {
+ Iterator<Widget> it = iterator();
+ while (it.hasNext()) {
+ updateWidgetSizedStyleName(it.next());
+ }
+ }
+
+ private void updateWidgetSizedStyleName(Widget w) {
+ if (verticalSized) {
+ w.addStyleDependentName("Vsized");
+ } else {
+ w.removeStyleDependentName("VSized");
+ }
+ if (horizontalSized) {
+ w.addStyleDependentName("Hsized");
+ } else {
+ w.removeStyleDependentName("HSized");
+ }
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java
new file mode 100644
index 0000000000..6b42caec10
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableComplexPanel.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+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.ComplexPanel;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A ComplexPanel that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableComplexPanel extends ComplexPanel implements
+ HasFocusHandlers, HasBlurHandlers, HasKeyDownHandlers,
+ HasKeyPressHandlers, Focusable {
+
+ protected void makeFocusable() {
+ // 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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
+ */
+ @Override
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java
new file mode 100644
index 0000000000..b40f1c3652
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableGrid.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+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.Grid;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A Grid that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableGrid extends Grid implements HasFocusHandlers,
+ HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+ /**
+ * Constructor
+ */
+ public FocusableGrid() {
+ super();
+ makeFocusable();
+ }
+
+ public FocusableGrid(int rows, int columns) {
+ super(rows, columns);
+ makeFocusable();
+ }
+
+ protected void makeFocusable() {
+ // 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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
+ */
+ @Override
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java
new file mode 100644
index 0000000000..31d810608a
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/FocusableHTML.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+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.HTML;
+import com.google.gwt.user.client.ui.impl.FocusImpl;
+import com.vaadin.client.Focusable;
+
+/**
+ * A HTML widget that can be focused
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class FocusableHTML extends HTML implements HasFocusHandlers,
+ HasBlurHandlers, HasKeyDownHandlers, HasKeyPressHandlers, Focusable {
+
+ /**
+ * Constructor
+ */
+ public FocusableHTML() {
+ // 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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)
+ */
+ @Override
+ 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
+ */
+ @Override
+ public void focus() {
+ setFocus(true);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java b/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java
new file mode 100644
index 0000000000..5827068840
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/HasTooltipKey.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+/**
+ * For Calendar client-side internal use only.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public interface HasTooltipKey {
+ /**
+ * Gets the key associated for the Widget implementing this interface. This
+ * key is used for getting a tooltip title identified by the key
+ *
+ * @return the tooltip key
+ */
+ Object getTooltipKey();
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java
new file mode 100644
index 0000000000..b7f6ee7a3c
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthEventLabel.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * The label in a month cell
+ *
+ * @since 7.1
+ */
+public class MonthEventLabel extends HTML implements HasTooltipKey {
+
+ private static final String STYLENAME = "v-calendar-event";
+
+ private boolean timeSpecificEvent = false;
+ private Integer eventIndex;
+ private VCalendar calendar;
+ private String caption;
+ private Date time;
+
+ /**
+ * Default constructor
+ */
+ public MonthEventLabel() {
+ setStylePrimaryName(STYLENAME);
+ }
+
+ /**
+ * Set the time of the event label
+ *
+ * @param date
+ * The date object that specifies the time
+ */
+ public void setTime(Date date) {
+ time = date;
+ renderCaption();
+ }
+
+ /**
+ * Set the caption of the event label
+ *
+ * @param caption
+ * The caption string, can be HTML
+ */
+ public void setCaption(String caption) {
+ this.caption = caption;
+ renderCaption();
+ }
+
+ /**
+ * Renders the caption in the DIV element
+ */
+ private void renderCaption() {
+ StringBuilder html = new StringBuilder();
+ if (caption != null && time != null) {
+ html.append("<span class=\"" + STYLENAME + "-time\">");
+ html.append(calendar.getTimeFormat().format(time));
+ html.append("</span> ");
+ html.append(caption);
+ } else if (caption != null) {
+ html.append(caption);
+ } else if (time != null) {
+ html.append("<span class=\"" + STYLENAME + "-time\">");
+ html.append(calendar.getTimeFormat().format(time));
+ html.append("</span>");
+ }
+ super.setHTML(html.toString());
+ }
+
+ /**
+ * Set the (server side) index of the event
+ *
+ * @param index
+ * The integer index
+ */
+ public void setEventIndex(int index) {
+ eventIndex = index;
+ }
+
+ /**
+ * Set the Calendar instance this label belongs to
+ *
+ * @param calendar
+ * The calendar instance
+ */
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ /**
+ * Is the event bound to a specific time
+ *
+ * @return
+ */
+ public boolean isTimeSpecificEvent() {
+ return timeSpecificEvent;
+ }
+
+ /**
+ * Is the event bound to a specific time
+ *
+ * @param timeSpecificEvent
+ * True if the event is bound to a time, false if it is only
+ * bound to the day
+ */
+ public void setTimeSpecificEvent(boolean timeSpecificEvent) {
+ this.timeSpecificEvent = timeSpecificEvent;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.google.gwt.user.client.ui.HTML#setHTML(java.lang.String)
+ */
+ @Override
+ public void setHTML(String html) {
+ throw new UnsupportedOperationException(
+ "Use setCaption() and setTime() instead");
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ return eventIndex;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
new file mode 100644
index 0000000000..df9bc42d2a
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+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.shared.HandlerRegistration;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class MonthGrid extends FocusableGrid implements KeyDownHandler {
+
+ private SimpleDayCell selectionStart;
+ private SimpleDayCell selectionEnd;
+ private final VCalendar calendar;
+ private boolean rangeSelectDisabled;
+ private boolean disabled;
+ private boolean enabled = true;
+ private final HandlerRegistration keyDownHandler;
+
+ public MonthGrid(VCalendar parent, int rows, int columns) {
+ super(rows, columns);
+ calendar = parent;
+ setCellSpacing(0);
+ setCellPadding(0);
+ setStylePrimaryName("v-calendar-month");
+
+ keyDownHandler = addKeyDownHandler(this);
+ }
+
+ @Override
+ protected void onUnload() {
+ keyDownHandler.removeHandler();
+ super.onUnload();
+ }
+
+ public void setSelectionEnd(SimpleDayCell simpleDayCell) {
+ selectionEnd = simpleDayCell;
+ updateSelection();
+ }
+
+ public void setSelectionStart(SimpleDayCell simpleDayCell) {
+ if (!rangeSelectDisabled && isEnabled()) {
+ selectionStart = simpleDayCell;
+ setFocus(true);
+ }
+
+ }
+
+ private void updateSelection() {
+ if (selectionStart == null) {
+ return;
+ }
+ if (selectionStart != null && selectionEnd != null) {
+ Date startDate = selectionStart.getDate();
+ Date endDate = selectionEnd.getDate();
+ for (int row = 0; row < getRowCount(); row++) {
+ for (int cell = 0; cell < getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(row, cell);
+ if (sdc == null) {
+ return;
+ }
+ Date d = sdc.getDate();
+ if (startDate.compareTo(d) <= 0
+ && endDate.compareTo(d) >= 0) {
+ sdc.addStyleDependentName("selected");
+ } else if (startDate.compareTo(d) >= 0
+ && endDate.compareTo(d) <= 0) {
+ sdc.addStyleDependentName("selected");
+ } else {
+ sdc.removeStyleDependentName("selected");
+ }
+ }
+ }
+ }
+ }
+
+ public void setSelectionReady() {
+ if (selectionStart != null && selectionEnd != null) {
+ String value = "";
+ Date startDate = selectionStart.getDate();
+ Date endDate = selectionEnd.getDate();
+ if (startDate.compareTo(endDate) > 0) {
+ Date temp = startDate;
+ startDate = endDate;
+ endDate = temp;
+ }
+
+ if (calendar.getRangeSelectListener() != null) {
+ value = calendar.getDateFormat().format(startDate) + "TO"
+ + calendar.getDateFormat().format(endDate);
+ calendar.getRangeSelectListener().rangeSelected(value);
+ }
+ selectionStart = null;
+ selectionEnd = null;
+ setFocus(false);
+ }
+ }
+
+ public void cancelRangeSelection() {
+ if (selectionStart != null && selectionEnd != null) {
+ for (int row = 0; row < getRowCount(); row++) {
+ for (int cell = 0; cell < getCellCount(row); cell++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(row, cell);
+ if (sdc == null) {
+ return;
+ }
+ sdc.removeStyleDependentName("selected");
+ }
+ }
+ }
+ setFocus(false);
+ selectionStart = null;
+ }
+
+ public void updateCellSizes(int totalWidthPX, int totalHeightPX) {
+ boolean setHeight = totalHeightPX > 0;
+ boolean setWidth = totalWidthPX > 0;
+ int rows = getRowCount();
+ int cells = getCellCount(0);
+ int cellWidth = (totalWidthPX / cells) - 1;
+ int widthRemainder = totalWidthPX % cells;
+ // Division for cells might not be even. Distribute it evenly to
+ // will whole space.
+ int heightPX = totalHeightPX;
+ int cellHeight = heightPX / rows;
+ int heightRemainder = heightPX % rows;
+
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cells; j++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(i, j);
+
+ if (setWidth) {
+ if (widthRemainder > 0) {
+ sdc.setWidth(cellWidth + 1 + "px");
+ widthRemainder--;
+
+ } else {
+ sdc.setWidth(cellWidth + "px");
+ }
+ }
+
+ if (setHeight) {
+ if (heightRemainder > 0) {
+ sdc.setHeightPX(cellHeight + 1, true);
+
+ } else {
+ sdc.setHeightPX(cellHeight, true);
+ }
+ } else {
+ sdc.setHeightPX(-1, true);
+ }
+ }
+ heightRemainder--;
+ }
+ }
+
+ /**
+ * Disable or enable possibility to select ranges
+ */
+ public void setRangeSelect(boolean b) {
+ rangeSelectDisabled = !b;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ int keycode = event.getNativeKeyCode();
+ if (KeyCodes.KEY_ESCAPE == keycode && selectionStart != null) {
+ cancelRangeSelection();
+ }
+ }
+
+ public int getDayCellIndex(SimpleDayCell dayCell) {
+ int rows = getRowCount();
+ int cells = getCellCount(0);
+ for (int i = 0; i < rows; i++) {
+ for (int j = 0; j < cells; j++) {
+ SimpleDayCell sdc = (SimpleDayCell) getWidget(i, j);
+ if (dayCell == sdc) {
+ return i * cells + j;
+ }
+ }
+ }
+
+ return -1;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
new file mode 100644
index 0000000000..a2bd008d01
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java
@@ -0,0 +1,701 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+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.MouseDownEvent;
+import com.google.gwt.event.dom.client.MouseDownHandler;
+import com.google.gwt.event.dom.client.MouseMoveEvent;
+import com.google.gwt.event.dom.client.MouseMoveHandler;
+import com.google.gwt.event.dom.client.MouseOverEvent;
+import com.google.gwt.event.dom.client.MouseOverHandler;
+import com.google.gwt.event.dom.client.MouseUpEvent;
+import com.google.gwt.event.dom.client.MouseUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Event;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.ui.FocusableFlowPanel;
+import com.vaadin.client.ui.VCalendar;
+import com.vaadin.shared.ui.calendar.DateConstants;
+
+/**
+ * A class representing a single cell within the calendar in month-view
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class SimpleDayCell extends FocusableFlowPanel implements
+ MouseUpHandler, MouseDownHandler, MouseOverHandler, MouseMoveHandler {
+
+ private static int BOTTOMSPACERHEIGHT = -1;
+ private static int EVENTHEIGHT = -1;
+ private static final int BORDERPADDINGSIZE = 1;
+
+ private final VCalendar calendar;
+ private Date date;
+ private int intHeight;
+ private final HTML bottomspacer;
+ private final Label caption;
+ private final CalendarEvent[] events = new CalendarEvent[10];
+ private final int cell;
+ private final int row;
+ private boolean monthNameVisible;
+ private HandlerRegistration mouseUpRegistration;
+ private HandlerRegistration mouseDownRegistration;
+ private HandlerRegistration mouseOverRegistration;
+ private boolean monthEventMouseDown;
+ private boolean labelMouseDown;
+ private int eventCount = 0;
+
+ private int startX = -1;
+ private int startY = -1;
+ private int startYrelative;
+ private int startXrelative;
+ private Date startDateFrom;
+ private Date startDateTo;
+ private int prevDayDiff = 0;
+ private int prevWeekDiff = 0;
+ private HandlerRegistration moveRegistration;
+ private CalendarEvent moveEvent;
+ private Widget clickedWidget;
+ private HandlerRegistration bottomSpacerMouseDownHandler;
+ private boolean scrollable = false;
+ private boolean eventCanceled;
+ private MonthGrid monthGrid;
+ private HandlerRegistration keyDownHandler;
+
+ public SimpleDayCell(VCalendar calendar, int row, int cell) {
+ this.calendar = calendar;
+ this.row = row;
+ this.cell = cell;
+ setStylePrimaryName("v-calendar-month-day");
+ caption = new Label();
+ bottomspacer = new HTML();
+ bottomspacer.setStyleName("v-calendar-bottom-spacer-empty");
+ bottomspacer.setWidth(3 + "em");
+ caption.setStyleName("v-calendar-day-number");
+ add(caption);
+ add(bottomspacer);
+ caption.addMouseDownHandler(this);
+ caption.addMouseUpHandler(this);
+ }
+
+ @Override
+ public void onLoad() {
+ BOTTOMSPACERHEIGHT = bottomspacer.getOffsetHeight();
+ EVENTHEIGHT = BOTTOMSPACERHEIGHT;
+ }
+
+ public void setMonthGrid(MonthGrid monthGrid) {
+ this.monthGrid = monthGrid;
+ }
+
+ public MonthGrid getMonthGrid() {
+ return monthGrid;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setDate(Date date) {
+ int dateOfMonth = date.getDate();
+ if (monthNameVisible) {
+ caption.setText(dateOfMonth + " "
+ + calendar.getMonthNames()[date.getMonth()]);
+ } else {
+ caption.setText("" + dateOfMonth);
+ }
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void reDraw(boolean clear) {
+ setHeightPX(intHeight + BORDERPADDINGSIZE, clear);
+ }
+
+ /*
+ * Events and whole cell content are drawn by this method. By the
+ * clear-argument, you can choose to clear all old content. Notice that
+ * clearing will also remove all element's event handlers.
+ */
+ public void setHeightPX(int px, boolean clear) {
+ // measure from DOM if needed
+ if (px < 0) {
+ intHeight = getOffsetHeight() - BORDERPADDINGSIZE;
+ } else {
+ intHeight = px - BORDERPADDINGSIZE;
+ }
+
+ // Couldn't measure height or it ended up negative. Don't bother
+ // continuing
+ if (intHeight == -1) {
+ return;
+ }
+
+ if (clear) {
+ while (getWidgetCount() > 1) {
+ remove(1);
+ }
+ }
+
+ // How many events can be shown in UI
+ int slots = 0;
+ if (scrollable) {
+ for (int i = 0; i < events.length; i++) {
+ if (events[i] != null) {
+ slots = i + 1;
+ }
+ }
+ setHeight(intHeight + "px"); // Fixed height
+ } else {
+ // Dynamic height by the content
+ DOM.removeElementAttribute(getElement(), "height");
+ slots = (intHeight - caption.getOffsetHeight() - BOTTOMSPACERHEIGHT)
+ / EVENTHEIGHT;
+ if (slots > 10) {
+ slots = 10;
+ }
+ }
+
+ updateEvents(slots, clear);
+
+ }
+
+ public void updateEvents(int slots, boolean clear) {
+ int eventsAdded = 0;
+
+ for (int i = 0; i < slots; i++) {
+ CalendarEvent e = events[i];
+ if (e == null) {
+ // Empty slot
+ HTML slot = new HTML();
+ slot.setStyleName("v-calendar-spacer");
+ if (!clear) {
+ remove(i + 1);
+ insert(slot, i + 1);
+ } else {
+ add(slot);
+ }
+ } else {
+ // Event slot
+ eventsAdded++;
+ if (!clear) {
+ Widget w = getWidget(i + 1);
+ if (!(w instanceof MonthEventLabel)) {
+ remove(i + 1);
+ insert(createMonthEventLabel(e), i + 1);
+ }
+ } else {
+ add(createMonthEventLabel(e));
+ }
+ }
+ }
+
+ int remainingSpace = intHeight
+ - ((slots * EVENTHEIGHT) + BOTTOMSPACERHEIGHT + caption
+ .getOffsetHeight());
+ int newHeight = remainingSpace + BOTTOMSPACERHEIGHT;
+ if (newHeight < 0) {
+ newHeight = EVENTHEIGHT;
+ }
+ bottomspacer.setHeight(newHeight + "px");
+
+ if (clear) {
+ add(bottomspacer);
+ }
+
+ int more = eventCount - eventsAdded;
+ if (more > 0) {
+ if (bottomSpacerMouseDownHandler == null) {
+ bottomSpacerMouseDownHandler = bottomspacer
+ .addMouseDownHandler(this);
+ }
+ bottomspacer.setStyleName("v-calendar-bottom-spacer");
+ bottomspacer.setText("+ " + more);
+ } else {
+ if (!scrollable && bottomSpacerMouseDownHandler != null) {
+ bottomSpacerMouseDownHandler.removeHandler();
+ bottomSpacerMouseDownHandler = null;
+ }
+
+ if (scrollable) {
+ bottomspacer.setText("[ - ]");
+ } else {
+ bottomspacer.setStyleName("v-calendar-bottom-spacer-empty");
+ bottomspacer.setText("");
+ }
+ }
+ }
+
+ private MonthEventLabel createMonthEventLabel(CalendarEvent e) {
+ long rangeInMillis = e.getRangeInMilliseconds();
+ boolean timeEvent = rangeInMillis <= DateConstants.DAYINMILLIS
+ && !e.isAllDay();
+ Date fromDatetime = e.getStartTime();
+
+ // Create a new MonthEventLabel
+ MonthEventLabel eventDiv = new MonthEventLabel();
+ eventDiv.addStyleDependentName("month");
+ eventDiv.addMouseDownHandler(this);
+ eventDiv.addMouseUpHandler(this);
+ eventDiv.setCalendar(calendar);
+ eventDiv.setEventIndex(e.getIndex());
+
+ if (timeEvent) {
+ eventDiv.setTimeSpecificEvent(true);
+ if (e.getStyleName() != null) {
+ eventDiv.addStyleDependentName(e.getStyleName());
+ }
+ eventDiv.setCaption(e.getCaption());
+ eventDiv.setTime(fromDatetime);
+
+ } else {
+ eventDiv.setTimeSpecificEvent(false);
+ Date from = e.getStart();
+ Date to = e.getEnd();
+ if (e.getStyleName().length() > 0) {
+ eventDiv.addStyleName("month-event " + e.getStyleName());
+ } else {
+ eventDiv.addStyleName("month-event");
+ }
+ int fromCompareToDate = from.compareTo(date);
+ int toCompareToDate = to.compareTo(date);
+ eventDiv.addStyleDependentName("all-day");
+ if (fromCompareToDate == 0) {
+ eventDiv.addStyleDependentName("start");
+ eventDiv.setCaption(e.getCaption());
+
+ } else if (fromCompareToDate < 0 && cell == 0) {
+ eventDiv.addStyleDependentName("continued-from");
+ eventDiv.setCaption(e.getCaption());
+ }
+ if (toCompareToDate == 0) {
+ eventDiv.addStyleDependentName("end");
+ } else if (toCompareToDate > 0
+ && (cell + 1) == getMonthGrid().getCellCount(row)) {
+ eventDiv.addStyleDependentName("continued-to");
+ }
+ if (e.getStyleName() != null) {
+ eventDiv.addStyleDependentName(e.getStyleName() + "-all-day");
+ }
+ }
+
+ return eventDiv;
+ }
+
+ private void setUnlimitedCellHeight() {
+ scrollable = true;
+ addStyleDependentName("scrollable");
+ }
+
+ private void setLimitedCellHeight() {
+ scrollable = false;
+ removeStyleDependentName("scrollable");
+ }
+
+ public void addCalendarEvent(CalendarEvent e) {
+ eventCount++;
+ int slot = e.getSlotIndex();
+ if (slot == -1) {
+ for (int i = 0; i < events.length; i++) {
+ if (events[i] == null) {
+ events[i] = e;
+ e.setSlotIndex(i);
+ break;
+ }
+ }
+ } else {
+ events[slot] = e;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setMonthNameVisible(boolean b) {
+ monthNameVisible = b;
+ int dateOfMonth = date.getDate();
+ caption.setText(dateOfMonth + " "
+ + calendar.getMonthNames()[date.getMonth()]);
+ }
+
+ public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
+ return addDomHandler(handler, MouseMoveEvent.getType());
+ }
+
+ @Override
+ protected void onAttach() {
+ super.onAttach();
+ mouseUpRegistration = addDomHandler(this, MouseUpEvent.getType());
+ mouseDownRegistration = addDomHandler(this, MouseDownEvent.getType());
+ mouseOverRegistration = addDomHandler(this, MouseOverEvent.getType());
+ }
+
+ @Override
+ protected void onDetach() {
+ mouseUpRegistration.removeHandler();
+ mouseDownRegistration.removeHandler();
+ mouseOverRegistration.removeHandler();
+ super.onDetach();
+ }
+
+ @Override
+ public void onMouseUp(MouseUpEvent event) {
+ if (event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ Widget w = (Widget) event.getSource();
+ if (moveRegistration != null) {
+ Event.releaseCapture(getElement());
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ keyDownHandler.removeHandler();
+ keyDownHandler = null;
+ }
+
+ if (w == bottomspacer && monthEventMouseDown) {
+ GWT.log("Mouse up over bottomspacer");
+
+ } else if (clickedWidget instanceof MonthEventLabel
+ && monthEventMouseDown) {
+ MonthEventLabel mel = (MonthEventLabel) clickedWidget;
+
+ int endX = event.getClientX();
+ int endY = event.getClientY();
+ int xDiff = startX - endX;
+ int yDiff = startY - endY;
+ startX = -1;
+ startY = -1;
+ prevDayDiff = 0;
+ prevWeekDiff = 0;
+
+ if (!mel.isTimeSpecificEvent()
+ && (xDiff < -3 || xDiff > 3 || yDiff < -3 || yDiff > 3)) {
+ eventMoved(moveEvent);
+
+ } else if (calendar.getEventClickListener() != null) {
+ CalendarEvent e = getEventByWidget(mel);
+ calendar.getEventClickListener().eventClick(e);
+ }
+
+ moveEvent = null;
+ } else if (w == this) {
+ getMonthGrid().setSelectionReady();
+
+ } else if (w instanceof Label && labelMouseDown) {
+ String clickedDate = calendar.getDateFormat().format(date);
+ if (calendar.getDateClickListener() != null) {
+ calendar.getDateClickListener().dateClick(clickedDate);
+ }
+ }
+ monthEventMouseDown = false;
+ labelMouseDown = false;
+ clickedWidget = null;
+ }
+
+ @Override
+ public void onMouseDown(MouseDownEvent event) {
+ if (calendar.isDisabled()
+ || event.getNativeButton() != NativeEvent.BUTTON_LEFT) {
+ return;
+ }
+
+ Widget w = (Widget) event.getSource();
+ clickedWidget = w;
+
+ if (w instanceof MonthEventLabel) {
+ // event clicks should be allowed even when read-only
+ monthEventMouseDown = true;
+
+ if (w instanceof MonthEventLabel) {
+ startCalendarEventDrag(event, (MonthEventLabel) w);
+ }
+ } else if (!calendar.isReadOnly()) {
+ // these are not allowed when in read-only
+ if (w == bottomspacer) {
+ if (scrollable) {
+ setLimitedCellHeight();
+ } else {
+ setUnlimitedCellHeight();
+ }
+ reDraw(true);
+
+ } else if (w == this && !scrollable) {
+ MonthGrid grid = getMonthGrid();
+ if (grid.isEnabled() && calendar.isRangeSelectAllowed()) {
+ grid.setSelectionStart(this);
+ grid.setSelectionEnd(this);
+ }
+ } else if (w instanceof Label) {
+ labelMouseDown = true;
+ }
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ }
+
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ event.preventDefault();
+ getMonthGrid().setSelectionEnd(this);
+ }
+
+ @Override
+ public void onMouseMove(MouseMoveEvent event) {
+ if (clickedWidget instanceof MonthEventLabel && !monthEventMouseDown
+ || (startY < 0 && startX < 0)) {
+ return;
+ }
+
+ MonthEventLabel w = (MonthEventLabel) clickedWidget;
+
+ if (calendar.isDisabledOrReadOnly()) {
+ Event.releaseCapture(getElement());
+ monthEventMouseDown = false;
+ startY = -1;
+ startX = -1;
+ return;
+ }
+
+ int currentY = event.getClientY();
+ int currentX = event.getClientX();
+ int moveY = (currentY - startY);
+ int moveX = (currentX - startX);
+ if ((moveY < 5 && moveY > -6) && (moveX < 5 && moveX > -6)) {
+ return;
+ }
+
+ int dateCellWidth = getWidth();
+ int dateCellHeigth = getHeigth();
+
+ Element parent = getMonthGrid().getElement();
+ int relativeX = event.getRelativeX(parent);
+ int relativeY = event.getRelativeY(parent);
+ int weekDiff = 0;
+ if (moveY > 0) {
+ weekDiff = (startYrelative + moveY) / dateCellHeigth;
+ } else {
+ weekDiff = (moveY - (dateCellHeigth - startYrelative))
+ / dateCellHeigth;
+ }
+
+ int dayDiff = 0;
+ if (moveX >= 0) {
+ dayDiff = (startXrelative + moveX) / dateCellWidth;
+ } else {
+ dayDiff = (moveX - (dateCellWidth - startXrelative))
+ / dateCellWidth;
+ }
+ // Check boundaries
+ if (relativeY < 0
+ || relativeY >= (calendar.getMonthGrid().getRowCount() * dateCellHeigth)
+ || relativeX < 0
+ || relativeX >= (calendar.getMonthGrid().getColumnCount() * dateCellWidth)) {
+ return;
+ }
+
+ GWT.log("Event moving delta: " + weekDiff + " weeks " + dayDiff
+ + " days" + " (" + getCell() + "," + getRow() + ")");
+
+ CalendarEvent e = moveEvent;
+ if (e == null) {
+ e = getEventByWidget(w);
+ }
+
+ Date from = e.getStart();
+ Date to = e.getEnd();
+ long duration = to.getTime() - from.getTime();
+
+ long daysMs = dayDiff * DateConstants.DAYINMILLIS;
+ long weeksMs = weekDiff * DateConstants.WEEKINMILLIS;
+ from.setTime(startDateFrom.getTime() + weeksMs + daysMs);
+ to.setTime((from.getTime() + duration));
+ e.setStart(from);
+ e.setEnd(to);
+ e.setStartTime(new Date(from.getTime()));
+ e.setEndTime(new Date(to.getTime()));
+
+ updateDragPosition(w, dayDiff, weekDiff);
+ }
+
+ private void eventMoved(CalendarEvent e) {
+ calendar.updateEventToMonthGrid(e);
+ if (calendar.getEventMovedListener() != null) {
+ calendar.getEventMovedListener().eventMoved(e);
+ }
+ }
+
+ public void startCalendarEventDrag(MouseDownEvent event,
+ final MonthEventLabel w) {
+ if (w.isTimeSpecificEvent()) {
+ return;
+ }
+
+ moveRegistration = addMouseMoveHandler(this);
+ startX = event.getClientX();
+ startY = event.getClientY();
+ startYrelative = event.getRelativeY(w.getParent().getElement())
+ % getHeigth();
+ startXrelative = event.getRelativeX(w.getParent().getElement())
+ % getWidth();
+
+ CalendarEvent e = getEventByWidget(w);
+ startDateFrom = (Date) e.getStart().clone();
+ startDateTo = (Date) e.getEnd().clone();
+
+ Event.setCapture(getElement());
+ keyDownHandler = addKeyDownHandler(new KeyDownHandler() {
+
+ @Override
+ public void onKeyDown(KeyDownEvent event) {
+ if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
+ cancelEventDrag(w);
+ }
+ }
+
+ });
+
+ focus();
+
+ GWT.log("Start drag");
+ }
+
+ protected void cancelEventDrag(MonthEventLabel w) {
+ if (moveRegistration != null) {
+ // reset position
+ if (moveEvent == null) {
+ moveEvent = getEventByWidget(w);
+ }
+
+ moveEvent.setStart(startDateFrom);
+ moveEvent.setEnd(startDateTo);
+ calendar.updateEventToMonthGrid(moveEvent);
+
+ // reset drag-related properties
+ Event.releaseCapture(getElement());
+ moveRegistration.removeHandler();
+ moveRegistration = null;
+ keyDownHandler.removeHandler();
+ keyDownHandler = null;
+ setFocus(false);
+ monthEventMouseDown = false;
+ startY = -1;
+ startX = -1;
+ moveEvent = null;
+ labelMouseDown = false;
+ clickedWidget = null;
+ }
+ }
+
+ public void updateDragPosition(MonthEventLabel w, int dayDiff, int weekDiff) {
+ // Draw event to its new position only when position has changed
+ if (dayDiff == prevDayDiff && weekDiff == prevWeekDiff) {
+ return;
+ }
+
+ prevDayDiff = dayDiff;
+ prevWeekDiff = weekDiff;
+
+ if (moveEvent == null) {
+ moveEvent = getEventByWidget(w);
+ }
+
+ calendar.updateEventToMonthGrid(moveEvent);
+ }
+
+ public int getRow() {
+ return row;
+ }
+
+ public int getCell() {
+ return cell;
+ }
+
+ public int getHeigth() {
+ return intHeight + BORDERPADDINGSIZE;
+ }
+
+ public int getWidth() {
+ return getOffsetWidth() - BORDERPADDINGSIZE;
+ }
+
+ public void setToday(boolean today) {
+ if (today) {
+ addStyleDependentName("today");
+ } else {
+ removeStyleDependentName("today");
+ }
+ }
+
+ public boolean removeEvent(CalendarEvent targetEvent,
+ boolean reDrawImmediately) {
+ int slot = targetEvent.getSlotIndex();
+ if (slot < 0) {
+ return false;
+ }
+
+ CalendarEvent e = getCalendarEvent(slot);
+ if (targetEvent.equals(e)) {
+ events[slot] = null;
+ eventCount--;
+ if (reDrawImmediately) {
+ reDraw(moveEvent == null);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private CalendarEvent getEventByWidget(MonthEventLabel eventWidget) {
+ int index = getWidgetIndex(eventWidget);
+ return getCalendarEvent(index - 1);
+ }
+
+ public CalendarEvent getCalendarEvent(int i) {
+ return events[i];
+ }
+
+ public CalendarEvent[] getEvents() {
+ return events;
+ }
+
+ public int getEventCount() {
+ return eventCount;
+ }
+
+ public CalendarEvent getMoveEvent() {
+ return moveEvent;
+ }
+
+ public void addEmphasisStyle() {
+ addStyleDependentName("dragemphasis");
+ }
+
+ public void removeEmphasisStyle() {
+ removeStyleDependentName("dragemphasis");
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java
new file mode 100644
index 0000000000..fc75136b93
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayToolbar.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+public class SimpleDayToolbar extends HorizontalPanel {
+ private int width = 0;
+ private boolean isWidthUndefined = false;
+
+ public SimpleDayToolbar() {
+ setStylePrimaryName("v-calendar-header-month");
+ }
+
+ public void setDayNames(String[] dayNames) {
+ clear();
+ for (int i = 0; i < dayNames.length; i++) {
+ Label l = new Label(dayNames[i]);
+ l.setStylePrimaryName("v-calendar-header-day");
+ add(l);
+ }
+ updateCellWidth();
+ }
+
+ public void setWidthPX(int width) {
+ this.width = width;
+
+ setWidthUndefined(width == -1);
+
+ if (!isWidthUndefined()) {
+ super.setWidth(this.width + "px");
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ }
+ updateCellWidth();
+ }
+
+ private boolean isWidthUndefined() {
+ return isWidthUndefined;
+ }
+
+ private void setWidthUndefined(boolean isWidthUndefined) {
+ this.isWidthUndefined = isWidthUndefined;
+
+ if (isWidthUndefined) {
+ addStyleDependentName("Hsized");
+
+ } else {
+ removeStyleDependentName("Hsized");
+ }
+ }
+
+ private void updateCellWidth() {
+ int cellw = -1;
+ int widgetCount = getWidgetCount();
+ if (widgetCount <= 0) {
+ return;
+ }
+ if (isWidthUndefined()) {
+ Widget widget = getWidget(0);
+ String w = widget.getElement().getStyle().getWidth();
+ if (w.length() > 2) {
+ cellw = Integer.parseInt(w.substring(0, w.length() - 2));
+ }
+ } else {
+ cellw = width / getWidgetCount();
+ }
+ if (cellw > 0) {
+ for (int i = 0; i < getWidgetCount(); i++) {
+ Widget widget = getWidget(i);
+ setCellWidth(widget, cellw + "px");
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java
new file mode 100644
index 0000000000..59902811cd
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleWeekToolbar.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.FlexTable;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class SimpleWeekToolbar extends FlexTable implements ClickHandler {
+ private int height;
+ private VCalendar calendar;
+ private boolean isHeightUndefined;
+
+ public SimpleWeekToolbar(VCalendar parent) {
+ calendar = parent;
+ setCellSpacing(0);
+ setCellPadding(0);
+ setStyleName("v-calendar-week-numbers");
+ }
+
+ public void addWeek(int week, int year) {
+ WeekLabel l = new WeekLabel(week + "", week, year);
+ l.addClickHandler(this);
+ int rowCount = getRowCount();
+ insertRow(rowCount);
+ setWidget(rowCount, 0, l);
+ updateCellHeights();
+ }
+
+ public void updateCellHeights() {
+ if (!isHeightUndefined()) {
+ int rowCount = getRowCount();
+ if (rowCount == 0) {
+ return;
+ }
+ int cellheight = (height / rowCount) - 1;
+ int remainder = height % rowCount;
+ if (cellheight < 0) {
+ cellheight = 0;
+ }
+ for (int i = 0; i < rowCount; i++) {
+ if (remainder > 0) {
+ getWidget(i, 0).setHeight(cellheight + 1 + "px");
+ } else {
+ getWidget(i, 0).setHeight(cellheight + "px");
+ }
+ getWidget(i, 0).getElement().getStyle()
+ .setProperty("lineHeight", cellheight + "px");
+ remainder--;
+ }
+ } else {
+ for (int i = 0; i < getRowCount(); i++) {
+ getWidget(i, 0).setHeight("");
+ getWidget(i, 0).getElement().getStyle()
+ .setProperty("lineHeight", "");
+ }
+ }
+ }
+
+ public void setHeightPX(int intHeight) {
+ setHeightUndefined(intHeight == -1);
+ height = intHeight;
+ updateCellHeights();
+ }
+
+ public boolean isHeightUndefined() {
+ return isHeightUndefined;
+ }
+
+ public void setHeightUndefined(boolean isHeightUndefined) {
+ this.isHeightUndefined = isHeightUndefined;
+
+ if (isHeightUndefined) {
+ addStyleDependentName("Vsized");
+
+ } else {
+ removeStyleDependentName("Vsized");
+ }
+ }
+
+ @Override
+ public void onClick(ClickEvent event) {
+ WeekLabel wl = (WeekLabel) event.getSource();
+ if (calendar.getWeekClickListener() != null) {
+ calendar.getWeekClickListener().weekClick(
+ wl.getYear() + "w" + wl.getWeek());
+ }
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java
new file mode 100644
index 0000000000..450ea29549
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGrid.java
@@ -0,0 +1,678 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Arrays;
+import java.util.Date;
+
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.event.dom.client.ScrollEvent;
+import com.google.gwt.event.dom.client.ScrollHandler;
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.ui.HTML;
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.google.gwt.user.client.ui.Panel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.Widget;
+import com.vaadin.client.DateTimeService;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class WeekGrid extends SimplePanel {
+
+ int width = 0;
+ private int height = 0;
+ final HorizontalPanel content;
+ private VCalendar calendar;
+ private boolean disabled;
+ final Timebar timebar;
+ private Panel wrapper;
+ private boolean verticalScrollEnabled;
+ private boolean horizontalScrollEnabled;
+ private int[] cellHeights;
+ private final int slotInMinutes = 30;
+ private int dateCellBorder;
+ private DateCell dateCellOfToday;
+ private int[] cellWidths;
+ private int firstHour;
+ private int lastHour;
+
+ public WeekGrid(VCalendar parent, boolean format24h) {
+ setCalendar(parent);
+ content = new HorizontalPanel();
+ timebar = new Timebar(format24h);
+ content.add(timebar);
+
+ wrapper = new SimplePanel();
+ wrapper.setStylePrimaryName("v-calendar-week-wrapper");
+ wrapper.add(content);
+
+ setWidget(wrapper);
+ }
+
+ private void setVerticalScroll(boolean isVerticalScrollEnabled) {
+ if (isVerticalScrollEnabled && !(isVerticalScrollable())) {
+ verticalScrollEnabled = true;
+ horizontalScrollEnabled = false;
+ wrapper.remove(content);
+
+ final ScrollPanel scrollPanel = new ScrollPanel();
+ scrollPanel.setStylePrimaryName("v-calendar-week-wrapper");
+ scrollPanel.setWidget(content);
+
+ scrollPanel.addScrollHandler(new ScrollHandler() {
+ @Override
+ public void onScroll(ScrollEvent event) {
+ if (calendar.getScrollListener() != null) {
+ calendar.getScrollListener().scroll(
+ scrollPanel.getVerticalScrollPosition());
+ }
+ }
+ });
+
+ setWidget(scrollPanel);
+ wrapper = scrollPanel;
+
+ } else if (!isVerticalScrollEnabled && (isVerticalScrollable())) {
+ verticalScrollEnabled = false;
+ horizontalScrollEnabled = false;
+ wrapper.remove(content);
+
+ SimplePanel simplePanel = new SimplePanel();
+ simplePanel.setStylePrimaryName("v-calendar-week-wrapper");
+ simplePanel.setWidget(content);
+
+ setWidget(simplePanel);
+ wrapper = simplePanel;
+ }
+ }
+
+ public void setVerticalScrollPosition(int verticalScrollPosition) {
+ if (isVerticalScrollable()) {
+ ((ScrollPanel) wrapper)
+ .setVerticalScrollPosition(verticalScrollPosition);
+ }
+ }
+
+ public int getInternalWidth() {
+ return width;
+ }
+
+ public void addDate(Date d) {
+ final DateCell dc = new DateCell(this, d);
+ dc.setDisabled(isDisabled());
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ dc.setVerticalSized(isVerticalScrollable());
+ content.add(dc);
+ }
+
+ /**
+ * @param dateCell
+ * @return get the index of the given date cell in this week, starting from
+ * 0
+ */
+ public int getDateCellIndex(DateCell dateCell) {
+ return content.getWidgetIndex(dateCell) - 1;
+ }
+
+ /**
+ * @return get the slot border in pixels
+ */
+ public int getDateSlotBorder() {
+ return ((DateCell) content.getWidget(1)).getSlotBorder();
+ }
+
+ private boolean isVerticalScrollable() {
+ return verticalScrollEnabled;
+ }
+
+ private boolean isHorizontalScrollable() {
+ return horizontalScrollEnabled;
+ }
+
+ public void setWidthPX(int width) {
+ if (isHorizontalScrollable()) {
+ updateCellWidths();
+
+ // Otherwise the scroll wrapper is somehow too narrow = horizontal
+ // scroll
+ wrapper.setWidth(content.getOffsetWidth()
+ + Util.getNativeScrollbarSize() + "px");
+
+ this.width = content.getOffsetWidth() - timebar.getOffsetWidth();
+
+ } else {
+ this.width = (width == -1) ? width : width
+ - timebar.getOffsetWidth();
+
+ if (isVerticalScrollable() && width != -1) {
+ this.width = this.width - Util.getNativeScrollbarSize();
+ }
+ updateCellWidths();
+ }
+ }
+
+ public void setHeightPX(int intHeight) {
+ height = intHeight;
+
+ setVerticalScroll(height <= -1);
+
+ // if not scrollable, use any height given
+ if (!isVerticalScrollable() && height > 0) {
+
+ content.setHeight(height + "px");
+ setHeight(height + "px");
+ wrapper.setHeight(height + "px");
+ wrapper.removeStyleDependentName("Vsized");
+ updateCellHeights();
+ timebar.setCellHeights(cellHeights);
+ timebar.setHeightPX(height);
+
+ } else if (isVerticalScrollable()) {
+ updateCellHeights();
+ wrapper.addStyleDependentName("Vsized");
+ timebar.setCellHeights(cellHeights);
+ timebar.setHeightPX(height);
+ }
+ }
+
+ public void clearDates() {
+ while (content.getWidgetCount() > 1) {
+ content.remove(1);
+ }
+
+ dateCellOfToday = null;
+ }
+
+ /**
+ * @return true if this weekgrid contains a date that is today
+ */
+ public boolean hasToday() {
+ return dateCellOfToday != null;
+ }
+
+ public void updateCellWidths() {
+ if (!isHorizontalScrollable() && width != -1) {
+ int count = content.getWidgetCount();
+ int datesWidth = width;
+ if (datesWidth > 0 && count > 1) {
+ cellWidths = VCalendar
+ .distributeSize(datesWidth, count - 1, -1);
+
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ dc.setWidthPX(cellWidths[i - 1]);
+ if (dc.isToday()) {
+ dc.setTimeBarWidth(getOffsetWidth());
+ }
+ }
+ }
+
+ } else {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHorizontalSized(isHorizontalScrollable() || width < 0);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return an int-array containing the widths of the cells (days)
+ */
+ public int[] getDateCellWidths() {
+ return cellWidths;
+ }
+
+ public void updateCellHeights() {
+ if (!isVerticalScrollable()) {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ DateCell first = (DateCell) content.getWidget(1);
+ dateCellBorder = first.getSlotBorder();
+ cellHeights = VCalendar.distributeSize(height,
+ first.getNumberOfSlots(), -dateCellBorder);
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setHeightPX(height, cellHeights);
+ }
+ }
+
+ } else {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ DateCell first = (DateCell) content.getWidget(1);
+ dateCellBorder = first.getSlotBorder();
+ int dateHeight = (first.getOffsetHeight() / first
+ .getNumberOfSlots()) - dateCellBorder;
+ cellHeights = new int[48];
+ Arrays.fill(cellHeights, dateHeight);
+
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ dc.setVerticalSized(isVerticalScrollable());
+ }
+ }
+ }
+ }
+
+ public void addEvent(CalendarEvent e) {
+ int dateCount = content.getWidgetCount();
+ Date from = e.getStart();
+ Date toTime = e.getEndTime();
+ for (int i = 1; i < dateCount; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(from);
+ int comp2 = dcDate.compareTo(toTime);
+ if (comp >= 0
+ && comp2 < 0
+ || (comp == 0 && comp2 == 0 && VCalendar
+ .isZeroLengthMidnightEvent(e))) {
+ // Same event may be over two DateCells if event's date
+ // range floats over one day. It can't float over two days,
+ // because event which range is over 24 hours, will be handled
+ // as a "fullDay" event.
+ dc.addEvent(dcDate, e);
+ }
+ }
+ }
+
+ public int getPixelLengthFor(int startFromMinutes, int durationInMinutes) {
+ int pixelLength = 0;
+ int currentSlot = 0;
+
+ int firstHourInMinutes = firstHour * 60;
+
+ if (firstHourInMinutes > startFromMinutes) {
+ startFromMinutes = 0;
+ } else {
+ startFromMinutes -= firstHourInMinutes;
+ }
+
+ // calculate full slots to event
+ int slotsTillEvent = startFromMinutes / slotInMinutes;
+ int startOverFlowTime = slotInMinutes
+ - (startFromMinutes % slotInMinutes);
+ if (startOverFlowTime == slotInMinutes) {
+ startOverFlowTime = 0;
+ currentSlot = slotsTillEvent;
+ } else {
+ currentSlot = slotsTillEvent + 1;
+ }
+
+ int durationInSlots = 0;
+ int endOverFlowTime = 0;
+
+ if (startOverFlowTime > 0) {
+ durationInSlots = (durationInMinutes - startOverFlowTime)
+ / slotInMinutes;
+ endOverFlowTime = (durationInMinutes - startOverFlowTime)
+ % slotInMinutes;
+
+ } else {
+ durationInSlots = durationInMinutes / slotInMinutes;
+ endOverFlowTime = durationInMinutes % slotInMinutes;
+ }
+
+ // calculate slot overflow at start
+ if (startOverFlowTime > 0 && currentSlot < cellHeights.length) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * startOverFlowTime);
+ }
+
+ // calculate length in full slots
+ int lastFullSlot = currentSlot + durationInSlots;
+ for (; currentSlot < lastFullSlot && currentSlot < cellHeights.length; currentSlot++) {
+ pixelLength += cellHeights[currentSlot] + dateCellBorder;
+ }
+
+ // calculate overflow at end
+ if (endOverFlowTime > 0 && currentSlot < cellHeights.length) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
+ }
+
+ // reduce possible underflow at end
+ if (endOverFlowTime < 0) {
+ int lastSlotHeight = cellHeights[currentSlot] + dateCellBorder;
+ pixelLength += (int) (((double) lastSlotHeight / (double) slotInMinutes) * endOverFlowTime);
+ }
+
+ return pixelLength;
+ }
+
+ public int getPixelTopFor(int startFromMinutes) {
+ int pixelsToTop = 0;
+ int slotIndex = 0;
+
+ int firstHourInMinutes = firstHour * 60;
+
+ if (firstHourInMinutes > startFromMinutes) {
+ startFromMinutes = 0;
+ } else {
+ startFromMinutes -= firstHourInMinutes;
+ }
+
+ // calculate full slots to event
+ int slotsTillEvent = startFromMinutes / slotInMinutes;
+ int overFlowTime = startFromMinutes % slotInMinutes;
+ if (slotsTillEvent > 0) {
+ for (slotIndex = 0; slotIndex < slotsTillEvent; slotIndex++) {
+ pixelsToTop += cellHeights[slotIndex] + dateCellBorder;
+ }
+ }
+
+ // calculate lengths less than one slot
+ if (overFlowTime > 0) {
+ int lastSlotHeight = cellHeights[slotIndex] + dateCellBorder;
+ pixelsToTop += ((double) lastSlotHeight / (double) slotInMinutes)
+ * overFlowTime;
+ }
+
+ return pixelsToTop;
+ }
+
+ public void eventMoved(DateCellDayEvent dayEvent) {
+ Style s = dayEvent.getElement().getStyle();
+ int left = Integer.parseInt(s.getLeft().substring(0,
+ s.getLeft().length() - 2));
+ DateCell previousParent = (DateCell) dayEvent.getParent();
+ DateCell newParent = (DateCell) content
+ .getWidget((left / getDateCellWidth()) + 1);
+ CalendarEvent se = dayEvent.getCalendarEvent();
+ previousParent.removeEvent(dayEvent);
+ newParent.addEvent(dayEvent);
+ if (!previousParent.equals(newParent)) {
+ previousParent.recalculateEventWidths();
+ }
+ newParent.recalculateEventWidths();
+ if (calendar.getEventMovedListener() != null) {
+ calendar.getEventMovedListener().eventMoved(se);
+ }
+ }
+
+ public void setToday(Date todayDate, Date todayTimestamp) {
+ int count = content.getWidgetCount();
+ if (count > 1) {
+ for (int i = 1; i < count; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ if (dc.getDate().getTime() == todayDate.getTime()) {
+ if (isVerticalScrollable()) {
+ dc.setToday(todayTimestamp, -1);
+ } else {
+ dc.setToday(todayTimestamp, getOffsetWidth());
+ }
+ }
+ dateCellOfToday = dc;
+ }
+ }
+ }
+
+ public DateCell getDateCellOfToday() {
+ return dateCellOfToday;
+ }
+
+ public void setDisabled(boolean disabled) {
+ this.disabled = disabled;
+ }
+
+ public boolean isDisabled() {
+ return disabled;
+ }
+
+ public Timebar getTimeBar() {
+ return timebar;
+ }
+
+ public void setDateColor(Date when, Date to, String styleName) {
+ int dateCount = content.getWidgetCount();
+ for (int i = 1; i < dateCount; i++) {
+ DateCell dc = (DateCell) content.getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(when);
+ int comp2 = dcDate.compareTo(to);
+ if (comp >= 0 && comp2 <= 0) {
+ dc.setDateColor(styleName);
+ }
+ }
+ }
+
+ /**
+ * @param calendar
+ * the calendar to set
+ */
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ /**
+ * @return the calendar
+ */
+ public VCalendar getCalendar() {
+ return calendar;
+ }
+
+ /**
+ * Get width of the single date cell
+ *
+ * @return Date cell width
+ */
+ public int getDateCellWidth() {
+ int count = content.getWidgetCount() - 1;
+ int cellWidth = -1;
+ if (count <= 0) {
+ return cellWidth;
+ }
+
+ if (width == -1) {
+ Widget firstWidget = content.getWidget(1);
+ cellWidth = firstWidget.getElement().getOffsetWidth();
+ } else {
+ cellWidth = getInternalWidth() / count;
+ }
+ return cellWidth;
+ }
+
+ /**
+ * @return the number of day cells in this week
+ */
+ public int getDateCellCount() {
+ return content.getWidgetCount() - 1;
+ }
+
+ public void setFirstHour(int firstHour) {
+ this.firstHour = firstHour;
+ timebar.setFirstHour(firstHour);
+ }
+
+ public void setLastHour(int lastHour) {
+ this.lastHour = lastHour;
+ timebar.setLastHour(lastHour);
+ }
+
+ public int getFirstHour() {
+ return firstHour;
+ }
+
+ public int getLastHour() {
+ return lastHour;
+ }
+
+ public static class Timebar extends HTML {
+
+ private static final int[] timesFor12h = { 12, 1, 2, 3, 4, 5, 6, 7, 8,
+ 9, 10, 11 };
+
+ private int height;
+
+ private final int verticalPadding = 7; // FIXME measure this from DOM
+
+ private int[] slotCellHeights;
+
+ private int firstHour;
+
+ private int lastHour;
+
+ public Timebar(boolean format24h) {
+ createTimeBar(format24h);
+ }
+
+ public void setLastHour(int lastHour) {
+ this.lastHour = lastHour;
+ }
+
+ public void setFirstHour(int firstHour) {
+ this.firstHour = firstHour;
+
+ }
+
+ public void setCellHeights(int[] cellHeights) {
+ slotCellHeights = cellHeights;
+ }
+
+ private void createTimeBar(boolean format24h) {
+ setStylePrimaryName("v-calendar-times");
+
+ // Fist "time" is empty
+ Element e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerText("");
+ getElement().appendChild(e);
+
+ DateTimeService dts = new DateTimeService();
+
+ if (format24h) {
+ for (int i = firstHour + 1; i <= lastHour; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ String delimiter = dts.getClockDelimeter();
+ e.setInnerHTML("<span>" + i + "</span>" + delimiter + "00");
+ getElement().appendChild(e);
+ }
+ } else {
+ // FIXME Use dts.getAmPmStrings(); and make sure that
+ // DateTimeService has a some Locale set.
+ String[] ampm = new String[] { "AM", "PM" };
+
+ int amStop = (lastHour < 11) ? lastHour : 11;
+ int pmStart = (firstHour > 11) ? firstHour % 11 : 0;
+
+ if (firstHour < 12) {
+ for (int i = firstHour + 1; i <= amStop; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerHTML("<span>" + timesFor12h[i] + "</span>"
+ + " " + ampm[0]);
+ getElement().appendChild(e);
+ }
+ }
+
+ if (lastHour > 11) {
+ for (int i = pmStart; i < lastHour - 11; i++) {
+ e = DOM.createDiv();
+ setStyleName(e, "v-calendar-time");
+ e.setInnerHTML("<span>" + timesFor12h[i] + "</span>"
+ + " " + ampm[1]);
+ getElement().appendChild(e);
+ }
+ }
+ }
+ }
+
+ public void updateTimeBar(boolean format24h) {
+ clear();
+ createTimeBar(format24h);
+ }
+
+ private void clear() {
+ while (getElement().getChildCount() > 0) {
+ getElement().removeChild(getElement().getChild(0));
+ }
+ }
+
+ public void setHeightPX(int pixelHeight) {
+ height = pixelHeight;
+
+ if (pixelHeight > -1) {
+ // as the negative margins on children pulls the whole element
+ // upwards, we must compensate. otherwise the element would be
+ // too short
+ super.setHeight((height + verticalPadding) + "px");
+ removeStyleDependentName("Vsized");
+ updateChildHeights();
+
+ } else {
+ addStyleDependentName("Vsized");
+ updateChildHeights();
+ }
+ }
+
+ private void updateChildHeights() {
+ int childCount = getElement().getChildCount();
+
+ if (height != -1) {
+
+ // 23 hours + first is empty
+ // we try to adjust the height of time labels to the distributed
+ // heights of the time slots
+ int hoursPerDay = lastHour - firstHour + 1;
+
+ int slotsPerHour = slotCellHeights.length / hoursPerDay;
+ int[] cellHeights = new int[slotCellHeights.length
+ / slotsPerHour];
+
+ int slotHeightPosition = 0;
+ for (int i = 0; i < cellHeights.length; i++) {
+ for (int j = slotHeightPosition; j < slotHeightPosition
+ + slotsPerHour; j++) {
+ cellHeights[i] += slotCellHeights[j] + 1;
+ // 1px more for borders
+ // FIXME measure from DOM
+ }
+ slotHeightPosition += slotsPerHour;
+ }
+
+ for (int i = 0; i < childCount; i++) {
+ Element e = (Element) getElement().getChild(i);
+ e.getStyle().setHeight(cellHeights[i], Unit.PX);
+ }
+
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ Element e = (Element) getElement().getChild(i);
+ e.getStyle().setProperty("height", "");
+ }
+ }
+ }
+ }
+
+ public VCalendar getParentCalendar() {
+ return calendar;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java
new file mode 100644
index 0000000000..e634735be7
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekGridMinuteTimeRange.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+/**
+ * Internally used by the calendar
+ *
+ * @since 7.1
+ */
+public class WeekGridMinuteTimeRange {
+ private final Date start;
+ private final Date end;
+
+ /**
+ * Creates a Date time range between start and end date. Drops seconds from
+ * the range.
+ *
+ * @param start
+ * Start time of the range
+ * @param end
+ * End time of the range
+ * @param clearSeconds
+ * Boolean Indicates, if seconds should be dropped from the range
+ * start and end
+ */
+ public WeekGridMinuteTimeRange(Date start, Date end) {
+ this.start = new Date(start.getTime());
+ this.end = new Date(end.getTime());
+ this.start.setSeconds(0);
+ this.end.setSeconds(0);
+ }
+
+ public Date getStart() {
+ return start;
+ }
+
+ public Date getEnd() {
+ return end;
+ }
+
+ public static boolean doesOverlap(WeekGridMinuteTimeRange a,
+ WeekGridMinuteTimeRange b) {
+ boolean overlaps = a.getStart().compareTo(b.getEnd()) < 0
+ && a.getEnd().compareTo(b.getStart()) > 0;
+ return overlaps;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java
new file mode 100644
index 0000000000..bde8675435
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeekLabel.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import com.google.gwt.user.client.ui.Label;
+
+/**
+ * A label in the {@link SimpleWeekToolbar}
+ *
+ * @since 7.1
+ */
+public class WeekLabel extends Label {
+ private int week;
+ private int year;
+
+ public WeekLabel(String string, int week2, int year2) {
+ super(string);
+ setStylePrimaryName("v-calendar-week-number");
+ week = week2;
+ year = year2;
+ }
+
+ public int getWeek() {
+ return week;
+ }
+
+ public void setWeek(int week) {
+ this.week = week;
+ }
+
+ public int getYear() {
+ return year;
+ }
+
+ public void setYear(int year) {
+ this.year = year;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java
new file mode 100644
index 0000000000..f7c5c0dac4
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEvents.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+import java.util.List;
+
+import com.google.gwt.user.client.ui.HorizontalPanel;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public class WeeklyLongEvents extends HorizontalPanel implements HasTooltipKey {
+
+ public static final int EVENT_HEIGTH = 15;
+
+ public static final int EVENT_MARGIN = 1;
+
+ private int rowCount = 0;
+
+ private VCalendar calendar;
+
+ private boolean undefinedWidth;
+
+ public WeeklyLongEvents(VCalendar calendar) {
+ setStylePrimaryName("v-calendar-weekly-longevents");
+ this.calendar = calendar;
+ }
+
+ public void addDate(Date d) {
+ DateCellContainer dcc = new DateCellContainer();
+ dcc.setDate(d);
+ dcc.setCalendar(calendar);
+ add(dcc);
+ }
+
+ public void setWidthPX(int width) {
+ if (getWidgetCount() == 0) {
+ return;
+ }
+ undefinedWidth = (width < 0);
+
+ updateCellWidths();
+ }
+
+ public void addEvents(List<CalendarEvent> events) {
+ for (CalendarEvent e : events) {
+ addEvent(e);
+ }
+ }
+
+ public void addEvent(CalendarEvent calendarEvent) {
+ updateEventSlot(calendarEvent);
+
+ int dateCount = getWidgetCount();
+ Date from = calendarEvent.getStart();
+ Date to = calendarEvent.getEnd();
+ boolean started = false;
+ for (int i = 0; i < dateCount; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(from);
+ int comp2 = dcDate.compareTo(to);
+ WeeklyLongEventsDateCell eventLabel = dc.getDateCell(calendarEvent
+ .getSlotIndex());
+ eventLabel.setStylePrimaryName("v-calendar-event");
+ if (comp >= 0 && comp2 <= 0) {
+ eventLabel.setEvent(calendarEvent);
+ eventLabel.setCalendar(calendar);
+
+ eventLabel.addStyleDependentName("all-day");
+ if (comp == 0) {
+ eventLabel.addStyleDependentName("start");
+ }
+ if (comp2 == 0) {
+ eventLabel.addStyleDependentName("end");
+ }
+ if (!started && comp > 0 && comp2 <= 0) {
+ eventLabel.addStyleDependentName("continued-from");
+ } else if (i == (dateCount - 1)) {
+ eventLabel.addStyleDependentName("continued-to");
+ }
+ final String extraStyle = calendarEvent.getStyleName();
+ if (extraStyle != null && extraStyle.length() > 0) {
+ eventLabel.addStyleDependentName(extraStyle + "-all-day");
+ }
+ if (!started) {
+ eventLabel.setText(calendarEvent.getCaption());
+ started = true;
+ }
+ }
+ }
+ }
+
+ private void updateEventSlot(CalendarEvent e) {
+ boolean foundFreeSlot = false;
+ int slot = 0;
+ while (!foundFreeSlot) {
+ if (isSlotFree(slot, e.getStart(), e.getEnd())) {
+ e.setSlotIndex(slot);
+ foundFreeSlot = true;
+
+ } else {
+ slot++;
+ }
+ }
+ }
+
+ private boolean isSlotFree(int slot, Date start, Date end) {
+ int dateCount = getWidgetCount();
+
+ // Go over all dates this week
+ for (int i = 0; i < dateCount; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+ Date dcDate = dc.getDate();
+ int comp = dcDate.compareTo(start);
+ int comp2 = dcDate.compareTo(end);
+
+ // check if the date is in the range we need
+ if (comp >= 0 && comp2 <= 0) {
+
+ // check if the slot is taken
+ if (dc.hasEvent(slot)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public int getRowCount() {
+ return rowCount;
+ }
+
+ public void updateCellWidths() {
+ int cells = getWidgetCount();
+ if (cells <= 0) {
+ return;
+ }
+
+ int cellWidth = -1;
+
+ // if width is undefined, use the width of the first cell
+ // otherwise use distributed sizes
+ if (undefinedWidth) {
+ cellWidth = calendar.getWeekGrid().getDateCellWidth()
+ - calendar.getWeekGrid().getDateSlotBorder();
+ }
+
+ for (int i = 0; i < cells; i++) {
+ DateCellContainer dc = (DateCellContainer) getWidget(i);
+
+ if (undefinedWidth) {
+ dc.setWidth(cellWidth + "px");
+
+ } else {
+ dc.setWidth(calendar.getWeekGrid().getDateCellWidths()[i]
+ + "px");
+ }
+ }
+ }
+
+ @Override
+ public String getTooltipKey() {
+ return null;
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java
new file mode 100644
index 0000000000..a97d352e81
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/WeeklyLongEventsDateCell.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule;
+
+import java.util.Date;
+
+import com.google.gwt.user.client.ui.HTML;
+import com.vaadin.client.ui.VCalendar;
+
+/**
+ * Represents a cell used in {@link WeeklyLongEvents}
+ *
+ * @since 7.1
+ */
+public class WeeklyLongEventsDateCell extends HTML implements HasTooltipKey {
+ private Date date;
+ private CalendarEvent calendarEvent;
+ private VCalendar calendar;
+
+ public WeeklyLongEventsDateCell() {
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setEvent(CalendarEvent event) {
+ calendarEvent = event;
+ }
+
+ public CalendarEvent getEvent() {
+ return calendarEvent;
+ }
+
+ public void setCalendar(VCalendar calendar) {
+ this.calendar = calendar;
+ }
+
+ public VCalendar getCalendar() {
+ return calendar;
+ }
+
+ @Override
+ public Object getTooltipKey() {
+ if (calendarEvent != null) {
+ return calendarEvent.getIndex();
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java
new file mode 100644
index 0000000000..aab9ca9c38
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarDropHandler.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.ui.calendar.CalendarConnector;
+import com.vaadin.client.ui.dd.VAbstractDropHandler;
+
+/**
+ * Abstract base class for calendar drop handlers.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+public abstract class CalendarDropHandler extends VAbstractDropHandler {
+
+ protected CalendarConnector calendarConnector;
+
+ /**
+ * Set the calendar instance
+ *
+ * @param calendarPaintable
+ */
+ public void setConnector(CalendarConnector calendarConnector) {
+ this.calendarConnector = calendarConnector;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#getConnector()
+ */
+ @Override
+ public CalendarConnector getConnector() {
+ return calendarConnector;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VDropHandler#getApplicationConnection
+ * ()
+ */
+ @Override
+ public ApplicationConnection getApplicationConnection() {
+ return calendarConnector.getClient();
+ }
+
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java
new file mode 100644
index 0000000000..913477ee14
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.calendar.schedule.SimpleDayCell;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragEvent;
+
+/**
+ * Handles DD when the monthly view is showing in the Calendar. In the monthly
+ * view, drops are only allowed in the the day cells. Only the day index is
+ * included in the drop details sent to the server.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarMonthDropHandler extends CalendarDropHandler {
+
+ private Element currentTargetElement;
+ private SimpleDayCell currentTargetDay;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragAccepted
+ * (com.vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ deEmphasis();
+ currentTargetElement = drag.getElementOver();
+ currentTargetDay = Util.findWidget(currentTargetElement,
+ SimpleDayCell.class);
+ emphasis();
+ }
+
+ /**
+ * Removed the emphasis CSS style name from the currently emphasized day
+ */
+ private void deEmphasis() {
+ if (currentTargetElement != null && currentTargetDay != null) {
+ currentTargetDay.removeEmphasisStyle();
+ currentTargetElement = null;
+ }
+ }
+
+ /**
+ * Add CSS style name for the currently emphasized day
+ */
+ private void emphasis() {
+ if (currentTargetElement != null && currentTargetDay != null) {
+ currentTargetDay.addEmphasisStyle();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragOver(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragOver(final VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ validate(new VAcceptCallback() {
+ @Override
+ public void accepted(VDragEvent event) {
+ dragAccepted(drag);
+ }
+ }, drag);
+ }
+ }
+
+ /**
+ * Checks if the one can perform a drop in a element
+ *
+ * @param elementOver
+ * The element to check
+ * @return
+ */
+ private boolean isLocationValid(
+ com.google.gwt.user.client.Element elementOver) {
+ com.google.gwt.user.client.Element monthGridElement = calendarConnector
+ .getWidget().getMonthGrid().getElement();
+
+ // drops are not allowed in:
+ // - weekday header
+ // - week number bart
+ return DOM.isOrHasChild(monthGridElement, elementOver);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragEnter(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ // NOOP, we determine drag acceptance in dragOver
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#drop(com.vaadin
+ * .terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public boolean drop(VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ updateDropDetails(drag);
+ deEmphasis();
+ return super.drop(drag);
+
+ } else {
+ deEmphasis();
+ return false;
+ }
+ }
+
+ /**
+ * Updates the drop details sent to the server
+ *
+ * @param drag
+ * The drag event
+ */
+ private void updateDropDetails(VDragEvent drag) {
+ int dayIndex = calendarConnector.getWidget().getMonthGrid()
+ .getDayCellIndex(currentTargetDay);
+
+ drag.getDropDetails().put("dropDayIndex", dayIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragLeave(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis();
+ super.dragLeave(drag);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java
new file mode 100644
index 0000000000..0ea683dc3c
--- /dev/null
+++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.ui.calendar.schedule.dd;
+
+import com.google.gwt.user.client.DOM;
+import com.google.gwt.user.client.Element;
+import com.vaadin.client.Util;
+import com.vaadin.client.ui.calendar.schedule.DateCell;
+import com.vaadin.client.ui.calendar.schedule.DateCellDayEvent;
+import com.vaadin.client.ui.dd.VAcceptCallback;
+import com.vaadin.client.ui.dd.VDragEvent;
+
+/**
+ * Handles DD when the weekly view is showing in the Calendar. In the weekly
+ * view, drops are only allowed in the the time slots for each day. The slot
+ * index and the day index are included in the drop details sent to the server.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public class CalendarWeekDropHandler extends CalendarDropHandler {
+
+ private com.google.gwt.user.client.Element currentTargetElement;
+ private DateCell currentTargetDay;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragAccepted
+ * (com.vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ protected void dragAccepted(VDragEvent drag) {
+ deEmphasis();
+ currentTargetElement = drag.getElementOver();
+ currentTargetDay = Util
+ .findWidget(currentTargetElement, DateCell.class);
+ emphasis();
+ }
+
+ /**
+ * Removes the CSS style name from the emphasized element
+ */
+ private void deEmphasis() {
+ if (currentTargetElement != null) {
+ currentTargetDay.removeEmphasisStyle(currentTargetElement);
+ currentTargetElement = null;
+ }
+ }
+
+ /**
+ * Add a CSS stylen name to current target element
+ */
+ private void emphasis() {
+ currentTargetDay.addEmphasisStyle(currentTargetElement);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragOver(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragOver(final VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ validate(new VAcceptCallback() {
+ @Override
+ public void accepted(VDragEvent event) {
+ dragAccepted(drag);
+ }
+ }, drag);
+ }
+ }
+
+ /**
+ * Checks if the location is a valid drop location
+ *
+ * @param elementOver
+ * The element to check
+ * @return
+ */
+ private boolean isLocationValid(
+ com.google.gwt.user.client.Element elementOver) {
+ com.google.gwt.user.client.Element weekGridElement = calendarConnector
+ .getWidget().getWeekGrid().getElement();
+ com.google.gwt.user.client.Element timeBarElement = calendarConnector
+ .getWidget().getWeekGrid().getTimeBar().getElement();
+
+ com.google.gwt.user.client.Element todayBarElement = null;
+ if (calendarConnector.getWidget().getWeekGrid().hasToday()) {
+ todayBarElement = (Element) calendarConnector.getWidget()
+ .getWeekGrid().getDateCellOfToday().getTodaybarElement();
+ }
+
+ // drops are not allowed in:
+ // - weekday header
+ // - allday event list
+ // - todaybar
+ // - timebar
+ // - events
+ return DOM.isOrHasChild(weekGridElement, elementOver)
+ && !DOM.isOrHasChild(timeBarElement, elementOver)
+ && todayBarElement != elementOver
+ && (Util.findWidget(elementOver, DateCellDayEvent.class) == null);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragEnter(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragEnter(VDragEvent drag) {
+ // NOOP, we determine drag acceptance in dragOver
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#drop(com.vaadin
+ * .terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public boolean drop(VDragEvent drag) {
+ if (isLocationValid(drag.getElementOver())) {
+ updateDropDetails(drag);
+ deEmphasis();
+ return super.drop(drag);
+
+ } else {
+ deEmphasis();
+ return false;
+ }
+ }
+
+ /**
+ * Update the drop details sent to the server
+ *
+ * @param drag
+ * The drag event
+ */
+ private void updateDropDetails(VDragEvent drag) {
+ int slotIndex = currentTargetDay.getSlotIndex(currentTargetElement);
+ int dayIndex = calendarConnector.getWidget().getWeekGrid()
+ .getDateCellIndex(currentTargetDay);
+
+ drag.getDropDetails().put("dropDayIndex", dayIndex);
+ drag.getDropDetails().put("dropSlotIndex", slotIndex);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.terminal.gwt.client.ui.dd.VAbstractDropHandler#dragLeave(com
+ * .vaadin.terminal.gwt.client.ui.dd.VDragEvent)
+ */
+ @Override
+ public void dragLeave(VDragEvent drag) {
+ deEmphasis();
+ super.dragLeave(drag);
+ }
+}
diff --git a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
index 772419e730..85e4e5ee8b 100644
--- a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
+++ b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java
@@ -69,6 +69,8 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
blurHandlerRegistration);
if (null != getState().errorMessage) {
+ getWidget().setAriaInvalid(true);
+
if (getWidget().errorIndicatorElement == null) {
getWidget().errorIndicatorElement = DOM.createSpan();
getWidget().errorIndicatorElement.setInnerHTML("&nbsp;");
@@ -85,8 +87,11 @@ public class CheckBoxConnector extends AbstractFieldConnector implements
} else if (getWidget().errorIndicatorElement != null) {
DOM.setStyleAttribute(getWidget().errorIndicatorElement, "display",
"none");
+
+ getWidget().setAriaInvalid(false);
}
+ getWidget().setAriaRequired(isRequired());
if (isReadOnly()) {
getWidget().setEnabled(false);
}
diff --git a/client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java b/client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java
index beff3eaa72..2fb40b3cdb 100644
--- a/client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java
+++ b/client/src/com/vaadin/client/ui/datefield/InlineDateFieldConnector.java
@@ -114,6 +114,8 @@ public class InlineDateFieldConnector extends AbstractDateFieldConnector {
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
getWidget().setTabIndex(getState().tabIndex);
+ getWidget().calendarPanel.setRangeStart(getState().rangeStart);
+ getWidget().calendarPanel.setRangeEnd(getState().rangeEnd);
}
@Override
diff --git a/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java b/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java
index 7246c27b6b..b3bb481658 100644
--- a/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java
+++ b/client/src/com/vaadin/client/ui/datefield/PopupDateFieldConnector.java
@@ -119,6 +119,8 @@ public class PopupDateFieldConnector extends TextualDateConnector {
+ "-button-readonly");
}
+ getWidget().setDescriptionForAssistiveDevices(
+ getState().descriptionForAssistiveDevices);
getWidget().calendarToggle.setEnabled(true);
}
@@ -136,6 +138,8 @@ public class PopupDateFieldConnector extends TextualDateConnector {
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
getWidget().setTextFieldEnabled(getState().textFieldEnabled);
+ getWidget().setRangeStart(getState().rangeStart);
+ getWidget().setRangeEnd(getState().rangeEnd);
}
@Override
diff --git a/client/src/com/vaadin/client/ui/form/FormConnector.java b/client/src/com/vaadin/client/ui/form/FormConnector.java
index 22277b6974..acd0e917fc 100644
--- a/client/src/com/vaadin/client/ui/form/FormConnector.java
+++ b/client/src/com/vaadin/client/ui/form/FormConnector.java
@@ -231,4 +231,9 @@ public class FormConnector extends AbstractComponentContainerConnector
// as a part of the actual layout
return null;
}
+
+ @Override
+ public boolean hasTooltip() {
+ return false;
+ }
}
diff --git a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java
index 1a952959f3..c65f689f7a 100644
--- a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java
+++ b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java
@@ -141,4 +141,13 @@ public class FormLayoutConnector extends AbstractLayoutConnector {
return info;
}
+ @Override
+ public boolean hasTooltip() {
+ /*
+ * Tooltips are fetched from child connectors -> there's no quick way of
+ * checking whether there might a tooltip hiding somewhere
+ */
+ return true;
+ }
+
}
diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java b/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java
index d8ca73a401..3e22ebb05b 100644
--- a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java
+++ b/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java
@@ -209,4 +209,14 @@ public class MenuBarConnector extends AbstractComponentConnector implements
return info;
}
+
+ @Override
+ public boolean hasTooltip() {
+ /*
+ * Item tooltips are not processed until updateFromUIDL, so we can't be
+ * sure that there are no tooltips during onStateChange when this method
+ * is used.
+ */
+ return true;
+ }
}
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
index 50de8e0936..cb6ad25e97 100644
--- a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
+++ b/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java
@@ -31,6 +31,7 @@ import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler;
import com.vaadin.client.ui.AbstractFieldConnector;
import com.vaadin.client.ui.AbstractLayoutConnector;
import com.vaadin.client.ui.LayoutClickEventHandler;
+import com.vaadin.client.ui.aria.AriaHelper;
import com.vaadin.client.ui.layout.ElementResizeEvent;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.shared.AbstractFieldState;
@@ -258,6 +259,10 @@ public abstract class AbstractOrderedLayoutConnector extends
slot.setCaption(caption, iconUrlString, styles, error, showError,
required, enabled);
+ AriaHelper.handleInputRequired(child.getWidget(), required);
+ AriaHelper.handleInputInvalid(child.getWidget(), showError);
+ AriaHelper.bindCaption(child.getWidget(), slot.getCaptionElement());
+
if (slot.hasCaption()) {
CaptionPosition pos = slot.getCaptionPosition();
getLayoutManager().addElementResizeListener(
diff --git a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
index 5fab131c65..00ff5bbc5a 100644
--- a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
+++ b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java
@@ -18,6 +18,7 @@ package com.vaadin.client.ui.orderedlayout;
import java.util.List;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
@@ -507,6 +508,11 @@ public final class Slot extends SimplePanel {
// character)
requiredIcon.setInnerHTML("*");
requiredIcon.setClassName("v-required-field-indicator");
+
+ // The star should not be read by the screen reader, as it is
+ // purely visual. Required state is set at the element level for
+ // the screen reader.
+ Roles.getTextboxRole().setAriaHiddenState(requiredIcon, true);
}
caption.appendChild(requiredIcon);
} else if (requiredIcon != null) {
diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java
index 6e50bab9ab..36587ffe4d 100644
--- a/client/src/com/vaadin/client/ui/table/TableConnector.java
+++ b/client/src/com/vaadin/client/ui/table/TableConnector.java
@@ -395,6 +395,16 @@ public class TableConnector extends AbstractHasComponentsConnector implements
}
@Override
+ public boolean hasTooltip() {
+ /*
+ * Tooltips for individual rows and cells are not processed until
+ * updateFromUIDL, so we can't be sure that there are no tooltips during
+ * onStateChange when this method is used.
+ */
+ return true;
+ }
+
+ @Override
public void onConnectorHierarchyChange(
ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
// TODO Move code from updateFromUIDL to this method
diff --git a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java b/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java
index f1ad5e792a..04a514738d 100644
--- a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java
+++ b/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java
@@ -138,6 +138,16 @@ public class TabsheetConnector extends TabsheetBaseConnector implements
}
@Override
+ public boolean hasTooltip() {
+ /*
+ * Tab tooltips are not processed until updateFromUIDL, so we can't be
+ * sure that there are no tooltips during onStateChange when this method
+ * is used.
+ */
+ return true;
+ }
+
+ @Override
public void onConnectorHierarchyChange(
ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) {
// TODO Move code from updateFromUIDL to this method
diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
index d6d1cef8cc..ef016c31b7 100644
--- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java
+++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java
@@ -19,6 +19,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
+import com.google.gwt.aria.client.Roles;
import com.google.gwt.dom.client.Element;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
@@ -26,6 +27,7 @@ import com.vaadin.client.Paintable;
import com.vaadin.client.TooltipInfo;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
+import com.vaadin.client.VConsole;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.VTree;
@@ -93,7 +95,7 @@ public class TreeConnector extends AbstractComponentConnector implements
}
childTree = getWidget().new TreeNode();
getConnection().getVTooltip().connectHandlersToWidget(childTree);
- updateNodeFromUIDL(childTree, childUidl);
+ updateNodeFromUIDL(childTree, childUidl, 1);
getWidget().body.add(childTree);
childTree.addStyleDependentName("root");
childTree.childNodeContainer.addStyleDependentName("root");
@@ -108,6 +110,9 @@ public class TreeConnector extends AbstractComponentConnector implements
getWidget().isMultiselect = "multi".equals(selectMode);
if (getWidget().isMultiselect) {
+ Roles.getTreeRole().setAriaMultiselectableProperty(
+ getWidget().getElement(), true);
+
if (BrowserInfo.get().isTouchDevice()) {
// Always use the simple mode for touch devices that do not have
// shift/ctrl keys (#8595)
@@ -116,6 +121,9 @@ public class TreeConnector extends AbstractComponentConnector implements
getWidget().multiSelectMode = MultiSelectMode.valueOf(uidl
.getStringAttribute("multiselectmode"));
}
+ } else {
+ Roles.getTreeRole().setAriaMultiselectableProperty(
+ getWidget().getElement(), false);
}
getWidget().selectedIds = uidl.getStringArrayVariableAsSet("selected");
@@ -169,7 +177,18 @@ public class TreeConnector extends AbstractComponentConnector implements
// expanding node happened server side
rootNode.setState(true, false);
}
- renderChildNodes(rootNode, (Iterator) uidl.getChildIterator());
+ String levelPropertyString = Roles.getTreeitemRole()
+ .getAriaLevelProperty(rootNode.getElement());
+ int levelProperty;
+ try {
+ levelProperty = Integer.valueOf(levelPropertyString);
+ } catch (NumberFormatException e) {
+ levelProperty = 1;
+ VConsole.error(e);
+ }
+
+ renderChildNodes(rootNode, (Iterator) uidl.getChildIterator(),
+ levelProperty + 1);
}
}
@@ -196,7 +215,10 @@ public class TreeConnector extends AbstractComponentConnector implements
}
- public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl) {
+ public void updateNodeFromUIDL(TreeNode treeNode, UIDL uidl, int level) {
+ Roles.getTreeitemRole().setAriaLevelProperty(treeNode.getElement(),
+ level);
+
String nodeKey = uidl.getStringAttribute("key");
treeNode.setText(uidl
.getStringAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION));
@@ -212,7 +234,8 @@ public class TreeConnector extends AbstractComponentConnector implements
if (uidl.getChildCount() == 0) {
treeNode.childNodeContainer.setVisible(false);
} else {
- renderChildNodes(treeNode, (Iterator) uidl.getChildIterator());
+ renderChildNodes(treeNode, (Iterator) uidl.getChildIterator(),
+ level + 1);
treeNode.childrenLoaded = true;
}
} else {
@@ -239,11 +262,14 @@ public class TreeConnector extends AbstractComponentConnector implements
getWidget().selectedIds.add(nodeKey);
}
- treeNode.setIcon(uidl
- .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON));
+ String iconUrl = uidl
+ .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON);
+ String iconAltText = uidl
+ .getStringAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT);
+ treeNode.setIcon(iconUrl, iconAltText);
}
- void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i) {
+ void renderChildNodes(TreeNode containerNode, Iterator<UIDL> i, int level) {
containerNode.childNodeContainer.clear();
containerNode.childNodeContainer.setVisible(true);
while (i.hasNext()) {
@@ -256,7 +282,7 @@ public class TreeConnector extends AbstractComponentConnector implements
}
final TreeNode childTree = getWidget().new TreeNode();
getConnection().getVTooltip().connectHandlersToWidget(childTree);
- updateNodeFromUIDL(childTree, childUidl);
+ updateNodeFromUIDL(childTree, childUidl, level);
containerNode.childNodeContainer.add(childTree);
if (!i.hasNext()) {
childTree
@@ -306,4 +332,14 @@ public class TreeConnector extends AbstractComponentConnector implements
return info;
}
+ @Override
+ public boolean hasTooltip() {
+ /*
+ * Item tooltips are not processed until updateFromUIDL, so we can't be
+ * sure that there are no tooltips during onStateChange when this method
+ * is used.
+ */
+ return true;
+ }
+
}
diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java
index 593aa0d793..643d687f1d 100644
--- a/client/src/com/vaadin/client/ui/ui/UIConnector.java
+++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java
@@ -21,9 +21,13 @@ import java.util.List;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.HeadElement;
+import com.google.gwt.dom.client.LinkElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.StyleInjector;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
@@ -34,6 +38,7 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
@@ -45,7 +50,6 @@ import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.Focusable;
import com.vaadin.client.Paintable;
-import com.vaadin.client.TooltipInfo;
import com.vaadin.client.UIDL;
import com.vaadin.client.VConsole;
import com.vaadin.client.communication.StateChangeEvent;
@@ -57,12 +61,16 @@ import com.vaadin.client.ui.VNotification;
import com.vaadin.client.ui.VUI;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.client.ui.window.WindowConnector;
+import com.vaadin.server.Page.Styles;
import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.communication.MethodInvocation;
import com.vaadin.shared.ui.ComponentStateUtil;
import com.vaadin.shared.ui.Connect;
import com.vaadin.shared.ui.Connect.LoadStyle;
import com.vaadin.shared.ui.ui.PageClientRpc;
+import com.vaadin.shared.ui.ui.PageState;
import com.vaadin.shared.ui.ui.ScrollClientRpc;
+import com.vaadin.shared.ui.ui.UIClientRpc;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIServerRpc;
import com.vaadin.shared.ui.ui.UIState;
@@ -91,6 +99,12 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
public void setTitle(String title) {
com.google.gwt.user.client.Window.setTitle(title);
}
+
+ @Override
+ public void reload() {
+ Window.Location.reload();
+
+ }
});
registerRpc(ScrollClientRpc.class, new ScrollClientRpc() {
@Override
@@ -103,13 +117,29 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
getWidget().getElement().setScrollLeft(scrollLeft);
}
});
+ registerRpc(UIClientRpc.class, new UIClientRpc() {
+ @Override
+ public void uiClosed(final boolean sessionExpired) {
+ Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+ @Override
+ public void execute() {
+ if (sessionExpired) {
+ getConnection().showSessionExpiredError(null);
+ } else {
+ getState().enabled = false;
+ updateEnabledState(getState().enabled);
+ }
+ }
+ });
+ }
+ });
getWidget().addResizeHandler(new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
getRpcProxy(UIServerRpc.class).resize(event.getHeight(),
event.getWidth(), Window.getClientWidth(),
Window.getClientHeight());
- if (getState().immediate) {
+ if (getState().immediate || getPageState().hasResizeListeners) {
getConnection().sendPendingVariableChanges();
}
}
@@ -257,6 +287,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
final UIDL notification = (UIDL) it.next();
VNotification.showNotification(client, notification);
}
+ } else if (tag == "css-injections") {
+ injectCSS(childUidl);
}
}
@@ -326,6 +358,47 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
getWidget().rendering = false;
}
+ /**
+ * Reads CSS strings and resources injected by {@link Styles#inject} from
+ * the UIDL stream.
+ *
+ * @param uidl
+ * The uidl which contains "css-resource" and "css-string" tags
+ */
+ private void injectCSS(UIDL uidl) {
+
+ final HeadElement head = HeadElement.as(Document.get()
+ .getElementsByTagName(HeadElement.TAG).getItem(0));
+
+ /*
+ * Search the UIDL stream for CSS resources and strings to be injected.
+ */
+ for (Iterator<?> it = uidl.getChildIterator(); it.hasNext();) {
+ UIDL cssInjectionsUidl = (UIDL) it.next();
+
+ // Check if we have resources to inject
+ if (cssInjectionsUidl.getTag().equals("css-resource")) {
+ String url = getWidget().connection
+ .translateVaadinUri(cssInjectionsUidl
+ .getStringAttribute("url"));
+ LinkElement link = LinkElement.as(DOM
+ .createElement(LinkElement.TAG));
+ link.setRel("stylesheet");
+ link.setHref(url);
+ link.setType("text/css");
+ head.appendChild(link);
+
+ // Check if we have CSS string to inject
+ } else if (cssInjectionsUidl.getTag().equals("css-string")) {
+ for (Iterator<?> it2 = cssInjectionsUidl.getChildIterator(); it2
+ .hasNext();) {
+ StyleInjector.injectAtEnd((String) it2.next());
+ StyleInjector.flush();
+ }
+ }
+ }
+ }
+
public void init(String rootPanelId,
ApplicationConnection applicationConnection) {
DOM.sinkEvents(getWidget().getElement(), Event.ONKEYDOWN
@@ -339,8 +412,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
String themeName = applicationConnection.getConfiguration()
.getThemeName();
- // Remove chars that are not suitable for style names
- themeName = themeName.replaceAll("[^a-zA-Z0-9]", "");
root.addStyleName(themeName);
root.add(getWidget());
@@ -368,6 +439,8 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
};
+ private Timer pollTimer = null;
+
@Override
public void updateCaption(ComponentConnector component) {
// NOP The main view never draws caption for its layout
@@ -432,6 +505,23 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
return (UIState) super.getState();
}
+ /**
+ * Returns the state of the Page associated with the UI.
+ * <p>
+ * Note that state is considered an internal part of the connector. You
+ * should not rely on the state object outside of the connector who owns it.
+ * If you depend on the state of other connectors you should use their
+ * public API instead of their state object directly. The page state might
+ * not be an independent state object but can be embedded in UI state.
+ * </p>
+ *
+ * @since 7.1
+ * @return state object of the page
+ */
+ public PageState getPageState() {
+ return getState().pageState;
+ }
+
@Override
public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) {
ComponentConnector oldChild = null;
@@ -477,13 +567,13 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
@Override
- public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) {
+ public boolean hasTooltip() {
/*
- * Override method to make AbstractComponentConnector.hasTooltip()
- * return true so there's a top level handler that takes care of hiding
- * tooltips whenever the mouse is moved somewhere else.
+ * Always return true so there's always top level tooltip handler that
+ * takes care of hiding tooltips whenever the mouse is moved somewhere
+ * else.
*/
- return super.getTooltipInfo(element);
+ return true;
}
/**
@@ -505,4 +595,74 @@ public class UIConnector extends AbstractSingleComponentContainerConnector
}
});
}
+
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+ if (stateChangeEvent.hasPropertyChanged("tooltipConfiguration")) {
+ getConnection().getVTooltip().setCloseTimeout(
+ getState().tooltipConfiguration.closeTimeout);
+ getConnection().getVTooltip().setOpenDelay(
+ getState().tooltipConfiguration.openDelay);
+ getConnection().getVTooltip().setQuickOpenDelay(
+ getState().tooltipConfiguration.quickOpenDelay);
+ getConnection().getVTooltip().setQuickOpenTimeout(
+ getState().tooltipConfiguration.quickOpenTimeout);
+ getConnection().getVTooltip().setMaxWidth(
+ getState().tooltipConfiguration.maxWidth);
+ }
+
+ if (stateChangeEvent
+ .hasPropertyChanged("loadingIndicatorConfiguration")) {
+ getConnection().getLoadingIndicator().setFirstDelay(
+ getState().loadingIndicatorConfiguration.firstDelay);
+ getConnection().getLoadingIndicator().setSecondDelay(
+ getState().loadingIndicatorConfiguration.secondDelay);
+ getConnection().getLoadingIndicator().setThirdDelay(
+ getState().loadingIndicatorConfiguration.thirdDelay);
+ }
+
+ if (stateChangeEvent.hasPropertyChanged("pollInterval")) {
+ configurePolling();
+ }
+
+ if (stateChangeEvent.hasPropertyChanged("pushMode")) {
+ getConnection().setPushEnabled(getState().pushMode.isEnabled());
+ }
+
+ if (stateChangeEvent.hasPropertyChanged("overlayContainerLabel")) {
+ getConnection().setOverlayContainerLabel(
+ getState().overlayContainerLabel);
+ }
+ }
+
+ private void configurePolling() {
+ if (pollTimer != null) {
+ pollTimer.cancel();
+ pollTimer = null;
+ }
+ if (getState().pollInterval >= 0) {
+ pollTimer = new Timer() {
+ @Override
+ public void run() {
+ /*
+ * Verify that polling has not recently been canceled. This
+ * is needed because Timer.cancel() does not always work
+ * properly in IE 8 until GWT issue 8101 has been fixed.
+ */
+ if (pollTimer != null) {
+ getRpcProxy(UIServerRpc.class).poll();
+ // Send changes even though poll is @Delayed
+ getConnection().sendPendingVariableChanges();
+ }
+ }
+ };
+ pollTimer.scheduleRepeating(getState().pollInterval);
+ } else {
+ // Ensure no more polls are sent as polling has been disabled
+ getConnection().removePendingInvocations(
+ new MethodInvocation(getConnectorId(), UIServerRpc.class
+ .getName(), "poll"));
+ }
+ }
}
diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java
index 8cfc25a9dc..90311e30ad 100644
--- a/client/src/com/vaadin/client/ui/window/WindowConnector.java
+++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java
@@ -20,7 +20,10 @@ import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.DOM;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.event.dom.client.DoubleClickEvent;
+import com.google.gwt.event.dom.client.DoubleClickHandler;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
import com.vaadin.client.ApplicationConnection;
@@ -31,6 +34,7 @@ import com.vaadin.client.LayoutManager;
import com.vaadin.client.Paintable;
import com.vaadin.client.UIDL;
import com.vaadin.client.Util;
+import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractSingleComponentContainerConnector;
import com.vaadin.client.ui.ClickEventHandler;
import com.vaadin.client.ui.PostLayoutListener;
@@ -41,6 +45,7 @@ import com.vaadin.client.ui.VWindow;
import com.vaadin.client.ui.layout.MayScrollChildren;
import com.vaadin.shared.MouseEventDetails;
import com.vaadin.shared.ui.Connect;
+import com.vaadin.shared.ui.window.WindowMode;
import com.vaadin.shared.ui.window.WindowServerRpc;
import com.vaadin.shared.ui.window.WindowState;
@@ -57,7 +62,32 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
}
};
- boolean minWidthChecked = false;
+ abstract class WindowEventHandler implements ClickHandler,
+ DoubleClickHandler {
+ }
+
+ private WindowEventHandler maximizeRestoreClickHandler = new WindowEventHandler() {
+
+ @Override
+ public void onClick(ClickEvent event) {
+ final Element target = event.getNativeEvent().getEventTarget()
+ .cast();
+ if (target == getWidget().maximizeRestoreBox) {
+ // Click on maximize/restore box
+ onMaximizeRestore();
+ }
+ }
+
+ @Override
+ public void onDoubleClick(DoubleClickEvent event) {
+ final Element target = event.getNativeEvent().getEventTarget()
+ .cast();
+ if (getWidget().header.isOrHasChild(target)) {
+ // Double click on header
+ onMaximizeRestore();
+ }
+ }
+ };
@Override
public boolean delegateCaptionHandling() {
@@ -68,12 +98,18 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
protected void init() {
super.init();
+ VWindow window = getWidget();
+
getLayoutManager().registerDependency(this,
- getWidget().contentPanel.getElement());
- getLayoutManager().registerDependency(this, getWidget().header);
- getLayoutManager().registerDependency(this, getWidget().footer);
+ window.contentPanel.getElement());
+ getLayoutManager().registerDependency(this, window.header);
+ getLayoutManager().registerDependency(this, window.footer);
- getWidget().setOwner(getConnection().getUIConnector().getWidget());
+ window.addHandler(maximizeRestoreClickHandler, ClickEvent.getType());
+ window.addHandler(maximizeRestoreClickHandler,
+ DoubleClickEvent.getType());
+
+ window.setOwner(getConnection().getUIConnector().getWidget());
}
@Override
@@ -87,109 +123,46 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
@Override
public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
- getWidget().id = getConnectorId();
- getWidget().client = client;
-
- // Workaround needed for Testing Tools (GWT generates window DOM
- // slightly different in different browsers).
- DOM.setElementProperty(getWidget().closeBox, "id", getConnectorId()
- + "_window_close");
- if (isRealUpdate(uidl)) {
- if (getState().modal != getWidget().vaadinModality) {
- getWidget().setVaadinModality(!getWidget().vaadinModality);
- }
- if (!getWidget().isAttached()) {
- getWidget().setVisible(false); // hide until
- // possible centering
- getWidget().show();
- }
- if (getState().resizable != getWidget().resizable) {
- getWidget().setResizable(getState().resizable);
- }
- getWidget().resizeLazy = getState().resizeLazy;
+ VWindow window = getWidget();
+ String connectorId = getConnectorId();
- getWidget().setDraggable(getState().draggable);
+ window.id = getConnectorId();
+ window.client = client;
- // Caption must be set before required header size is measured. If
- // the caption attribute is missing the caption should be cleared.
- String iconURL = null;
- if (getIcon() != null) {
- iconURL = getIcon();
- }
- getWidget().setCaption(getState().caption, iconURL);
- }
+ // Workaround needed for Testing Tools (GWT generates window DOM
+ // slightly different in different browsers).
+ window.closeBox.setId(connectorId + "_window_close");
+ window.maximizeRestoreBox
+ .setId(connectorId + "_window_maximizerestore");
- getWidget().visibilityChangesDisabled = true;
+ window.visibilityChangesDisabled = true;
if (!isRealUpdate(uidl)) {
return;
}
- getWidget().visibilityChangesDisabled = false;
-
- clickEventHandler.handleEventHandlerRegistration();
-
- getWidget().immediate = getState().immediate;
-
- getWidget().setClosable(!isReadOnly());
-
- // Initialize the position form UIDL
- int positionx = getState().positionX;
- int positiony = getState().positionY;
- if (positionx >= 0 || positiony >= 0) {
- if (positionx < 0) {
- positionx = 0;
- }
- if (positiony < 0) {
- positiony = 0;
- }
- getWidget().setPopupPosition(positionx, positiony);
- }
-
- int childIndex = 0;
+ window.visibilityChangesDisabled = false;
// we may have actions
for (int i = 0; i < uidl.getChildCount(); i++) {
UIDL childUidl = uidl.getChildUIDL(i);
if (childUidl.getTag().equals("actions")) {
- if (getWidget().shortcutHandler == null) {
- getWidget().shortcutHandler = new ShortcutActionHandler(
- getConnectorId(), client);
+ if (window.shortcutHandler == null) {
+ window.shortcutHandler = new ShortcutActionHandler(
+ connectorId, client);
}
- getWidget().shortcutHandler.updateActionMap(childUidl);
+ window.shortcutHandler.updateActionMap(childUidl);
}
}
- // setting scrollposition must happen after children is rendered
- getWidget().contentPanel.setScrollPosition(getState().scrollTop);
- getWidget().contentPanel
- .setHorizontalScrollPosition(getState().scrollLeft);
-
- // Center this window on screen if requested
- // This had to be here because we might not know the content size before
- // everything is painted into the window
-
- // centered is this is unset on move/resize
- getWidget().centered = getState().centered;
- getWidget().setVisible(true);
-
- // ensure window is not larger than browser window
- if (getWidget().getOffsetWidth() > Window.getClientWidth()) {
- getWidget().setWidth(Window.getClientWidth() + "px");
- }
- if (getWidget().getOffsetHeight() > Window.getClientHeight()) {
- getWidget().setHeight(Window.getClientHeight() + "px");
- }
-
if (uidl.hasAttribute("bringToFront")) {
/*
* Focus as a side-effect. Will be overridden by
* ApplicationConnection if another component was focused by the
* server side.
*/
- getWidget().contentPanel.focus();
- getWidget().bringToFrontSequence = uidl
- .getIntAttribute("bringToFront");
+ window.contentPanel.focus();
+ window.bringToFrontSequence = uidl.getIntAttribute("bringToFront");
VWindow.deferOrdering();
}
}
@@ -224,26 +197,6 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
boolean hasContent = (content != null);
Element contentElement = window.contentPanel.getElement();
- if (!minWidthChecked) {
- boolean needsMinWidth = !isUndefinedWidth() || !hasContent
- || content.isRelativeWidth();
- int minWidth = window.getMinWidth();
- if (needsMinWidth && lm.getInnerWidth(contentElement) < minWidth) {
- minWidthChecked = true;
- // Use minimum width if less than a certain size
- window.setWidth(minWidth + "px");
- }
- minWidthChecked = true;
- }
-
- boolean needsMinHeight = !isUndefinedHeight() || !hasContent
- || content.isRelativeHeight();
- int minHeight = window.getMinHeight();
- if (needsMinHeight && lm.getInnerHeight(contentElement) < minHeight) {
- // Use minimum height if less than a certain size
- window.setHeight(minHeight + "px");
- }
-
Style contentStyle = window.contents.getStyle();
int headerHeight = lm.getOuterHeight(window.header);
@@ -291,9 +244,8 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
@Override
public void postLayout() {
- minWidthChecked = false;
VWindow window = getWidget();
- if (window.centered) {
+ if (window.centered && getState().windowMode != WindowMode.MAXIMIZED) {
window.center();
}
window.positionOrSizeUpdated();
@@ -304,6 +256,126 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector
return (WindowState) super.getState();
}
+ @Override
+ public void onStateChanged(StateChangeEvent stateChangeEvent) {
+ super.onStateChanged(stateChangeEvent);
+
+ VWindow window = getWidget();
+ WindowState state = getState();
+
+ if (state.modal != window.vaadinModality) {
+ window.setVaadinModality(!window.vaadinModality);
+ }
+ if (!window.isAttached()) {
+ window.setVisible(false); // hide until possible centering
+ window.show();
+ }
+ boolean resizeable = state.resizable
+ && state.windowMode == WindowMode.NORMAL;
+ window.setResizable(resizeable);
+
+ window.resizeLazy = state.resizeLazy;
+
+ window.setDraggable(state.draggable
+ && state.windowMode == WindowMode.NORMAL);
+
+ window.updateMaximizeRestoreClassName(state.resizable, state.windowMode);
+
+ // Caption must be set before required header size is measured. If
+ // the caption attribute is missing the caption should be cleared.
+ String iconURL = null;
+ if (getIcon() != null) {
+ iconURL = getIcon();
+ }
+ window.setCaption(state.caption, iconURL);
+
+ clickEventHandler.handleEventHandlerRegistration();
+
+ window.immediate = state.immediate;
+
+ window.setClosable(!isReadOnly());
+ // initialize position from state
+ updateWindowPosition();
+
+ // setting scrollposition must happen after children is rendered
+ window.contentPanel.setScrollPosition(state.scrollTop);
+ window.contentPanel.setHorizontalScrollPosition(state.scrollLeft);
+
+ // Center this window on screen if requested
+ // This had to be here because we might not know the content size before
+ // everything is painted into the window
+
+ // centered is this is unset on move/resize
+ window.centered = state.centered;
+ window.setVisible(true);
+
+ // ensure window is not larger than browser window
+ if (window.getOffsetWidth() > Window.getClientWidth()) {
+ window.setWidth(Window.getClientWidth() + "px");
+ }
+ if (window.getOffsetHeight() > Window.getClientHeight()) {
+ window.setHeight(Window.getClientHeight() + "px");
+ }
+ }
+
+ // Need to override default because of window mode
+ @Override
+ protected void updateComponentSize() {
+ if (getState().windowMode == WindowMode.NORMAL) {
+ super.updateComponentSize();
+ } else if (getState().windowMode == WindowMode.MAXIMIZED) {
+ super.updateComponentSize("100%", "100%");
+ }
+ }
+
+ protected void updateWindowPosition() {
+ VWindow window = getWidget();
+ WindowState state = getState();
+ if (state.windowMode == WindowMode.NORMAL) {
+ // if centered, position handled in postLayout()
+ if (!state.centered
+ && (state.positionX >= 0 || state.positionY >= 0)) {
+ // If both positions are negative, then
+ // setWindowOrderAndPosition has already taken care of
+ // positioning the window so it stacks with other windows
+ window.setPopupPosition(state.positionX, state.positionY);
+ }
+ } else if (state.windowMode == WindowMode.MAXIMIZED) {
+ window.setPopupPositionNoUpdate(0, 0);
+ window.bringToFront();
+ }
+ }
+
+ protected void updateWindowMode() {
+ VWindow window = getWidget();
+ WindowState state = getState();
+
+ // update draggable on widget
+ window.setDraggable(state.draggable
+ && state.windowMode == WindowMode.NORMAL);
+ // update resizable on widget
+ window.setResizable(state.resizable
+ && state.windowMode == WindowMode.NORMAL);
+ updateComponentSize();
+ updateWindowPosition();
+ window.updateMaximizeRestoreClassName(state.resizable, state.windowMode);
+ window.updateContentsSize();
+ }
+
+ protected void onMaximizeRestore() {
+ WindowState state = getState();
+ if (state.resizable) {
+ if (state.windowMode == WindowMode.MAXIMIZED) {
+ state.windowMode = WindowMode.NORMAL;
+ } else {
+ state.windowMode = WindowMode.MAXIMIZED;
+ }
+ updateWindowMode();
+ getRpcProxy(WindowServerRpc.class).windowModeChanged(
+ state.windowMode);
+ }
+ }
+
/**
* Gives the WindowConnector an order number. As a side effect, moves the
* window according to its order number so the windows are stacked. This