diff options
Diffstat (limited to 'client')
164 files changed, 8539 insertions, 2564 deletions
diff --git a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml index 3aba1f6fee..2719493853 100755 --- a/client/src/com/vaadin/DefaultWidgetSet.gwt.xml +++ b/client/src/com/vaadin/DefaultWidgetSet.gwt.xml @@ -10,4 +10,8 @@ <entry-point class="com.vaadin.client.ApplicationConfiguration" /> + <!-- Since 7.2. Compile all permutations (browser support) into one Javascript + file. Speeds up compilation and does not make the Javascript significantly + larger. --> + <collapse-all-properties /> </module> diff --git a/client/src/com/vaadin/Vaadin.gwt.xml b/client/src/com/vaadin/Vaadin.gwt.xml index a1dca07a1c..711729f64f 100644 --- a/client/src/com/vaadin/Vaadin.gwt.xml +++ b/client/src/com/vaadin/Vaadin.gwt.xml @@ -1,4 +1,3 @@ - <module> <!-- This GWT module inherits all Vaadin client side functionality modules. This is the module you want to inherit in your client side project to be @@ -59,8 +58,43 @@ <!-- Use the new cross site linker to get a nocache.js without document.write --> <add-linker name="xsiframe" /> + <extend-property name="user.agent" values="opera" /> <!-- Remove IE6/IE7 permutation as they are not supported --> <set-property name="user.agent" value="ie8,ie9,ie10,gecko1_8,safari,opera" /> + <!-- Pointer event support --> + <define-property name="modernie" values="none,yes" /> + <property-provider name="modernie"><![CDATA[ + { + var ua = $wnd.navigator.userAgent; + if (ua.indexOf('IE') == -1 && ua.indexOf('Trident') != -1) { return 'yes'; } + return 'none'; + } + ]]></property-provider> + + <set-property name="modernie" value="none"> + <none> + <when-property-is name="user.agent" value="gecko1_8" /> + </none> + </set-property> + + <!-- Fall through to this rule when the browser doesn't support pointer + event --> + <replace-with class="com.vaadin.client.event.PointerEventSupportImpl"> + <when-type-is class="com.vaadin.client.event.PointerEventSupportImpl" /> + </replace-with> + + <replace-with + class="com.vaadin.client.event.PointerEventSupportImplModernIE"> + <when-type-is class="com.vaadin.client.event.PointerEventSupportImpl" /> + <none> + <when-property-is value="none" name="modernie" /> + </none> + </replace-with> + + <replace-with class="com.vaadin.client.event.PointerEventSupportImplIE10"> + <when-type-is class="com.vaadin.client.event.PointerEventSupportImpl" /> + <when-property-is value="ie10" name="user.agent" /> + </replace-with> </module> diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java index 7a70080c7e..47edd5efdc 100644 --- a/client/src/com/vaadin/client/ApplicationConfiguration.java +++ b/client/src/com/vaadin/client/ApplicationConfiguration.java @@ -41,7 +41,9 @@ import com.vaadin.client.debug.internal.LogSection; import com.vaadin.client.debug.internal.NetworkSection; import com.vaadin.client.debug.internal.ProfilerSection; import com.vaadin.client.debug.internal.Section; +import com.vaadin.client.debug.internal.TestBenchSection; import com.vaadin.client.debug.internal.VDebugWindow; +import com.vaadin.client.event.PointerEventSupport; import com.vaadin.client.metadata.BundleLoadCallback; import com.vaadin.client.metadata.ConnectorBundleLoader; import com.vaadin.client.metadata.NoDataException; @@ -511,6 +513,30 @@ public class ApplicationConfiguration implements EntryPoint { } } + /** + * Returns all tags for given class. Tags are used in + * {@link ApplicationConfiguration} to keep track of different classes and + * their hierarchy + * + * @since 7.2 + * @param classname + * name of class which tags we want + * @return Integer array of tags pointing to this classname + */ + public Integer[] getTagsForServerSideClassName(String classname) { + List<Integer> tags = new ArrayList<Integer>(); + + for (Map.Entry<Integer, String> entry : tagToServerSideClassName + .entrySet()) { + if (classname.equals(entry.getValue())) { + tags.add(entry.getKey()); + } + } + + Integer[] out = new Integer[tags.size()]; + return tags.toArray(out); + } + public Integer getParentTag(int tag) { return componentInheritanceMap.get(tag); } @@ -580,6 +606,14 @@ public class ApplicationConfiguration implements EntryPoint { enableIOS6castFix(); } + // Enable IE prompt fix (#13367) + if (browserInfo.isIE() && browserInfo.getBrowserMajorVersion() >= 10) { + enableIEPromptFix(); + } + + // Register pointer events (must be done before any events are used) + PointerEventSupport.init(); + // Prepare the debugging window if (isDebugMode()) { /* @@ -595,6 +629,7 @@ public class ApplicationConfiguration implements EntryPoint { window.addSection((Section) GWT.create(InfoSection.class)); window.addSection((Section) GWT.create(HierarchySection.class)); window.addSection((Section) GWT.create(NetworkSection.class)); + window.addSection((Section) GWT.create(TestBenchSection.class)); if (Profiler.isEnabled()) { window.addSection((Section) GWT.create(ProfilerSection.class)); } @@ -656,6 +691,25 @@ public class ApplicationConfiguration implements EntryPoint { }-*/; /** + * Make Metro versions of IE suggest switching to the desktop when + * window.prompt is called. + */ + private static native void enableIEPromptFix() + /*-{ + var prompt = $wnd.prompt; + $wnd.prompt = function () { + var result = prompt.apply($wnd, Array.prototype.slice.call(arguments)); + if (result === undefined) { + // force the browser to suggest desktop mode + showModalDialog(); + return null; + } else { + return result; + } + }; + }-*/; + + /** * Registers that callback that the bootstrap javascript uses to start * applications once the widgetset is loaded and all required information is * available @@ -760,5 +814,4 @@ public class ApplicationConfiguration implements EntryPoint { 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 a87fa3e342..5d614439bb 100644 --- a/client/src/com/vaadin/client/ApplicationConnection.java +++ b/client/src/com/vaadin/client/ApplicationConnection.java @@ -1,4 +1,4 @@ -/* +/* * Copyright 2000-2013 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not @@ -38,6 +38,7 @@ import com.google.gwt.core.client.JsArray; 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.dom.client.Element; import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.GwtEvent; @@ -50,13 +51,13 @@ import com.google.gwt.http.client.RequestException; import com.google.gwt.http.client.Response; import com.google.gwt.http.client.URL; import com.google.gwt.json.client.JSONArray; +import com.google.gwt.json.client.JSONNumber; import com.google.gwt.json.client.JSONObject; import com.google.gwt.json.client.JSONString; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; 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.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window.ClosingEvent; @@ -64,15 +65,18 @@ import com.google.gwt.user.client.Window.ClosingHandler; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConfiguration.ErrorMessage; +import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.ResourceLoader.ResourceLoadEvent; import com.vaadin.client.ResourceLoader.ResourceLoadListener; import com.vaadin.client.communication.HasJavaScriptConnectorHelper; +import com.vaadin.client.communication.Heartbeat; 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.componentlocator.ComponentLocator; import com.vaadin.client.extensions.AbstractExtensionConnector; import com.vaadin.client.metadata.ConnectorBundleLoader; import com.vaadin.client.metadata.Method; @@ -82,6 +86,9 @@ import com.vaadin.client.metadata.Type; import com.vaadin.client.metadata.TypeData; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.AbstractConnector; +import com.vaadin.client.ui.FontIcon; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; import com.vaadin.client.ui.VContextMenu; import com.vaadin.client.ui.VNotification; import com.vaadin.client.ui.VNotification.HideEvent; @@ -91,6 +98,7 @@ import com.vaadin.client.ui.ui.UIConnector; import com.vaadin.client.ui.window.WindowConnector; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.JsonConstants; import com.vaadin.shared.Version; import com.vaadin.shared.communication.LegacyChangeVariablesInvocation; import com.vaadin.shared.communication.MethodInvocation; @@ -137,10 +145,6 @@ public class ApplicationConnection { public static final String ERROR_CLASSNAME_EXT = "-error"; - public static final char VAR_BURST_SEPARATOR = '\u001d'; - - public static final char VAR_ESCAPE_CHARACTER = '\u001b'; - /** * A string that, if found in a non-JSON response to a UIDL request, will * cause the browser to refresh the page. If followed by a colon, optional @@ -271,8 +275,6 @@ public class ApplicationConnection { /** Event bus for communication events */ private EventBus eventBus = GWT.create(SimpleEventBus.class); - private int lastResponseId = -1; - /** * The communication handler methods are called at certain points during * communication with the server. This allows for making add-ons that keep @@ -366,7 +368,7 @@ public class ApplicationConnection { * * To listen for the event add a {@link ApplicationStoppedHandler} by * invoking - * {@link ApplicationConnection#addHandler(ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} + * {@link ApplicationConnection#addHandler(ApplicationConnection.ApplicationStoppedEvent.Type, ApplicationStoppedHandler)} * to the {@link ApplicationConnection} * * @since 7.1.8 @@ -431,6 +433,8 @@ public class ApplicationConnection { private VLoadingIndicator loadingIndicator; + private Heartbeat heartbeat = GWT.create(Heartbeat.class); + public static class MultiStepDuration extends Duration { private int previousStep = elapsedMillis(); @@ -493,7 +497,7 @@ public class ApplicationConnection { getLoadingIndicator().show(); - scheduleHeartbeat(); + heartbeat.init(this); Window.addWindowClosingHandler(new ClosingHandler() { @Override @@ -540,44 +544,57 @@ public class ApplicationConnection { // initial UIDL provided in DOM, continue as if returned by request handleJSONText(jsonText, -1); + + // Tooltip can't be created earlier because the necessary fields are + // not setup to add it in the correct place in the DOM + getVTooltip().showAssistive(new TooltipInfo(" ")); } } private native void initializeTestbenchHooks( ComponentLocator componentLocator, String TTAppId) /*-{ - var ap = this; - var client = {}; - client.isActive = $entry(function() { - return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()() - || ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()(); - }); - var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); - if (vi) { - client.getVersionInfo = function() { - return vi; - } - } + var ap = this; + var client = {}; + client.isActive = $entry(function() { + return ap.@com.vaadin.client.ApplicationConnection::hasActiveRequest()() + || ap.@com.vaadin.client.ApplicationConnection::isExecutingDeferredCommands()(); + }); + var vi = ap.@com.vaadin.client.ApplicationConnection::getVersionInfo()(); + if (vi) { + client.getVersionInfo = function() { + return vi; + } + } - client.getProfilingData = $entry(function() { - var pd = [ - ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime, + client.getProfilingData = $entry(function() { + var pd = [ + ap.@com.vaadin.client.ApplicationConnection::lastProcessingTime, ap.@com.vaadin.client.ApplicationConnection::totalProcessingTime - ]; - pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo); - pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime; - return pd; - }); + ]; + pd = pd.concat(ap.@com.vaadin.client.ApplicationConnection::serverTimingInfo); + pd[pd.length] = ap.@com.vaadin.client.ApplicationConnection::bootstrapTime; + return pd; + }); - client.getElementByPath = $entry(function(id) { - return componentLocator.@com.vaadin.client.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); - }); - client.getPathForElement = $entry(function(element) { - return componentLocator.@com.vaadin.client.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element); - }); - client.initializing = false; + client.getElementByPath = $entry(function(id) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPath(Ljava/lang/String;)(id); + }); + client.getElementByPathStartingAt = $entry(function(id, element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); + }); + client.getElementsByPath = $entry(function(id) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPath(Ljava/lang/String;)(id); + }); + client.getElementsByPathStartingAt = $entry(function(id, element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getElementsByPathStartingAt(Ljava/lang/String;Lcom/google/gwt/dom/client/Element;)(id, element); + }); + client.getPathForElement = $entry(function(element) { + return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/dom/client/Element;)(element); + }); + client.initializing = false; - $wnd.vaadin.clients[TTAppId] = client; + $wnd.vaadin.clients[TTAppId] = client; }-*/; private static native final int calculateBootstrapTime() @@ -721,7 +738,7 @@ public class ApplicationConnection { }-*/; protected void repaintAll() { - makeUidlRequest("", getRepaintAllParameters()); + makeUidlRequest(new JSONArray(), getRepaintAllParameters()); } /** @@ -752,20 +769,25 @@ public class ApplicationConnection { /** * Makes an UIDL request to the server. * - * @param requestData - * Data that is passed to the server. + * @param reqInvocations + * Data containing RPC invocations and all related information. * @param extraParams * Parameters that are added as GET parameters to the url. * Contains key=value pairs joined by & characters or is empty if * no parameters should be added. Should not start with any * special character. */ - protected void makeUidlRequest(final String requestData, + protected void makeUidlRequest(final JSONArray reqInvocations, final String extraParams) { startRequest(); - // Security: double cookie submission pattern - final String payload = getCsrfToken() + VAR_BURST_SEPARATOR - + requestData; + + JSONObject payload = new JSONObject(); + payload.put(ApplicationConstants.CSRF_TOKEN, new JSONString( + getCsrfToken())); + payload.put(ApplicationConstants.RPC_INVOCATIONS, reqInvocations); + payload.put(ApplicationConstants.SERVER_SYNC_ID, new JSONNumber( + lastSeenServerSyncId)); + VConsole.log("Making UIDL Request with params: " + payload); String uri = translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX + ApplicationConstants.UIDL_PATH + '/'); @@ -789,7 +811,7 @@ public class ApplicationConnection { * @param payload * The contents of the request to send */ - protected void doUidlRequest(final String uri, final String payload) { + protected void doUidlRequest(final String uri, final JSONObject payload) { RequestCallback requestCallback = new RequestCallback() { @Override public void onError(Request request, Throwable exception) { @@ -956,14 +978,14 @@ public class ApplicationConnection { * @throws RequestException * if the request could not be sent */ - protected void doAjaxRequest(String uri, String payload, + protected void doAjaxRequest(String uri, JSONObject 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.setHeader("Content-Type", JsonConstants.JSON_CONTENT_TYPE); + rb.setRequestData(payload.toString()); rb.setCallback(requestCallback); final Request request = rb.send(); @@ -1028,6 +1050,29 @@ public class ApplicationConnection { */ private ValueMap serverTimingInfo; + /** + * Holds the last seen response id given by the server. + * <p> + * The server generates a strictly increasing id for each response to each + * request from the client. This ID is then replayed back to the server on + * each request. This helps the server in knowing in what state the client + * is, and compare it to its own state. In short, it helps with concurrent + * changes between the client and server. + * <p> + * Initial value, i.e. no responses received from the server, is + * {@link #UNDEFINED_SYNC_ID} ({@value #UNDEFINED_SYNC_ID}). This happens + * between the bootstrap HTML being loaded and the first UI being rendered; + */ + private int lastSeenServerSyncId = UNDEFINED_SYNC_ID; + + /** + * The value of an undefined sync id. + * <p> + * This must be <code>-1</code>, because of the contract in + * {@link #getLastResponseId()} + */ + private static final int UNDEFINED_SYNC_ID = -1; + static final int MAX_CSS_WAITS = 100; protected void handleWhenCSSLoaded(final String jsonText, @@ -1308,7 +1353,13 @@ public class ApplicationConnection { * @return and id identifying the response */ public int getLastResponseId() { - return lastResponseId; + /* + * The discrepancy between field name and getter name is simply historic + * - API can't be changed, but the field was repurposed in a more + * general, yet compatible, use. "Response id" was deemed unsuitable a + * name, so it was called "server sync id" instead. + */ + return lastSeenServerSyncId; } protected void handleUIDLMessage(final Date start, final String jsonText, @@ -1335,6 +1386,17 @@ public class ApplicationConnection { VConsole.log("Handling message from server"); eventBus.fireEvent(new ResponseHandlingStartedEvent(this)); + if (json.containsKey(ApplicationConstants.SERVER_SYNC_ID)) { + int syncId = json.getInt(ApplicationConstants.SERVER_SYNC_ID); + + assert (lastSeenServerSyncId == UNDEFINED_SYNC_ID || syncId == lastSeenServerSyncId + 1) : "Newly retrieved server sync id was not exactly one larger than the previous one (new: " + + syncId + ", last seen: " + lastSeenServerSyncId + ")"; + + lastSeenServerSyncId = syncId; + } else { + VConsole.error("Server response didn't contain an id."); + } + // Handle redirect if (json.containsKey("redirect")) { String url = json.getValueMap("redirect").getString("url"); @@ -1343,8 +1405,6 @@ public class ApplicationConnection { return; } - lastResponseId++; - final MultiStepDuration handleUIDLDuration = new MultiStepDuration(); // Get security key @@ -2532,15 +2592,13 @@ public class ApplicationConnection { */ private void buildAndSendVariableBurst( LinkedHashMap<String, MethodInvocation> pendingInvocations) { - final StringBuffer req = new StringBuffer(); - while (!pendingInvocations.isEmpty()) { + JSONArray reqJson = new JSONArray(); + if (!pendingInvocations.isEmpty()) { if (ApplicationConfiguration.isDebugMode()) { Util.logVariableBurst(this, pendingInvocations.values()); } - JSONArray reqJson = new JSONArray(); - for (MethodInvocation invocation : pendingInvocations.values()) { JSONArray invocationJson = new JSONArray(); invocationJson.set(0, @@ -2579,9 +2637,6 @@ public class ApplicationConnection { reqJson.set(reqJson.size(), invocationJson); } - // escape burst separators (if any) - req.append(escapeBurstContents(reqJson.toString())); - pendingInvocations.clear(); // Keep tag string short lastInvocationTag = 0; @@ -2605,7 +2660,7 @@ public class ApplicationConnection { getConfiguration().setWidgetsetVersionSent(); } - makeUidlRequest(req.toString(), extraParams); + makeUidlRequest(reqJson, extraParams); } private boolean isJavascriptRpc(MethodInvocation invocation) { @@ -2849,35 +2904,6 @@ public class ApplicationConnection { } /** - * Encode burst separator characters in a String for transport over the - * network. This protects from separator injection attacks. - * - * @param value - * to encode - * @return encoded value - */ - protected String escapeBurstContents(String value) { - final StringBuilder result = new StringBuilder(); - for (int i = 0; i < value.length(); ++i) { - char character = value.charAt(i); - switch (character) { - case VAR_ESCAPE_CHARACTER: - // fall-through - escape character is duplicated - case VAR_BURST_SEPARATOR: - result.append(VAR_ESCAPE_CHARACTER); - // encode as letters for easier reading - result.append(((char) (character + 0x30))); - break; - default: - // the char is not a special one - add it to the result as is - result.append(character); - break; - } - } - return result.toString(); - } - - /** * Does absolutely nothing. Replaced by {@link LayoutManager}. * * @param container @@ -3009,6 +3035,26 @@ public class ApplicationConnection { } /** + * Gets an {@link Icon} instance corresponding to a URI. + * + * @since 7.2 + * @param uri + * @return Icon object + */ + public Icon getIcon(String uri) { + Icon icon; + if (uri == null) { + return null; + } else if (FontIcon.isFontIconUri(uri)) { + icon = GWT.create(FontIcon.class); + } else { + icon = GWT.create(ImageIcon.class); + } + icon.setUri(translateVaadinUri(uri)); + return icon; + } + + /** * Translates custom protocols in UIDL URI's to be recognizable by browser. * All uri's from UIDL should be routed via this method before giving them * to browser due URI's in UIDL may contain custom protocols like theme://. @@ -3304,20 +3350,11 @@ public class ApplicationConnection { * interval elapses if the interval is a positive number. Otherwise, does * nothing. * - * @see #sendHeartbeat() - * @see ApplicationConfiguration#getHeartbeatInterval() + * @deprecated as of 7.2, use {@link Heartbeat#schedule()} instead */ + @Deprecated protected void scheduleHeartbeat() { - final int interval = getConfiguration().getHeartbeatInterval(); - if (interval > 0) { - VConsole.log("Scheduling heartbeat in " + interval + " seconds"); - new Timer() { - @Override - public void run() { - sendHeartbeat(); - } - }.schedule(interval * 1000); - } + heartbeat.schedule(); } /** @@ -3326,51 +3363,12 @@ public class ApplicationConnection { * Heartbeat requests are used to inform the server that the client-side is * still alive. If the client page is closed or the connection lost, the * server will eventually close the inactive UI. - * <p> - * <b>TODO</b>: Improved error handling, like in doUidlRequest(). * - * @see #scheduleHeartbeat() + * @deprecated as of 7.2, use {@link Heartbeat#send()} instead */ + @Deprecated protected void sendHeartbeat() { - final String uri = addGetParameters( - translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX - + ApplicationConstants.HEARTBEAT_PATH + '/'), - UIConstants.UI_ID_PARAMETER + "=" - + getConfiguration().getUIId()); - - final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); - - final RequestCallback callback = new RequestCallback() { - - @Override - public void onResponseReceived(Request request, Response response) { - int status = response.getStatusCode(); - if (status == Response.SC_OK) { - // TODO Permit retry in some error situations - VConsole.log("Heartbeat response OK"); - scheduleHeartbeat(); - } else if (status == Response.SC_GONE) { - showSessionExpiredError(null); - } else { - VConsole.error("Failed sending heartbeat to server. Error code: " - + status); - } - } - - @Override - public void onError(Request request, Throwable exception) { - VConsole.error("Exception sending heartbeat: " + exception); - } - }; - - rb.setCallback(callback); - - try { - VConsole.log("Sending heartbeat request..."); - rb.send(); - } catch (RequestException re) { - callback.onError(null, re); - } + heartbeat.send(); } /** diff --git a/client/src/com/vaadin/client/ComponentLocator.java b/client/src/com/vaadin/client/ComponentLocator.java index af934470c2..f30528c0c0 100644 --- a/client/src/com/vaadin/client/ComponentLocator.java +++ b/client/src/com/vaadin/client/ComponentLocator.java @@ -15,706 +15,22 @@ */ package com.vaadin.client; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import com.google.gwt.core.client.JavaScriptObject; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; -import com.google.gwt.user.client.ui.HasWidgets; -import com.google.gwt.user.client.ui.RootPanel; -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; -import com.vaadin.client.ui.orderedlayout.Slot; -import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; -import com.vaadin.client.ui.window.WindowConnector; -import com.vaadin.shared.AbstractComponentState; -import com.vaadin.shared.Connector; -import com.vaadin.shared.communication.SharedState; - /** * ComponentLocator provides methods for generating a String locator for a given * DOM element and for locating a DOM element using a String locator. + * + * @since 5.4 + * @deprecated Moved to com.vaadin.client.componentlocator.ComponentLocator */ -public class ComponentLocator { - - /** - * Separator used in the String locator between a parent and a child widget. - */ - private static final String PARENTCHILD_SEPARATOR = "/"; - - /** - * Separator used in the String locator between the part identifying the - * containing widget and the part identifying the target element within the - * widget. - */ - private static final String SUBPART_SEPARATOR = "#"; - - /** - * String that identifies the root panel when appearing first in the String - * locator. - */ - private static final String ROOT_ID = "Root"; - - /** - * Reference to ApplicationConnection instance. - */ - private ApplicationConnection client; - +public class ComponentLocator extends + com.vaadin.client.componentlocator.ComponentLocator { /** * Construct a ComponentLocator for the given ApplicationConnection. - * + * * @param client * ApplicationConnection instance for the application. */ public ComponentLocator(ApplicationConnection client) { - this.client = client; - } - - /** - * Generates a String locator which uniquely identifies the target element. - * The {@link #getElementByPath(String)} method can be used for the inverse - * operation, i.e. locating an element based on the return value from this - * method. - * <p> - * Note that getElementByPath(getPathForElement(element)) == element is not - * always true as {@link #getPathForElement(Element)} can return a path to - * another element if the widget determines an action on the other element - * will give the same result as the action on the target element. - * </p> - * - * @since 5.4 - * @param targetElement - * The element to generate a path for. - * @return A String locator that identifies the target element or null if a - * String locator could not be created. - */ - public String getPathForElement(Element targetElement) { - String pid = null; - - targetElement = getElement(targetElement); - - Element e = targetElement; - - while (true) { - pid = ConnectorMap.get(client).getConnectorId(e); - if (pid != null) { - break; - } - - e = DOM.getParent(e); - if (e == null) { - break; - } - } - - Widget w = null; - if (pid != null) { - // If we found a Paintable then we use that as reference. We should - // find the Paintable for all but very special cases (like - // overlays). - w = ((ComponentConnector) ConnectorMap.get(client) - .getConnector(pid)).getWidget(); - - /* - * Still if the Paintable contains a widget that implements - * SubPartAware, we want to use that as a reference - */ - Widget targetParent = findParentWidget(targetElement, w); - while (targetParent != w && targetParent != null) { - if (targetParent instanceof SubPartAware) { - /* - * The targetParent widget is a child of the Paintable and - * the first parent (of the targetElement) that implements - * SubPartAware - */ - w = targetParent; - break; - } - targetParent = targetParent.getParent(); - } - } - if (w == null) { - // Check if the element is part of a widget that is attached - // directly to the root panel - RootPanel rootPanel = RootPanel.get(); - int rootWidgetCount = rootPanel.getWidgetCount(); - for (int i = 0; i < rootWidgetCount; i++) { - Widget rootWidget = rootPanel.getWidget(i); - if (rootWidget.getElement().isOrHasChild(targetElement)) { - // The target element is contained by this root widget - w = findParentWidget(targetElement, rootWidget); - break; - } - } - if (w != null) { - // We found a widget but we should still see if we find a - // SubPartAware implementor (we cannot find the Paintable as - // there is no link from VOverlay to its paintable/owner). - Widget subPartAwareWidget = findSubPartAwareParentWidget(w); - if (subPartAwareWidget != null) { - w = subPartAwareWidget; - } - } - } - - if (w == null) { - // Containing widget not found - return null; - } - - // Determine the path for the target widget - String path = getPathForWidget(w); - if (path == null) { - /* - * No path could be determined for the target widget. Cannot create - * a locator string. - */ - return null; - } - - // The parent check is a work around for Firefox 15 which fails to - // compare elements properly (#9534) - if (w.getElement() == targetElement) { - /* - * We are done if the target element is the root of the target - * widget. - */ - return path; - } else if (w instanceof SubPartAware) { - /* - * If the widget can provide an identifier for the targetElement we - * let it do that - */ - String elementLocator = ((SubPartAware) w) - .getSubPartName(targetElement); - if (elementLocator != null) { - return path + SUBPART_SEPARATOR + elementLocator; - } - } - /* - * If everything else fails we use the DOM path to identify the target - * element - */ - String domPath = getDOMPathForElement(targetElement, w.getElement()); - if (domPath == null) { - return path; - } else { - return path + domPath; - } - } - - /** - * Returns the element passed to the method. Or in case of Firefox 15, - * returns the real element that is in the DOM instead of the element passed - * to the method (which is the same element but not ==). - * - * @param targetElement - * the element to return - * @return the element passed to the method - */ - private Element getElement(Element targetElement) { - if (targetElement == null) { - return null; - } - - if (!BrowserInfo.get().isFirefox()) { - return targetElement; - } - - if (BrowserInfo.get().getBrowserMajorVersion() != 15) { - return targetElement; - } - - // Firefox 15, you make me sad - if (targetElement.getNextSibling() != null) { - return (Element) targetElement.getNextSibling() - .getPreviousSibling(); - } - if (targetElement.getPreviousSibling() != null) { - return (Element) targetElement.getPreviousSibling() - .getNextSibling(); - } - // No siblings so this is the only child - return (Element) targetElement.getParentNode().getChild(0); - } - - /** - * Finds the first widget in the hierarchy (moving upwards) that implements - * SubPartAware. Returns the SubPartAware implementor or null if none is - * found. - * - * @param w - * The widget to start from. This is returned if it implements - * SubPartAware. - * @return The first widget (upwards in hierarchy) that implements - * SubPartAware or null - */ - private Widget findSubPartAwareParentWidget(Widget w) { - - while (w != null) { - if (w instanceof SubPartAware) { - return w; - } - w = w.getParent(); - } - return null; - } - - /** - * Returns the first widget found when going from {@code targetElement} - * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a - * parent of {@code targetElement}. - * - * @param targetElement - * @param ancestorWidget - * @return The widget whose root element is a parent of - * {@code targetElement}. - */ - private Widget findParentWidget(Element targetElement, Widget ancestorWidget) { - /* - * As we cannot resolve Widgets from the element we start from the - * widget and move downwards to the correct child widget, as long as we - * find one. - */ - if (ancestorWidget instanceof HasWidgets) { - for (Widget w : ((HasWidgets) ancestorWidget)) { - if (w.getElement().isOrHasChild(targetElement)) { - return findParentWidget(targetElement, w); - } - } - } - - // No children found, this is it - return ancestorWidget; - } - - /** - * Locates an element based on a DOM path and a base element. - * - * @param baseElement - * The base element which the path is relative to - * @param path - * String locator (consisting of domChild[x] parts) that - * identifies the element - * @return The element identified by path, relative to baseElement or null - * if the element could not be found. - */ - private Element getElementByDOMPath(Element baseElement, String path) { - String parts[] = path.split(PARENTCHILD_SEPARATOR); - Element element = baseElement; - - for (String part : parts) { - if (part.startsWith("domChild[")) { - String childIndexString = part.substring("domChild[".length(), - part.length() - 1); - - if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) { - if (element.hasChildNodes()) { - Element e = element.getFirstChildElement().cast(); - String cn = e.getClassName(); - if (cn != null - && (cn.equals("v-expand") || cn - .contains("v-has-caption"))) { - element = e; - } - } - } - - try { - int childIndex = Integer.parseInt(childIndexString); - element = DOM.getChild(element, childIndex); - } catch (Exception e) { - return null; - } - - if (element == null) { - return null; - } - - } - } - - return element; + super(client); } - - /** - * Generates a String locator using domChild[x] parts for the element - * relative to the baseElement. - * - * @param element - * The target element - * @param baseElement - * The starting point for the locator. The generated path is - * relative to this element. - * @return A String locator that can be used to locate the target element - * using {@link #getElementByDOMPath(Element, String)} or null if - * the locator String cannot be created. - */ - private String getDOMPathForElement(Element element, Element baseElement) { - Element e = element; - String path = ""; - while (true) { - int childIndex = -1; - Element siblingIterator = e; - while (siblingIterator != null) { - childIndex++; - siblingIterator = siblingIterator.getPreviousSiblingElement() - .cast(); - } - - path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]" - + path; - - JavaScriptObject parent = e.getParentElement(); - if (parent == null) { - return null; - } - // The parent check is a work around for Firefox 15 which fails to - // compare elements properly (#9534) - if (parent == baseElement) { - break; - } - - e = parent.cast(); - } - - return path; - } - - /** - * Locates an element using a String locator (path) which identifies a DOM - * element. The {@link #getPathForElement(Element)} method can be used for - * the inverse operation, i.e. generating a string expression for a DOM - * element. - * - * @since 5.4 - * @param path - * The String locater which identifies the target element. - * @return The DOM element identified by {@code path} or null if the element - * could not be located. - */ - public Element getElementByPath(String path) { - /* - * Path is of type "targetWidgetPath#componentPart" or - * "targetWidgetPath". - */ - String parts[] = path.split(SUBPART_SEPARATOR, 2); - String widgetPath = parts[0]; - Widget w = getWidgetFromPath(widgetPath); - if (w == null || !Util.isAttachedAndDisplayed(w)) { - return null; - } - - if (parts.length == 1) { - int pos = widgetPath.indexOf("domChild"); - if (pos == -1) { - return w.getElement(); - } - - // Contains dom reference to a sub element of the widget - String subPath = widgetPath.substring(pos); - return getElementByDOMPath(w.getElement(), subPath); - } else if (parts.length == 2) { - if (w instanceof SubPartAware) { - return ((SubPartAware) w).getSubPartElement(parts[1]); - } - } - - return null; - } - - /** - * Creates a locator String for the given widget. The path can be used to - * locate the widget using {@link #getWidgetFromPath(String)}. - * - * Returns null if no path can be determined for the widget or if the widget - * is null. - * - * @param w - * The target widget - * @return A String locator for the widget - */ - private String getPathForWidget(Widget w) { - if (w == null) { - return null; - } - String elementId = w.getElement().getId(); - 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 ""; - } else if (w instanceof VWindow) { - Connector windowConnector = ConnectorMap.get(client) - .getConnector(w); - List<WindowConnector> subWindowList = client.getUIConnector() - .getSubWindows(); - int indexOfSubWindow = subWindowList.indexOf(windowConnector); - return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; - } else if (w instanceof RootPanel) { - return ROOT_ID; - } - - Widget parent = w.getParent(); - - String basePath = getPathForWidget(parent); - if (basePath == null) { - return null; - } - String simpleName = Util.getSimpleName(w); - - /* - * Check if the parent implements Iterable. At least VPopupView does not - * implement HasWdgets so we cannot check for that. - */ - if (!(parent instanceof Iterable<?>)) { - // Parent does not implement Iterable so we cannot find out which - // child this is - return null; - } - - Iterator<?> i = ((Iterable<?>) parent).iterator(); - int pos = 0; - while (i.hasNext()) { - Object child = i.next(); - if (child == w) { - return basePath + PARENTCHILD_SEPARATOR + simpleName + "[" - + pos + "]"; - } - String simpleName2 = Util.getSimpleName(child); - if (simpleName.equals(simpleName2)) { - pos++; - } - } - - return null; - } - - /** - * Locates the widget based on a String locator. - * - * @param path - * The String locator that identifies the widget. - * @return The Widget identified by the String locator or null if the widget - * could not be identified. - */ - private Widget getWidgetFromPath(String path) { - Widget w = null; - String parts[] = path.split(PARENTCHILD_SEPARATOR); - - for (int i = 0; i < parts.length; i++) { - String part = parts[i]; - - if (part.equals(ROOT_ID)) { - w = RootPanel.get(); - } else if (part.equals("")) { - w = client.getUIConnector().getWidget(); - } else if (w == null) { - String id = part; - // Must be old static pid (PID_S*) - ServerConnector connector = ConnectorMap.get(client) - .getConnector(id); - if (connector == null) { - // Lookup by component id - // TODO Optimize this - connector = findConnectorById(client.getUIConnector(), - id.substring(5)); - } - - if (connector instanceof ComponentConnector) { - w = ((ComponentConnector) connector).getWidget(); - } else { - // Not found - return null; - } - } else if (part.startsWith("domChild[")) { - // The target widget has been found and the rest identifies the - // element - break; - } else if (w instanceof Iterable) { - // W identifies a widget that contains other widgets, as it - // should. Try to locate the child - Iterable<?> parent = (Iterable<?>) w; - - // Part is of type "VVerticalLayout[0]", split this into - // VVerticalLayout and 0 - String[] split = part.split("\\[", 2); - String widgetClassName = split[0]; - String indexString = split[1].substring(0, - split[1].length() - 1); - int widgetPosition = Integer.parseInt(indexString); - - // AbsolutePanel in GridLayout has been removed -> skip it - if (w instanceof VGridLayout - && "AbsolutePanel".equals(widgetClassName)) { - continue; - } - - // FlowPane in CSSLayout has been removed -> skip it - if (w instanceof VCssLayout - && "VCssLayout$FlowPane".equals(widgetClassName)) { - continue; - } - - // ChildComponentContainer and VOrderedLayout$Slot have been - // replaced with Slot - if (w instanceof VAbstractOrderedLayout - && ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot" - .equals(widgetClassName))) { - widgetClassName = "Slot"; - } - - if (w instanceof VTabsheetPanel && widgetPosition != 0) { - // TabSheetPanel now only contains 1 connector => the index - // 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 - * constructing a path step that would find the desired widget - * from the layout and injecting it as the next search step - * (which would originally have found the widget inside the - * ChildComponentContainer) - */ - if ((w instanceof VGridLayout) - && "ChildComponentContainer".equals(widgetClassName) - && i + 1 < parts.length) { - - HasWidgets layout = (HasWidgets) w; - - String nextPart = parts[i + 1]; - String[] nextSplit = nextPart.split("\\[", 2); - String nextWidgetClassName = nextSplit[0]; - - // Find the n:th child and count the number of children with - // the same type before it - int nextIndex = 0; - for (Widget child : layout) { - boolean matchingType = nextWidgetClassName.equals(Util - .getSimpleName(child)); - if (matchingType && widgetPosition == 0) { - // This is the n:th child that we looked for - break; - } else if (widgetPosition < 0) { - // Error if we're past the desired position without - // a match - return null; - } else if (matchingType) { - // If this was another child of the expected type, - // increase the count for the next step - nextIndex++; - } - - // Don't count captions - if (!(child instanceof VCaption)) { - widgetPosition--; - } - } - - // Advance to the next step, this time checking for the - // actual child widget - parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']'; - continue; - } - - // Locate the child - Iterator<? extends Widget> iterator; - - /* - * VWindow and VContextMenu workarounds for backwards - * compatibility - */ - if (widgetClassName.equals("VWindow")) { - List<WindowConnector> windows = client.getUIConnector() - .getSubWindows(); - List<VWindow> windowWidgets = new ArrayList<VWindow>( - windows.size()); - for (WindowConnector wc : windows) { - windowWidgets.add(wc.getWidget()); - } - iterator = windowWidgets.iterator(); - } else if (widgetClassName.equals("VContextMenu")) { - return client.getContextMenu(); - } else { - iterator = (Iterator<? extends Widget>) parent.iterator(); - } - - boolean ok = false; - - // Find the widgetPosition:th child of type "widgetClassName" - while (iterator.hasNext()) { - - Widget child = iterator.next(); - String simpleName2 = Util.getSimpleName(child); - - if (!widgetClassName.equals(simpleName2) - && child instanceof Slot) { - /* - * Support legacy tests without any selector for the - * Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by - * directly checking the stuff inside the slot - */ - child = ((Slot) child).getWidget(); - simpleName2 = Util.getSimpleName(child); - } - - if (widgetClassName.equals(simpleName2)) { - if (widgetPosition == 0) { - w = child; - ok = true; - break; - } - widgetPosition--; - - } - } - - if (!ok) { - // Did not find the child - return null; - } - } else { - // W identifies something that is not a "HasWidgets". This - // should not happen as all widget containers should implement - // HasWidgets. - return null; - } - } - - return w; - } - - private ServerConnector findConnectorById(ServerConnector root, String id) { - SharedState state = root.getState(); - if (state instanceof AbstractComponentState - && id.equals(((AbstractComponentState) state).id)) { - return root; - } - for (ServerConnector child : root.getChildren()) { - ServerConnector found = findConnectorById(child, id); - if (found != null) { - return found; - } - } - - return null; - } - } diff --git a/client/src/com/vaadin/client/ConnectorMap.java b/client/src/com/vaadin/client/ConnectorMap.java index 810f12824a..c2f1eda21d 100644 --- a/client/src/com/vaadin/client/ConnectorMap.java +++ b/client/src/com/vaadin/client/ConnectorMap.java @@ -116,7 +116,7 @@ public class ConnectorMap { * no connector was found */ public ComponentConnector getConnector(Widget widget) { - return getConnector(widget.getElement()); + return widget == null ? null : getConnector(widget.getElement()); } public void registerConnector(String id, ServerConnector connector) { diff --git a/client/src/com/vaadin/client/LayoutManager.java b/client/src/com/vaadin/client/LayoutManager.java index 5b27031a29..69f3f08144 100644 --- a/client/src/com/vaadin/client/LayoutManager.java +++ b/client/src/com/vaadin/client/LayoutManager.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; import com.google.gwt.core.client.Duration; import com.google.gwt.core.client.JsArrayString; @@ -827,6 +828,12 @@ public class LayoutManager { /** * Marks that a ManagedLayout should be layouted in the next layout phase * even if none of the elements managed by the layout have been resized. + * <p> + * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. * * @param layout * the managed layout that should be layouted @@ -840,14 +847,25 @@ public class LayoutManager { * Marks that a ManagedLayout should be layouted horizontally in the next * layout phase even if none of the elements managed by the layout have been * resized horizontally. - * + * <p> * For SimpleManagedLayout which is always layouted in both directions, this * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * <p> + * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. * * @param layout * the managed layout that should be layouted */ public final void setNeedsHorizontalLayout(ManagedLayout layout) { + if (isLayoutRunning()) { + getLogger() + .warning( + "setNeedsHorizontalLayout should not be run while a layout phase is in progress."); + } needsHorizontalLayout.add(layout.getConnectorId()); } @@ -855,14 +873,25 @@ public class LayoutManager { * Marks that a ManagedLayout should be layouted vertically in the next * layout phase even if none of the elements managed by the layout have been * resized vertically. - * + * <p> * For SimpleManagedLayout which is always layouted in both directions, this * has the same effect as {@link #setNeedsLayout(ManagedLayout)}. + * <p> + * This method should not be invoked during a layout phase since it only + * controls what will happen in the beginning of the next phase. If you want + * to explicitly cause some layout to be considered in an ongoing layout + * phase, you should use {@link #setNeedsMeasure(ComponentConnector)} + * instead. * * @param layout * the managed layout that should be layouted */ public final void setNeedsVerticalLayout(ManagedLayout layout) { + if (isLayoutRunning()) { + getLogger() + .warning( + "setNeedsVerticalLayout should not be run while a layout phase is in progress."); + } needsVerticalLayout.add(layout.getConnectorId()); } @@ -1038,6 +1067,98 @@ public class LayoutManager { } /** + * Gets the top border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured top border of the element in pixels. + */ + public int getBorderTop(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderTop(); + } + + /** + * Gets the left border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured left border of the element in pixels. + */ + public int getBorderLeft(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderLeft(); + } + + /** + * Gets the bottom border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured bottom border of the element in pixels. + */ + public int getBorderBottom(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderBottom(); + } + + /** + * Gets the right border of the given element, provided that it has been + * measured. These elements are guaranteed to be measured: + * <ul> + * <li>ManagedLayotus and their child Connectors + * <li>Elements for which there is at least one ElementResizeListener + * <li>Elements for which at least one ManagedLayout has registered a + * dependency + * </ul> + * + * A negative number is returned if the element has not been measured. If 0 + * is returned, it might indicate that the element is not attached to the + * DOM. + * + * @param element + * the element to get the measured size for + * @return the measured right border of the element in pixels. + */ + public int getBorderRight(Element element) { + assert needsMeasure(element) : "Getting measurement for element that is not measured"; + return getMeasuredSize(element, nullSize).getBorderRight(); + } + + /** * Gets the padding width (left padding + right padding) of the given * element, provided that it has been measured. These elements are * guaranteed to be measured: @@ -1458,10 +1579,15 @@ public class LayoutManager { /** * Informs this LayoutManager that the size of a component might have - * changed. If there is no upcoming layout phase, a new layout phase is - * scheduled. This method should be used whenever a size might have changed - * from outside of Vaadin's normal update phase, e.g. when an icon has been - * loaded or when the user resizes some part of the UI using the mouse. + * changed. This method should be used whenever the size of an individual + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when an icon has been loaded or when the user resizes some + * part of the UI using the mouse. + * <p> + * To set an entire component hierarchy to be measured, use + * {@link #setNeedsMeasureRecursively(ComponentConnector)} instead. + * <p> + * If there is no upcoming layout phase, a new layout phase is scheduled. * * @param component * the component whose size might have changed. @@ -1475,6 +1601,33 @@ public class LayoutManager { } } + /** + * Informs this LayoutManager that some sizes in a component hierarchy might + * have changed. This method should be used whenever the size of any child + * component might have changed from outside of Vaadin's normal update + * phase, e.g. when a CSS class name related to sizing has been changed. + * <p> + * To set a single component to be measured, use + * {@link #setNeedsMeasure(ComponentConnector)} instead. + * <p> + * If there is no upcoming layout phase, a new layout phase is scheduled. + * + * @since 7.2 + * @param component + * the component at the root of the component hierarchy to + * measure + */ + public void setNeedsMeasureRecursively(ComponentConnector component) { + setNeedsMeasure(component); + + if (component instanceof HasComponentsConnector) { + HasComponentsConnector hasComponents = (HasComponentsConnector) component; + for (ComponentConnector child : hasComponents.getChildComponents()) { + setNeedsMeasureRecursively(child); + } + } + } + public void setEverythingNeedsMeasure() { everythingNeedsMeasure = true; } @@ -1485,4 +1638,8 @@ public class LayoutManager { protected void cleanMeasuredSizes() { } + private static Logger getLogger() { + return Logger.getLogger(LayoutManager.class.getName()); + } + } diff --git a/client/src/com/vaadin/client/Profiler.java b/client/src/com/vaadin/client/Profiler.java index 083f2559b1..cfce59b08b 100644 --- a/client/src/com/vaadin/client/Profiler.java +++ b/client/src/com/vaadin/client/Profiler.java @@ -297,10 +297,6 @@ public class Profiler { if (isEnabled()) { double now = Duration.currentTimeMillis(); - StringBuilder stringBuilder = new StringBuilder( - "Time since window.performance.timing events"); - SimpleTree tree = new SimpleTree(stringBuilder.toString()); - String[] keys = new String[] { "navigationStart", "unloadEventStart", "unloadEventEnd", "redirectStart", "redirectEnd", "fetchStart", "domainLookupStart", diff --git a/client/src/com/vaadin/client/RenderInformation.java b/client/src/com/vaadin/client/RenderInformation.java index e1ad9a8999..4d856e90ee 100644 --- a/client/src/com/vaadin/client/RenderInformation.java +++ b/client/src/com/vaadin/client/RenderInformation.java @@ -15,7 +15,8 @@ */ package com.vaadin.client; -import com.google.gwt.user.client.Element; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; /** * Contains size information about a rendered container and its content area. @@ -51,8 +52,11 @@ public class RenderInformation { * @param widget * * @return true if the size has changed since last update + * @deprecated As of 7.2, call and override {@link #updateSize(Element)} + * instead */ - public boolean updateSize(Element element) { + @Deprecated + public boolean updateSize(com.google.gwt.user.client.Element element) { Size newSize = new Size(element.getOffsetWidth(), element.getOffsetHeight()); if (newSize.equals(renderedSize)) { @@ -63,6 +67,19 @@ public class RenderInformation { } } + /** + * Update the size of the widget. + * + * @param widget + * + * @return true if the size has changed since last update + * + * @since 7.2 + */ + public boolean updateSize(Element element) { + return updateSize(DOM.asOld(element)); + } + @Override public String toString() { return "RenderInformation [contentArea=" + contentArea diff --git a/client/src/com/vaadin/client/SimpleTree.java b/client/src/com/vaadin/client/SimpleTree.java index 7370496cb8..39e76a6d75 100644 --- a/client/src/com/vaadin/client/SimpleTree.java +++ b/client/src/com/vaadin/client/SimpleTree.java @@ -17,6 +17,7 @@ package com.vaadin.client; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Cursor; @@ -30,7 +31,7 @@ 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.DOM; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; @@ -43,7 +44,7 @@ import com.google.gwt.user.client.ui.Widget; */ @Deprecated public class SimpleTree extends ComplexPanel implements HasDoubleClickHandlers { - private Element children = Document.get().createDivElement().cast(); + private Element children = Document.get().createDivElement(); private SpanElement handle = Document.get().createSpanElement(); private SpanElement text = Document.get().createSpanElement(); @@ -116,6 +117,14 @@ public class SimpleTree extends ComplexPanel implements HasDoubleClickHandlers { } } + public boolean isOpen() { + return "-".equals(handle.getInnerHTML()); + } + + public String getCaption() { + return text.getInnerText(); + } + public SimpleTree(String caption) { this(); setText(caption); @@ -135,14 +144,32 @@ public class SimpleTree extends ComplexPanel implements HasDoubleClickHandlers { add(child, children); } + /** + * {@inheritDoc} + * + * @deprecated As of 7.2, call and override {@link #add(Widget, Element)} + * instead. + */ @Override - protected void add(Widget child, Element container) { + @Deprecated + protected void add(Widget child, + com.google.gwt.user.client.Element container) { super.add(child, container); handle.getStyle().setDisplay(Display.INLINE_BLOCK); getElement().getStyle().setPaddingLeft(3, Unit.PX); } /** + * {@inheritDoc} + * + * @since 7.2 + */ + @Override + protected void add(Widget child, Element container) { + add(child, DOM.asOld(container)); + } + + /** * {@inheritDoc} Events are not fired when double clicking child widgets. */ @Override diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java index aae3dd5458..730f844985 100644 --- a/client/src/com/vaadin/client/Util.java +++ b/client/src/com/vaadin/client/Util.java @@ -27,6 +27,7 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.DivElement; 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.dom.client.Node; import com.google.gwt.dom.client.NodeList; @@ -35,7 +36,6 @@ import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Touch; 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.Event; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.Window; @@ -75,7 +75,8 @@ public class Util { * @param y * @return the element at given coordinates */ - public static native Element getElementFromPoint(int clientX, int clientY) + public static native com.google.gwt.user.client.Element getElementFromPoint( + int clientX, int clientY) /*-{ var el = $wnd.document.elementFromPoint(clientX, clientY); // Call elementFromPoint two times to make sure IE8 also returns something sensible if the application is running in an iframe @@ -196,7 +197,8 @@ public class Util { * clone child tree also * @return */ - public static native Element cloneNode(Element element, boolean deep) + public static native com.google.gwt.user.client.Element cloneNode( + Element element, boolean deep) /*-{ return element.cloneNode(deep); }-*/; @@ -789,7 +791,7 @@ public class Util { if (connector != null) { // check that inside the rootElement while (browseElement != null && browseElement != rootElement) { - browseElement = (Element) browseElement.getParentElement(); + browseElement = browseElement.getParentElement(); } if (browseElement != rootElement) { return null; @@ -798,7 +800,7 @@ public class Util { } } - browseElement = (Element) browseElement.getParentElement(); + browseElement = browseElement.getParentElement(); } // No connector found, element is possibly inside a VOverlay @@ -855,6 +857,7 @@ public class Util { * @param class1 * the Widget type to seek for */ + @SuppressWarnings("unchecked") public static <T> T findWidget(Element element, Class<? extends Widget> class1) { if (element != null) { @@ -863,7 +866,7 @@ public class Util { while (eventListener == null && element != null) { eventListener = Event.getEventListener(element); if (eventListener == null) { - element = (Element) element.getParentElement(); + element = element.getParentElement(); } } if (eventListener instanceof Widget) { @@ -946,7 +949,7 @@ public class Util { NodeList<com.google.gwt.dom.client.Element> imgElements = element .getElementsByTagName("img"); for (int i = 0; i < imgElements.getLength(); i++) { - DOM.sinkEvents((Element) imgElements.getItem(i), Event.ONLOAD); + DOM.sinkEvents(imgElements.getItem(i), Event.ONLOAD); } } @@ -1073,7 +1076,8 @@ public class Util { * the mouse event to get coordinates from * @return the element at the coordinates of the event */ - public static Element getElementUnderMouse(NativeEvent event) { + public static com.google.gwt.user.client.Element getElementUnderMouse( + NativeEvent event) { int pageX = getTouchOrMouseClientX(event); int pageY = getTouchOrMouseClientY(event); @@ -1180,7 +1184,7 @@ public class Util { * * @return The active element or null if no active element could be found. */ - public native static Element getFocusedElement() + public native static com.google.gwt.user.client.Element getFocusedElement() /*-{ if ($wnd.document.activeElement) { return $wnd.document.activeElement; @@ -1196,7 +1200,7 @@ public class Util { * @deprecated Use #getFocusedElement instead */ @Deprecated - public static Element getIEFocusedElement() { + public static com.google.gwt.user.client.Element getIEFocusedElement() { return getFocusedElement(); } diff --git a/client/src/com/vaadin/client/VCaption.java b/client/src/com/vaadin/client/VCaption.java index d0338de4a1..1b98a0fa78 100644 --- a/client/src/com/vaadin/client/VCaption.java +++ b/client/src/com/vaadin/client/VCaption.java @@ -17,13 +17,14 @@ package com.vaadin.client; import com.google.gwt.aria.client.Roles; +import com.google.gwt.dom.client.Element; 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.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.ImageIcon; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.AbstractComponentState; import com.vaadin.shared.AbstractFieldState; @@ -42,6 +43,8 @@ public class VCaption extends HTML { private Icon icon; + private String iconAltText = ""; + private Element captionText; private final ApplicationConnection client; @@ -104,6 +107,7 @@ public class VCaption extends HTML { if (null != owner) { AriaHelper.bindCaption(owner.getWidget(), getElement()); } + } @Override @@ -112,6 +116,8 @@ public class VCaption extends HTML { if (null != owner) { AriaHelper.bindCaption(owner.getWidget(), null); + AriaHelper.handleInputInvalid(owner.getWidget(), false); + AriaHelper.handleInputRequired(owner.getWidget(), false); } } @@ -155,25 +161,27 @@ public class VCaption extends HTML { showRequired = ((AbstractFieldConnector) owner).isRequired(); } + if (icon != null) { + getElement().removeChild(icon.getElement()); + icon = null; + } if (hasIcon) { - if (icon == null) { - icon = new Icon(client); + String uri = owner.getState().resources.get( + ComponentConstants.ICON_RESOURCE).getURL(); + + icon = client.getIcon(uri); + + if (icon instanceof ImageIcon) { + // onload will set appropriate size later icon.setWidth("0"); icon.setHeight("0"); - - DOM.insertChild(getElement(), icon.getElement(), - getInsertPosition(InsertPosition.ICON)); } - // Icon forces the caption to be above the component - placedAfterComponent = false; - icon.setUri(owner.getState().resources.get( - ComponentConstants.ICON_RESOURCE).getURL()); + DOM.insertChild(getElement(), icon.getElement(), + getInsertPosition(InsertPosition.ICON)); - } else if (icon != null) { - // Remove existing - DOM.removeChild(getElement(), icon.getElement()); - icon = null; + // Icon forces the caption to be above the component + placedAfterComponent = false; } if (owner.getState().caption != null) { @@ -300,6 +308,14 @@ public class VCaption extends HTML { @Deprecated public boolean updateCaptionWithoutOwner(String caption, boolean disabled, boolean hasDescription, boolean hasError, String iconURL) { + return updateCaptionWithoutOwner(caption, disabled, hasDescription, + hasError, iconURL, ""); + } + + @Deprecated + public boolean updateCaptionWithoutOwner(String caption, boolean disabled, + boolean hasDescription, boolean hasError, String iconURL, + String iconAltText) { boolean wasPlacedAfterComponent = placedAfterComponent; // Caption is placed after component unless there is some part which @@ -320,24 +336,24 @@ public class VCaption extends HTML { } boolean hasIcon = iconURL != null; + if (icon != null) { + getElement().removeChild(icon.getElement()); + icon = null; + } if (hasIcon) { - if (icon == null) { - icon = new Icon(client); + icon = client.getIcon(iconURL); + if (icon instanceof ImageIcon) { + // onload sets appropriate size later icon.setWidth("0"); icon.setHeight("0"); - - DOM.insertChild(getElement(), icon.getElement(), - getInsertPosition(InsertPosition.ICON)); } + icon.setAlternateText(iconAltText); + DOM.insertChild(getElement(), icon.getElement(), + getInsertPosition(InsertPosition.ICON)); + // Icon forces the caption to be above the component placedAfterComponent = false; - icon.setUri(iconURL); - - } else if (icon != null) { - // Remove existing - DOM.removeChild(getElement(), icon.getElement()); - icon = null; } if (caption != null) { @@ -653,8 +669,8 @@ public class VCaption extends HTML { return tooltipInfo; } - protected Element getTextElement() { - return captionText; + protected com.google.gwt.user.client.Element getTextElement() { + return DOM.asOld(captionText); } public static String getCaptionOwnerPid(Element e) { diff --git a/client/src/com/vaadin/client/VErrorMessage.java b/client/src/com/vaadin/client/VErrorMessage.java index 2e42b98a05..77b3970aba 100644 --- a/client/src/com/vaadin/client/VErrorMessage.java +++ b/client/src/com/vaadin/client/VErrorMessage.java @@ -16,8 +16,8 @@ package com.vaadin.client; +import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Widget; @@ -31,9 +31,6 @@ public class VErrorMessage extends FlowPanel { public VErrorMessage() { super(); setStyleName(CLASSNAME); - - // Needed for binding with WAI-ARIA attributes - getElement().setId(DOM.createUniqueId()); } /** @@ -64,8 +61,10 @@ public class VErrorMessage extends FlowPanel { * Shows this error message next to given element. * * @param indicatorElement + * @deprecated As of 7.2, call and override {@link #showAt(Element)} instead */ - public void showAt(Element indicatorElement) { + @Deprecated + public void showAt(com.google.gwt.user.client.Element indicatorElement) { VOverlay errorContainer = (VOverlay) getParent(); if (errorContainer == null) { errorContainer = new VOverlay(); @@ -85,6 +84,17 @@ public class VErrorMessage extends FlowPanel { } + /** + * Shows this error message next to given element. + * + * @param indicatorElement + * + * @since 7.2 + */ + public void showAt(Element indicatorElement) { + showAt(DOM.asOld(indicatorElement)); + } + public void hide() { final VOverlay errorContainer = (VOverlay) getParent(); if (errorContainer != null) { diff --git a/client/src/com/vaadin/client/VLoadingIndicator.java b/client/src/com/vaadin/client/VLoadingIndicator.java index 3b6cf2252c..3a6f8e08bb 100644 --- a/client/src/com/vaadin/client/VLoadingIndicator.java +++ b/client/src/com/vaadin/client/VLoadingIndicator.java @@ -16,10 +16,10 @@ package com.vaadin.client; +import com.google.gwt.dom.client.Element; 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; /** @@ -228,14 +228,14 @@ public class VLoadingIndicator { * * @return The loading indicator DOM element */ - public Element getElement() { + public com.google.gwt.user.client.Element getElement() { if (element == null) { element = DOM.createDiv(); element.getStyle().setPosition(Position.ABSOLUTE); getConnection().getUIConnector().getWidget().getElement() .appendChild(element); } - return element; + return DOM.asOld(element); } } diff --git a/client/src/com/vaadin/client/VTooltip.java b/client/src/com/vaadin/client/VTooltip.java index 6191821988..9badd0ca1c 100644 --- a/client/src/com/vaadin/client/VTooltip.java +++ b/client/src/com/vaadin/client/VTooltip.java @@ -15,8 +15,10 @@ */ package com.vaadin.client; -import com.google.gwt.aria.client.Id; +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.dom.client.Element; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; @@ -29,19 +31,18 @@ import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.MouseMoveEvent; import com.google.gwt.event.dom.client.MouseMoveHandler; 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.FlowPanel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ui.VOverlay; +import com.vaadin.client.ui.VWindowOverlay; /** * TODO open for extension */ -public class VTooltip extends VOverlay { +public class VTooltip extends VWindowOverlay { private static final String CLASSNAME = "v-tooltip"; private static final int MARGIN = 4; public static final int TOOLTIP_EVENTS = Event.ONKEYDOWN @@ -57,7 +58,6 @@ public class VTooltip extends VOverlay { private boolean justClosed = false; private String uniqueId = DOM.createUniqueId(); - private Element layoutElement; private int maxWidth; // Delays for the tooltip, configurable on the server side @@ -80,17 +80,28 @@ public class VTooltip extends VOverlay { setWidget(layout); layout.add(em); DOM.setElementProperty(description, "className", CLASSNAME + "-text"); - - layoutElement = layout.getElement(); - DOM.appendChild(layoutElement, description); + DOM.appendChild(layout.getElement(), description); setSinkShadowEvents(true); - // Used to bind the tooltip to the owner for assistive devices - layoutElement.setId(uniqueId); + // When a tooltip is shown, the content of the tooltip changes. With a + // tooltip being a live-area, this change is notified to a assistive + // device. + Roles.getTooltipRole().set(getElement()); + Roles.getTooltipRole().setAriaLiveProperty(getElement(), + LiveValue.ASSERTIVE); + Roles.getTooltipRole().setAriaRelevantProperty(getElement(), + RelevantValue.ADDITIONS); + } - description.setId(DOM.createUniqueId()); - Roles.getTooltipRole().set(layoutElement); - Roles.getTooltipRole().setAriaHiddenState(layoutElement, true); + /** + * Show the tooltip with the provided info for assistive devices. + * + * @param info + * with the content of the tooltip + */ + public void showAssistive(TooltipInfo info) { + updatePosition(null, true); + show(info); } /** @@ -229,10 +240,11 @@ public class VTooltip extends VOverlay { @Override public void hide() { - super.hide(); - Roles.getTooltipRole().setAriaHiddenState(layoutElement, true); - Roles.getTooltipRole().removeAriaDescribedbyProperty( - tooltipEventHandler.currentElement); + em.updateMessage(""); + description.setInnerHTML(""); + + updatePosition(null, true); + setPopupPosition(tooltipEventMouseX, tooltipEventMouseY); } private int tooltipEventMouseX; @@ -287,9 +299,9 @@ public class VTooltip extends VOverlay { private com.google.gwt.dom.client.Element currentElement = null; /** - * Current element focused + * Marker for handling of tooltip through focus */ - private boolean currentIsFocused; + private boolean handledByFocus; /** * Current tooltip active @@ -392,44 +404,37 @@ public class VTooltip extends VOverlay { */ @Override public void onBlur(BlurEvent be) { + handledByFocus = false; 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()); + Element element = Element.as(event.getEventTarget()); // We can ignore move event if it's handled by move or over already - if (currentElement == element && currentIsFocused == isFocused) { + if (currentElement == element && handledByFocus == true) { return; } - boolean connectorAndTooltipFound = resolveConnector((com.google.gwt.user.client.Element) element); + boolean connectorAndTooltipFound = resolveConnector(element); if (!connectorAndTooltipFound) { if (isShowing()) { handleHideEvent(); - Roles.getButtonRole() - .removeAriaDescribedbyProperty(element); } else { currentTooltipInfo = null; } } else { updatePosition(event, isFocused); - if (isShowing()) { + if (isShowing() && !isFocused) { replaceCurrentTooltip(); - Roles.getTooltipRole().removeAriaDescribedbyProperty( - currentElement); } else { showTooltip(); } - - Roles.getTooltipRole().setAriaDescribedbyProperty(element, - Id.of(uniqueId)); } - currentIsFocused = isFocused; + handledByFocus = isFocused; currentElement = element; } } @@ -464,9 +469,11 @@ public class VTooltip extends VOverlay { @Override public void setPopupPositionAndShow(PositionCallback callback) { - super.setPopupPositionAndShow(callback); - - Roles.getTooltipRole().setAriaHiddenState(layoutElement, false); + if (isAttached()) { + callback.setPosition(getOffsetWidth(), getOffsetHeight()); + } else { + super.setPopupPositionAndShow(callback); + } } /** diff --git a/client/src/com/vaadin/client/annotations/OnStateChange.java b/client/src/com/vaadin/client/annotations/OnStateChange.java new file mode 100644 index 0000000000..8223507b7f --- /dev/null +++ b/client/src/com/vaadin/client/annotations/OnStateChange.java @@ -0,0 +1,50 @@ +/* + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.vaadin.client.communication.StateChangeEvent; + +/** + * Marks a method in Connector classes that should be used to handle changes to + * specific properties in the connector's shared state. + * <p> + * The annotated method will be called whenever at least one of the named state + * properties have changed. If multiple listened properties are changed by the + * same {@link StateChangeEvent}, the method will only be called once. + * <p> + * If there is no state variable with the provided name, the widgetset + * compilation will fail. + * + * @since 7.2 + * @author Vaadin Ltd + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@Documented +public @interface OnStateChange { + /** + * Defines a list of property names to listen for. + * + * @return an array of property names, should contain at least one item + */ + public String[] value(); +} diff --git a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java index f9bff8199e..f0e3eb5b48 100644 --- a/client/src/com/vaadin/client/communication/AtmospherePushConnection.java +++ b/client/src/com/vaadin/client/communication/AtmospherePushConnection.java @@ -20,6 +20,7 @@ import java.util.ArrayList; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Command; import com.vaadin.client.ApplicationConfiguration; import com.vaadin.client.ApplicationConnection; @@ -110,7 +111,7 @@ public class AtmospherePushConnection implements PushConnection { private JavaScriptObject socket; - private ArrayList<String> messageQueue = new ArrayList<String>(); + private ArrayList<JSONObject> messageQueue = new ArrayList<JSONObject>(); private State state = State.CONNECT_PENDING; @@ -191,14 +192,8 @@ public class AtmospherePushConnection implements PushConnection { } } - /* - * (non-Javadoc) - * - * @see - * com.vaadin.client.communication.PushConenction#push(java.lang.String) - */ @Override - public void push(String message) { + public void push(JSONObject message) { switch (state) { case CONNECT_PENDING: assert isActive(); @@ -210,12 +205,13 @@ public class AtmospherePushConnection implements PushConnection { VConsole.log("Sending push message: " + message); if (transport.equals("websocket")) { - FragmentedMessage fragmented = new FragmentedMessage(message); + FragmentedMessage fragmented = new FragmentedMessage( + message.toString()); while (fragmented.hasNextFragment()) { doPush(socket, fragmented.getNextFragment()); } } else { - doPush(socket, message); + doPush(socket, message.toString()); } break; case DISCONNECT_PENDING: @@ -254,7 +250,7 @@ public class AtmospherePushConnection implements PushConnection { switch (state) { case CONNECT_PENDING: state = State.CONNECTED; - for (String message : messageQueue) { + for (JSONObject message : messageQueue) { push(message); } messageQueue.clear(); @@ -448,13 +444,13 @@ public class AtmospherePushConnection implements PushConnection { return { transport: 'websocket', maxStreamingLength: 1000000, - fallbackTransport: 'streaming', + fallbackTransport: 'long-polling', contentType: 'application/json; charset=UTF-8', reconnectInterval: 5000, timeout: -1, maxReconnectOnClose: 10000000, trackMessageLength: true, - enableProtocol: false, + enableProtocol: true, messageDelimiter: String.fromCharCode(@com.vaadin.shared.communication.PushConstants::MESSAGE_DELIMITER) }; }-*/; diff --git a/client/src/com/vaadin/client/communication/Date_Serializer.java b/client/src/com/vaadin/client/communication/Date_Serializer.java new file mode 100644 index 0000000000..c6eb7af188 --- /dev/null +++ b/client/src/com/vaadin/client/communication/Date_Serializer.java @@ -0,0 +1,44 @@ +/* + * 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.Date; + +import com.google.gwt.json.client.JSONNumber; +import com.google.gwt.json.client.JSONValue; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.metadata.Type; + +/** + * Client side serializer/deserializer for java.util.Date + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class Date_Serializer implements JSONSerializer<Date> { + + @Override + public Date deserialize(Type type, JSONValue jsonValue, + ApplicationConnection connection) { + return new Date((long) ((JSONNumber) jsonValue).doubleValue()); + } + + @Override + public JSONValue serialize(Date value, ApplicationConnection connection) { + return new JSONNumber(value.getTime()); + } + +} diff --git a/client/src/com/vaadin/client/communication/Heartbeat.java b/client/src/com/vaadin/client/communication/Heartbeat.java new file mode 100644 index 0000000000..4b80827127 --- /dev/null +++ b/client/src/com/vaadin/client/communication/Heartbeat.java @@ -0,0 +1,171 @@ +/* + * 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.logging.Logger; + +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.user.client.Timer; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; +import com.vaadin.shared.ApplicationConstants; +import com.vaadin.shared.ui.ui.UIConstants; + +/** + * Handles sending of heartbeats to the server and reacting to the response + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class Heartbeat { + + private int interval = -1; + private Timer timer = new Timer() { + @Override + public void run() { + send(); + } + }; + + private ApplicationConnection connection; + + private static Logger getLogger() { + return Logger.getLogger(Heartbeat.class.getName()); + } + + /** + * Initializes the heartbeat for the given application connection + * + * @param connection + * the connection + */ + public void init(ApplicationConnection connection) { + this.connection = connection; + interval = connection.getConfiguration().getHeartbeatInterval(); + setInterval(interval); + schedule(); + + connection.addHandler( + ApplicationConnection.ApplicationStoppedEvent.TYPE, + new ApplicationConnection.ApplicationStoppedHandler() { + + @Override + public void onApplicationStopped( + ApplicationStoppedEvent event) { + setInterval(-1); + schedule(); + } + }); + + } + + /** + * Sends a heartbeat to the server + */ + public void send() { + final String uri = ApplicationConnection.addGetParameters( + getConnection().translateVaadinUri( + ApplicationConstants.APP_PROTOCOL_PREFIX + + ApplicationConstants.HEARTBEAT_PATH + '/'), + UIConstants.UI_ID_PARAMETER + "=" + + getConnection().getConfiguration().getUIId()); + + final RequestBuilder rb = new RequestBuilder(RequestBuilder.POST, uri); + + final RequestCallback callback = new RequestCallback() { + + @Override + public void onResponseReceived(Request request, Response response) { + int status = response.getStatusCode(); + if (status == Response.SC_OK) { + // TODO Permit retry in some error situations + getLogger().fine("Heartbeat response OK"); + schedule(); + } else if (status == Response.SC_GONE) { + // FIXME This should really do something else like send an + // event + getConnection().showSessionExpiredError(null); + } else { + getLogger().warning( + "Failed sending heartbeat to server. Error code: " + + status); + } + } + + @Override + public void onError(Request request, Throwable exception) { + getLogger().severe("Exception sending heartbeat: " + exception); + } + }; + + rb.setCallback(callback); + + try { + getLogger().fine("Sending heartbeat request..."); + rb.send(); + } catch (RequestException re) { + callback.onError(null, re); + } + + } + + /** + * @return the interval at which heartbeat requests are sent + */ + public int getInterval() { + return interval; + } + + /** + * sets the interval at which heartbeat requests are sent + * + * @param interval + * the new interval + */ + public void setInterval(int interval) { + this.interval = interval; + } + + /** + * Updates the schedule of the heartbeat to match the set interval. A + * negative interval disables the heartbeat. + */ + public void schedule() { + if (getInterval() > 0) { + getLogger() + .fine("Scheduling heartbeat in " + interval + " seconds"); + timer.schedule(interval * 1000); + } else { + if (timer != null) { + getLogger().fine("Disabling heartbeat"); + timer.cancel(); + } + } + + } + + /** + * @return the application connection + */ + protected ApplicationConnection getConnection() { + return connection; + } + +} diff --git a/client/src/com/vaadin/client/communication/JSONSerializer.java b/client/src/com/vaadin/client/communication/JSONSerializer.java index e5829ece24..a4e78e503c 100644 --- a/client/src/com/vaadin/client/communication/JSONSerializer.java +++ b/client/src/com/vaadin/client/communication/JSONSerializer.java @@ -23,14 +23,17 @@ import com.vaadin.client.metadata.Type; /** * Implementors of this interface knows how to serialize an Object of a given * type to JSON and how to deserialize the JSON back into an object. - * + * <p> * The {@link #serialize(Object, ApplicationConnection)} and * {@link #deserialize(Type, JSONValue, ApplicationConnection)} methods must be * symmetric so they can be chained and produce the original result (or an equal * result). - * + * <p> * Each {@link JSONSerializer} implementation can handle an object of a single * type - see {@link Type#findSerializer()}. + * <p> + * This is the client side interface, see + * com.vaadin.server.communication.JSONSerializer for the server side interface. * * @since 7.0 */ diff --git a/client/src/com/vaadin/client/communication/PushConnection.java b/client/src/com/vaadin/client/communication/PushConnection.java index a7eba224be..ba79af9d2c 100644 --- a/client/src/com/vaadin/client/communication/PushConnection.java +++ b/client/src/com/vaadin/client/communication/PushConnection.java @@ -16,6 +16,7 @@ package com.vaadin.client.communication; +import com.google.gwt.json.client.JSONObject; import com.google.gwt.user.client.Command; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ApplicationConnection.CommunicationErrorHandler; @@ -53,14 +54,14 @@ public interface PushConnection { * replay those messages in the original order when the connection has been * established. * - * @param message - * the message to push + * @param payload + * the payload to push * @throws IllegalStateException * if this connection is not active * * @see #isActive() */ - public void push(String message); + public void push(JSONObject payload); /** * Checks whether this push connection is in a state where it can push diff --git a/client/src/com/vaadin/client/componentlocator/ComponentLocator.java b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java new file mode 100644 index 0000000000..f0b76766a7 --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java @@ -0,0 +1,250 @@ +/* + * 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.componentlocator; + +import java.util.Arrays; +import java.util.List; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.vaadin.client.ApplicationConnection; + +/** + * ComponentLocator provides methods for generating a String locator for a given + * DOM element and for locating a DOM element using a String locator. + * <p> + * The main use for this class is locating components for automated testing + * purposes. + * + * @since 7.2, moved from {@link com.vaadin.client.ComponentLocator} + */ +public class ComponentLocator { + + private final List<LocatorStrategy> locatorStrategies; + + /** + * Reference to ApplicationConnection instance. + */ + + private final ApplicationConnection client; + + /** + * Construct a ComponentLocator for the given ApplicationConnection. + * + * @param client + * ApplicationConnection instance for the application. + */ + public ComponentLocator(ApplicationConnection client) { + this.client = client; + locatorStrategies = Arrays.asList(new VaadinFinderLocatorStrategy( + client), new LegacyLocatorStrategy(client)); + } + + /** + * Generates a String locator which uniquely identifies the target element. + * The {@link #getElementByPath(String)} method can be used for the inverse + * operation, i.e. locating an element based on the return value from this + * method. + * <p> + * Note that getElementByPath(getPathForElement(element)) == element is not + * always true as #getPathForElement(Element) can return a path to another + * element if the widget determines an action on the other element will give + * the same result as the action on the target element. + * </p> + * + * @since 5.4 + * @param targetElement + * The element to generate a path for. + * @return A String locator that identifies the target element or null if a + * String locator could not be created. + * @deprecated As of 7.2, call and override + * {@link #getPathForElement(Element)} instead + */ + @Deprecated + public String getPathForElement( + com.google.gwt.user.client.Element targetElement) { + for (LocatorStrategy strategy : locatorStrategies) { + String path = strategy.getPathForElement(targetElement); + if (null != path) { + return path; + } + } + return null; + } + + /** + * Generates a String locator which uniquely identifies the target element. + * The {@link #getElementByPath(String)} method can be used for the inverse + * operation, i.e. locating an element based on the return value from this + * method. + * <p> + * Note that getElementByPath(getPathForElement(element)) == element is not + * always true as #getPathForElement(Element) can return a path to another + * element if the widget determines an action on the other element will give + * the same result as the action on the target element. + * </p> + * + * @since 7.2 + * @param targetElement + * The element to generate a path for. + * @return A String locator that identifies the target element or null if a + * String locator could not be created. + */ + public String getPathForElement(Element targetElement) { + return getPathForElement(DOM.asOld(targetElement)); + } + + /** + * Locates an element using a String locator (path) which identifies a DOM + * element. The {@link #getPathForElement(Element)} method can be used for + * the inverse operation, i.e. generating a string expression for a DOM + * element. + * + * @since 5.4 + * @param path + * The String locator which identifies the target element. + * @return The DOM element identified by {@code path} or null if the element + * could not be located. + */ + public com.google.gwt.user.client.Element getElementByPath(String path) { + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + Element element = strategy.getElementByPath(path); + if (null != element) { + return DOM.asOld(element); + } + } + } + return null; + } + + /** + * Locates elements using a String locator (path) which identifies DOM + * elements. + * + * @since 7.2 + * @param path + * The String locator which identifies target elements. + * @return The JavaScriptArray of DOM elements identified by {@code path} or + * empty array if elements could not be located. + */ + public JsArray<Element> getElementsByPath(String path) { + JsArray<Element> jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List<Element> elements = strategy.getElementsByPath(path); + if (elements.size() > 0) { + for (Element e : elements) { + jsElements.push(e); + } + return jsElements; + } + } + } + return jsElements; + } + + /** + * Locates elements using a String locator (path) which identifies DOM + * elements. The path starts from the specified root element. + * + * @see #getElementByPath(String) + * + * @since 7.2 + * @param path + * The path of elements to be found + * @param root + * The root element where the path is anchored + * @return The JavaScriptArray of DOM elements identified by {@code path} or + * empty array if elements could not be located. + */ + public JsArray<Element> getElementsByPathStartingAt(String path, + Element root) { + JsArray<Element> jsElements = JavaScriptObject.createArray().cast(); + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + List<Element> elements = strategy.getElementsByPathStartingAt( + path, root); + if (elements.size() > 0) { + for (Element e : elements) { + jsElements.push(e); + } + return jsElements; + } + } + } + return jsElements; + } + + /** + * Locates an element using a String locator (path) which identifies a DOM + * element. The path starts from the specified root element. + * + * @see #getElementByPath(String) + * + * @since 7.2 + * + * @param path + * The path of the element to be found + * @param root + * The root element where the path is anchored + * @return The DOM element identified by {@code path} or null if the element + * could not be located. + */ + public com.google.gwt.user.client.Element getElementByPathStartingAt( + String path, Element root) { + for (LocatorStrategy strategy : locatorStrategies) { + if (strategy.validatePath(path)) { + Element element = strategy.getElementByPathStartingAt(path, + root); + if (null != element) { + return DOM.asOld(element); + } + } + } + return null; + } + + /** + * Returns the {@link ApplicationConnection} used by this locator. + * <p> + * This method is primarily for internal use by the framework. + * + * @return the application connection + */ + public ApplicationConnection getClient() { + return client; + } + + /** + * Check if a given selector is valid for LegacyLocatorStrategy. + * + * @param path + * Vaadin selector path + * @return true if passes path validation with LegacyLocatorStrategy + */ + public boolean isValidForLegacyLocator(String path) { + for (LocatorStrategy ls : locatorStrategies) { + if (ls instanceof LegacyLocatorStrategy) { + return ls.validatePath(path); + } + } + return false; + } + +} diff --git a/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java new file mode 100644 index 0000000000..232433273f --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java @@ -0,0 +1,718 @@ +/* + * 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.componentlocator; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Element; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ConnectorMap; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.Util; +import com.vaadin.client.VCaption; +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; +import com.vaadin.client.ui.orderedlayout.Slot; +import com.vaadin.client.ui.orderedlayout.VAbstractOrderedLayout; +import com.vaadin.client.ui.window.WindowConnector; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.Connector; +import com.vaadin.shared.communication.SharedState; + +/** + * The LegacyLocatorStrategy class handles the legacy locator syntax that was + * introduced in version 5.4 of the framework. The legacy locator strategy is + * always used if no other strategy claims responsibility for a locator string. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class LegacyLocatorStrategy implements LocatorStrategy { + + /** + * Separator used in the String locator between a parent and a child widget. + */ + static final String PARENTCHILD_SEPARATOR = "/"; + /** + * Separator used in the String locator between the part identifying the + * containing widget and the part identifying the target element within the + * widget. + */ + static final String SUBPART_SEPARATOR = "#"; + /** + * String that identifies the root panel when appearing first in the String + * locator. + */ + static final String ROOT_ID = "Root"; + + private final ApplicationConnection client; + + private static final RegExp validSyntax = RegExp + .compile("^((\\w+::)?((PID_S)?\\w[-$_a-zA-Z0-9.' ]*)?)?(/[-$_a-zA-Z0-9]+\\[\\d+\\])*/?(#.*)?$"); + + public LegacyLocatorStrategy(ApplicationConnection clientConnection) { + client = clientConnection; + } + + @Override + public boolean validatePath(String path) { + return validSyntax.test(path); + } + + @Override + public String getPathForElement(Element targetElement) { + ComponentConnector connector = Util + .findPaintable(client, targetElement); + + Widget w = null; + if (connector != null) { + // If we found a Paintable then we use that as reference. We should + // find the Paintable for all but very special cases (like + // overlays). + w = connector.getWidget(); + + /* + * Still if the Paintable contains a widget that implements + * SubPartAware, we want to use that as a reference + */ + Widget targetParent = findParentWidget(targetElement, w); + while (targetParent != w && targetParent != null) { + if (targetParent instanceof SubPartAware) { + /* + * The targetParent widget is a child of the Paintable and + * the first parent (of the targetElement) that implements + * SubPartAware + */ + w = targetParent; + break; + } + targetParent = targetParent.getParent(); + } + } + if (w == null) { + // Check if the element is part of a widget that is attached + // directly to the root panel + RootPanel rootPanel = RootPanel.get(); + int rootWidgetCount = rootPanel.getWidgetCount(); + for (int i = 0; i < rootWidgetCount; i++) { + Widget rootWidget = rootPanel.getWidget(i); + if (rootWidget.getElement().isOrHasChild(targetElement)) { + // The target element is contained by this root widget + w = findParentWidget(targetElement, rootWidget); + break; + } + } + if (w != null) { + // We found a widget but we should still see if we find a + // SubPartAware implementor (we cannot find the Paintable as + // there is no link from VOverlay to its paintable/owner). + Widget subPartAwareWidget = findSubPartAwareParentWidget(w); + if (subPartAwareWidget != null) { + w = subPartAwareWidget; + } + } + } + + if (w == null) { + // Containing widget not found + return null; + } + + // Determine the path for the target widget + String path = getPathForWidget(w); + if (path == null) { + /* + * No path could be determined for the target widget. Cannot create + * a locator string. + */ + return null; + } + + // The parent check is a work around for Firefox 15 which fails to + // compare elements properly (#9534) + if (w.getElement() == targetElement) { + /* + * We are done if the target element is the root of the target + * widget. + */ + return path; + } else if (w instanceof SubPartAware) { + /* + * If the widget can provide an identifier for the targetElement we + * let it do that + */ + String elementLocator = ((SubPartAware) w).getSubPartName(DOM + .asOld(targetElement)); + if (elementLocator != null) { + return path + LegacyLocatorStrategy.SUBPART_SEPARATOR + + elementLocator; + } + } + /* + * If everything else fails we use the DOM path to identify the target + * element + */ + String domPath = getDOMPathForElement(targetElement, w.getElement()); + if (domPath == null) { + return path; + } else { + return path + domPath; + } + } + + /** + * {@inheritDoc} + */ + @Override + public Element getElementByPath(String path) { + return getElementByPathStartingAt(path, null); + } + + /** + * {@inheritDoc} + */ + @Override + public Element getElementByPathStartingAt(String path, Element baseElement) { + /* + * Path is of type "targetWidgetPath#componentPart" or + * "targetWidgetPath". + */ + String parts[] = path.split(LegacyLocatorStrategy.SUBPART_SEPARATOR, 2); + String widgetPath = parts[0]; + + // Note that this only works if baseElement can be mapped to a + // widget to which the path is relative. Otherwise, the current + // implementation simply interprets the path as if baseElement was + // null. + Widget baseWidget = Util.findWidget(baseElement, null); + + Widget w = getWidgetFromPath(widgetPath, baseWidget); + if (w == null || !Util.isAttachedAndDisplayed(w)) { + return null; + } + if (parts.length == 1) { + int pos = widgetPath.indexOf("domChild"); + if (pos == -1) { + return w.getElement(); + } + + // Contains dom reference to a sub element of the widget + String subPath = widgetPath.substring(pos); + return getElementByDOMPath(w.getElement(), subPath); + } else if (parts.length == 2) { + if (w instanceof SubPartAware) { + return ((SubPartAware) w).getSubPartElement(parts[1]); + } + } + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPath(String path) { + // This type of search is not supported in LegacyLocator + List<Element> array = new ArrayList<Element>(); + Element e = getElementByPath(path); + if (e != null) { + array.add(e); + } + return array; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPathStartingAt(String path, Element root) { + // This type of search is not supported in LegacyLocator + List<Element> array = new ArrayList<Element>(); + Element e = getElementByPathStartingAt(path, root); + if (e != null) { + array.add(e); + } + return array; + } + + /** + * Finds the first widget in the hierarchy (moving upwards) that implements + * SubPartAware. Returns the SubPartAware implementor or null if none is + * found. + * + * @param w + * The widget to start from. This is returned if it implements + * SubPartAware. + * @return The first widget (upwards in hierarchy) that implements + * SubPartAware or null + */ + Widget findSubPartAwareParentWidget(Widget w) { + + while (w != null) { + if (w instanceof SubPartAware) { + return w; + } + w = w.getParent(); + } + return null; + } + + /** + * Returns the first widget found when going from {@code targetElement} + * upwards in the DOM hierarchy, assuming that {@code ancestorWidget} is a + * parent of {@code targetElement}. + * + * @param targetElement + * @param ancestorWidget + * @return The widget whose root element is a parent of + * {@code targetElement}. + */ + private Widget findParentWidget(Element targetElement, Widget ancestorWidget) { + /* + * As we cannot resolve Widgets from the element we start from the + * widget and move downwards to the correct child widget, as long as we + * find one. + */ + if (ancestorWidget instanceof HasWidgets) { + for (Widget w : ((HasWidgets) ancestorWidget)) { + if (w.getElement().isOrHasChild(targetElement)) { + return findParentWidget(targetElement, w); + } + } + } + + // No children found, this is it + return ancestorWidget; + } + + /** + * Locates an element based on a DOM path and a base element. + * + * @param baseElement + * The base element which the path is relative to + * @param path + * String locator (consisting of domChild[x] parts) that + * identifies the element + * @return The element identified by path, relative to baseElement or null + * if the element could not be found. + */ + private Element getElementByDOMPath(Element baseElement, String path) { + String parts[] = path.split(PARENTCHILD_SEPARATOR); + Element element = baseElement; + + for (int i = 0, l = parts.length; i < l; ++i) { + String part = parts[i]; + if (part.startsWith("domChild[")) { + String childIndexString = part.substring("domChild[".length(), + part.length() - 1); + + if (Util.findWidget(baseElement, null) instanceof VAbstractOrderedLayout) { + if (element.hasChildNodes()) { + Element e = element.getFirstChildElement().cast(); + String cn = e.getClassName(); + if (cn != null + && (cn.equals("v-expand") || cn + .contains("v-has-caption"))) { + element = e; + } + } + } + + try { + int childIndex = Integer.parseInt(childIndexString); + element = DOM.getChild(element, childIndex); + } catch (Exception e) { + return null; + } + + if (element == null) { + return null; + } + + } else { + + path = parts[i]; + for (int j = i + 1; j < l; ++j) { + path += PARENTCHILD_SEPARATOR + parts[j]; + } + + return getElementByPathStartingAt(path, element); + } + } + + return element; + } + + /** + * Generates a String locator using domChild[x] parts for the element + * relative to the baseElement. + * + * @param element + * The target element + * @param baseElement + * The starting point for the locator. The generated path is + * relative to this element. + * @return A String locator that can be used to locate the target element + * using {@link #getElementByDOMPath(Element, String)} or null if + * the locator String cannot be created. + */ + private String getDOMPathForElement(Element element, Element baseElement) { + Element e = element; + String path = ""; + while (true) { + int childIndex = -1; + Element siblingIterator = e; + while (siblingIterator != null) { + childIndex++; + siblingIterator = siblingIterator.getPreviousSiblingElement() + .cast(); + } + + path = PARENTCHILD_SEPARATOR + "domChild[" + childIndex + "]" + + path; + + JavaScriptObject parent = e.getParentElement(); + if (parent == null) { + return null; + } + // The parent check is a work around for Firefox 15 which fails to + // compare elements properly (#9534) + if (parent == baseElement) { + break; + } + + e = parent.cast(); + } + + return path; + } + + /** + * Creates a locator String for the given widget. The path can be used to + * locate the widget using {@link #getWidgetFromPath(String, Widget)}. + * <p/> + * Returns null if no path can be determined for the widget or if the widget + * is null. + * + * @param w + * The target widget + * @return A String locator for the widget + */ + private String getPathForWidget(Widget w) { + if (w == null) { + return null; + } + String elementId = w.getElement().getId(); + 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 ""; + } else if (w instanceof VWindow) { + Connector windowConnector = ConnectorMap.get(client) + .getConnector(w); + List<WindowConnector> subWindowList = client.getUIConnector() + .getSubWindows(); + int indexOfSubWindow = subWindowList.indexOf(windowConnector); + return PARENTCHILD_SEPARATOR + "VWindow[" + indexOfSubWindow + "]"; + } else if (w instanceof RootPanel) { + return ROOT_ID; + } + + Widget parent = w.getParent(); + + String basePath = getPathForWidget(parent); + if (basePath == null) { + return null; + } + String simpleName = Util.getSimpleName(w); + + /* + * Check if the parent implements Iterable. At least VPopupView does not + * implement HasWdgets so we cannot check for that. + */ + if (!(parent instanceof Iterable<?>)) { + // Parent does not implement Iterable so we cannot find out which + // child this is + return null; + } + + Iterator<?> i = ((Iterable<?>) parent).iterator(); + int pos = 0; + while (i.hasNext()) { + Object child = i.next(); + if (child == w) { + return basePath + PARENTCHILD_SEPARATOR + simpleName + "[" + + pos + "]"; + } + String simpleName2 = Util.getSimpleName(child); + if (simpleName.equals(simpleName2)) { + pos++; + } + } + + return null; + } + + /** + * Locates the widget based on a String locator. + * + * @param path + * The String locator that identifies the widget. + * @param baseWidget + * the widget to which the path is relative, null if relative to + * root + * @return The Widget identified by the String locator or null if the widget + * could not be identified. + */ + @SuppressWarnings("unchecked") + private Widget getWidgetFromPath(String path, Widget baseWidget) { + Widget w = baseWidget; + String parts[] = path.split(PARENTCHILD_SEPARATOR); + + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + + if (part.equals(ROOT_ID)) { + w = RootPanel.get(); + } else if (part.equals("")) { + if (w == null) { + w = client.getUIConnector().getWidget(); + } + } else if (w == null) { + String id = part; + // Must be old static pid (PID_S*) + ServerConnector connector = ConnectorMap.get(client) + .getConnector(id); + if (connector == null) { + // Lookup by component id + // TODO Optimize this + connector = findConnectorById(client.getUIConnector(), + id.substring(5)); + } + + if (connector instanceof ComponentConnector) { + w = ((ComponentConnector) connector).getWidget(); + } else { + // Not found + return null; + } + } else if (part.startsWith("domChild[")) { + // The target widget has been found and the rest identifies the + // element + break; + } else if (w instanceof Iterable) { + // W identifies a widget that contains other widgets, as it + // should. Try to locate the child + Iterable<?> parent = (Iterable<?>) w; + + // Part is of type "VVerticalLayout[0]", split this into + // VVerticalLayout and 0 + String[] split = part.split("\\[", 2); + String widgetClassName = split[0]; + String indexString = split[1].substring(0, + split[1].length() - 1); + + int widgetPosition; + try { + widgetPosition = Integer.parseInt(indexString); + } catch (NumberFormatException e) { + // We've probably been fed a new-style Vaadin locator with a + // string-form predicate, that doesn't match anything in the + // search space. + return null; + } + + // AbsolutePanel in GridLayout has been removed -> skip it + if (w instanceof VGridLayout + && "AbsolutePanel".equals(widgetClassName)) { + continue; + } + + // FlowPane in CSSLayout has been removed -> skip it + if (w instanceof VCssLayout + && "VCssLayout$FlowPane".equals(widgetClassName)) { + continue; + } + + // ChildComponentContainer and VOrderedLayout$Slot have been + // replaced with Slot + if (w instanceof VAbstractOrderedLayout + && ("ChildComponentContainer".equals(widgetClassName) || "VOrderedLayout$Slot" + .equals(widgetClassName))) { + widgetClassName = "Slot"; + } + + if (w instanceof VTabsheetPanel && widgetPosition != 0) { + // TabSheetPanel now only contains 1 connector => the index + // 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 layouts do not contain + * ChildComponentContainer widgets. This is instead simulated by + * constructing a path step that would find the desired widget + * from the layout and injecting it as the next search step + * (which would originally have found the widget inside the + * ChildComponentContainer) + */ + if ((w instanceof VGridLayout) + && "ChildComponentContainer".equals(widgetClassName) + && i + 1 < parts.length) { + + HasWidgets layout = (HasWidgets) w; + + String nextPart = parts[i + 1]; + String[] nextSplit = nextPart.split("\\[", 2); + String nextWidgetClassName = nextSplit[0]; + + // Find the n:th child and count the number of children with + // the same type before it + int nextIndex = 0; + for (Widget child : layout) { + boolean matchingType = nextWidgetClassName.equals(Util + .getSimpleName(child)); + if (matchingType && widgetPosition == 0) { + // This is the n:th child that we looked for + break; + } else if (widgetPosition < 0) { + // Error if we're past the desired position without + // a match + return null; + } else if (matchingType) { + // If this was another child of the expected type, + // increase the count for the next step + nextIndex++; + } + + // Don't count captions + if (!(child instanceof VCaption)) { + widgetPosition--; + } + } + + // Advance to the next step, this time checking for the + // actual child widget + parts[i + 1] = nextWidgetClassName + '[' + nextIndex + ']'; + continue; + } + + // Locate the child + Iterator<? extends Widget> iterator; + + /* + * VWindow and VContextMenu workarounds for backwards + * compatibility + */ + if (widgetClassName.equals("VWindow")) { + List<WindowConnector> windows = client.getUIConnector() + .getSubWindows(); + List<VWindow> windowWidgets = new ArrayList<VWindow>( + windows.size()); + for (WindowConnector wc : windows) { + windowWidgets.add(wc.getWidget()); + } + iterator = windowWidgets.iterator(); + } else if (widgetClassName.equals("VContextMenu")) { + return client.getContextMenu(); + } else { + iterator = (Iterator<? extends Widget>) parent.iterator(); + } + + boolean ok = false; + + // Find the widgetPosition:th child of type "widgetClassName" + while (iterator.hasNext()) { + + Widget child = iterator.next(); + String simpleName2 = Util.getSimpleName(child); + + if (!widgetClassName.equals(simpleName2) + && child instanceof Slot) { + /* + * Support legacy tests without any selector for the + * Slot widget (i.e. /VVerticalLayout[0]/VButton[0]) by + * directly checking the stuff inside the slot + */ + child = ((Slot) child).getWidget(); + simpleName2 = Util.getSimpleName(child); + } + + if (widgetClassName.equals(simpleName2)) { + if (widgetPosition == 0) { + w = child; + ok = true; + break; + } + widgetPosition--; + + } + } + + if (!ok) { + // Did not find the child + return null; + } + } else { + // W identifies something that is not a "HasWidgets". This + // should not happen as all widget containers should implement + // HasWidgets. + return null; + } + } + + return w; + } + + private ServerConnector findConnectorById(ServerConnector root, String id) { + SharedState state = root.getState(); + if (state instanceof AbstractComponentState + && id.equals(((AbstractComponentState) state).id)) { + return root; + } + for (ServerConnector child : root.getChildren()) { + ServerConnector found = findConnectorById(child, id); + if (found != null) { + return found; + } + } + + return null; + } + +} diff --git a/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java new file mode 100644 index 0000000000..6b3103c677 --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.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.componentlocator; + +import java.util.List; + +import com.google.gwt.dom.client.Element; + +/** + * This interface should be implemented by all locator strategies. A locator + * strategy is responsible for generating and decoding a string that identifies + * an element in the DOM. A strategy can implement its own syntax for the + * locator string, which may be completely different from any other strategy's + * syntax. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public interface LocatorStrategy { + + /** + * Test the given input path for formatting errors. If a given path can not + * be validated, the locator strategy will not be attempted. + * + * @param path + * a locator path expression + * @return true, if the implementing class can process the given path, + * otherwise false + */ + boolean validatePath(String path); + + /** + * Generates a String locator which uniquely identifies the target element. + * The {@link #getElementByPath(String)} method can be used for the inverse + * operation, i.e. locating an element based on the return value from this + * method. + * <p> + * Note that getElementByPath(getPathForElement(element)) == element is not + * always true as #getPathForElement(Element) can return a path to another + * element if the widget determines an action on the other element will give + * the same result as the action on the target element. + * </p> + * + * @param targetElement + * The element to generate a path for. + * @return A String locator that identifies the target element or null if a + * String locator could not be created. + */ + String getPathForElement(Element targetElement); + + /** + * Locates an element using a String locator (path) which identifies a DOM + * element. The {@link #getPathForElement(Element)} method can be used for + * the inverse operation, i.e. generating a string expression for a DOM + * element. + * + * @param path + * The String locator which identifies the target element. + * @return The DOM element identified by {@code path} or null if the element + * could not be located. + */ + Element getElementByPath(String path); + + /** + * Locates an element using a String locator (path) which identifies a DOM + * element. The path starts from the specified root element. + * + * @see #getElementByPath(String) + * + * @param path + * The String locator which identifies the target element. + * @param root + * The element that is at the root of the path. + * @return The DOM element identified by {@code path} or null if the element + * could not be located. + */ + Element getElementByPathStartingAt(String path, + Element root); + + /** + * Locates all elements that match a String locator (path) which identifies + * DOM elements. + * + * This functionality is limited in {@link LegacyLocatorStrategy}. + * + * @param path + * The String locator which identifies target elements. + * @return List that contains all matched elements. Empty list if none + * found. + */ + List<Element> getElementsByPath(String path); + + /** + * Locates all elements that match a String locator (path) which identifies + * DOM elements. The path starts from the specified root element. + * + * This functionality is limited in {@link LegacyLocatorStrategy}. + * + * @see #getElementsByPath(String) + * + * @param path + * The String locator which identifies target elements. + * @param root + * The element that is at the root of the path. + * @return List that contains all matched elements. Empty list if none + * found. + */ + + List<Element> getElementsByPathStartingAt( + String path, Element root); +} diff --git a/client/src/com/vaadin/client/componentlocator/LocatorUtil.java b/client/src/com/vaadin/client/componentlocator/LocatorUtil.java new file mode 100644 index 0000000000..04624920a9 --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/LocatorUtil.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.componentlocator; + +/** + * Common String manipulator utilities used in VaadinFinderLocatorStrategy and + * SelectorPredicates. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class LocatorUtil { + + /** + * Find first occurrence of character that's not inside quotes starting from + * specified index. + * + * @param str + * Full string for searching + * @param find + * Character we want to find + * @param startingAt + * Index where we start + * @return Index of character. -1 if character not found + */ + protected static int indexOfIgnoringQuoted(String str, char find, + int startingAt) { + boolean quote = false; + String quoteChars = "'\""; + char currentQuote = '"'; + for (int i = startingAt; i < str.length(); ++i) { + char cur = str.charAt(i); + if (quote) { + if (cur == currentQuote) { + quote = !quote; + } + continue; + } else if (cur == find) { + return i; + } else { + if (quoteChars.indexOf(cur) >= 0) { + currentQuote = cur; + quote = !quote; + } + } + } + return -1; + } + + /** + * Find first occurrence of character that's not inside quotes starting from + * the beginning of string. + * + * @param str + * Full string for searching + * @param find + * Character we want to find + * @return Index of character. -1 if character not found + */ + protected static int indexOfIgnoringQuoted(String str, char find) { + return indexOfIgnoringQuoted(str, find, 0); + } +} diff --git a/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java new file mode 100644 index 0000000000..32b33005ed --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/SelectorPredicate.java @@ -0,0 +1,228 @@ +/* + * 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.componentlocator; + +import java.util.ArrayList; +import java.util.List; + +/** + * SelectorPredicates are statements about the state of different components + * that VaadinFinderLocatorStrategy is finding. SelectorPredicates also provide + * useful information of said components to debug window by giving means to + * provide better variable naming. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class SelectorPredicate { + private String name = ""; + private String value = ""; + private boolean wildcard = false; + private int index = -1; + + public static List<SelectorPredicate> extractPostFilterPredicates( + String path) { + if (path.startsWith("(")) { + return extractPredicates(path.substring(path.lastIndexOf(')'))); + } + return new ArrayList<SelectorPredicate>(); + } + + /** + * Generate a list of predicates from a single predicate string + * + * @param str + * a comma separated string of predicates + * @return a List of Predicate objects + */ + public static List<SelectorPredicate> extractPredicates(String path) { + List<SelectorPredicate> predicates = new ArrayList<SelectorPredicate>(); + + String predicateStr = extractPredicateString(path); + if (null == predicateStr || predicateStr.length() == 0) { + return predicates; + } + + // Extract input strings + List<String> input = readPredicatesFromString(predicateStr); + + // Process each predicate into proper predicate descriptor + for (String s : input) { + SelectorPredicate p = new SelectorPredicate(); + s = s.trim(); + + try { + // If we can parse out the predicate as a pure index argument, + // stop processing here. + p.index = Integer.parseInt(s); + predicates.add(p); + + continue; + } catch (Exception e) { + p.index = -1; + } + + int idx = LocatorUtil.indexOfIgnoringQuoted(s, '='); + if (idx < 0) { + continue; + } + p.name = s.substring(0, idx); + p.value = s.substring(idx + 1); + + if (p.value.equals("?")) { + p.wildcard = true; + p.value = null; + } else { + // Only unquote predicate value once we're sure it's a proper + // value... + + p.value = unquote(p.value); + } + + predicates.add(p); + } + // Move any (and all) index predicates to last place in the list. + for (int i = 0, l = predicates.size(); i < l - 1; ++i) { + if (predicates.get(i).index > -1) { + predicates.add(predicates.remove(i)); + --i; + --l; + } + } + + return predicates; + } + + /** + * Splits the predicate string to list of predicate strings. + * + * @param predicateStr + * Comma separated predicate strings + * @return List of predicate strings + */ + private static List<String> readPredicatesFromString(String predicateStr) { + List<String> predicates = new ArrayList<String>(); + int prevIdx = 0; + int idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx); + + while (idx > -1) { + predicates.add(predicateStr.substring(prevIdx, idx)); + prevIdx = idx + 1; + idx = LocatorUtil.indexOfIgnoringQuoted(predicateStr, ',', prevIdx); + } + predicates.add(predicateStr.substring(prevIdx)); + + return predicates; + } + + /** + * Returns the predicate string, i.e. the string between the brackets in a + * path fragment. Examples: <code> + * VTextField[0] => 0 + * VTextField[caption='foo'] => caption='foo' + * </code> + * + * @param pathFragment + * The path fragment from which to extract the predicate string. + * @return The predicate string for the path fragment or empty string if not + * found. + */ + private static String extractPredicateString(String pathFragment) { + int ixOpenBracket = LocatorUtil + .indexOfIgnoringQuoted(pathFragment, '['); + if (ixOpenBracket >= 0) { + int ixCloseBracket = LocatorUtil.indexOfIgnoringQuoted( + pathFragment, ']', ixOpenBracket); + return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket); + } + return ""; + } + + /** + * Removes the surrounding quotes from a string if it is quoted. + * + * @param str + * the possibly quoted string + * @return an unquoted version of str + */ + private static String unquote(String str) { + if ((str.startsWith("\"") && str.endsWith("\"")) + || (str.startsWith("'") && str.endsWith("'"))) { + return str.substring(1, str.length() - 1); + } + return str; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value + * the value to set + */ + public void setValue(String value) { + this.value = value; + } + + /** + * @return the index + */ + public int getIndex() { + return index; + } + + /** + * @param index + * the index to set + */ + public void setIndex(int index) { + this.index = index; + } + + /** + * @return the wildcard + */ + public boolean isWildcard() { + return wildcard; + } + + /** + * @param wildcard + * the wildcard to set + */ + public void setWildcard(boolean wildcard) { + this.wildcard = wildcard; + } +} diff --git a/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java new file mode 100644 index 0000000000..e7e752ef34 --- /dev/null +++ b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java @@ -0,0 +1,748 @@ +/* + * 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.componentlocator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.ui.RootPanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.HasComponentsConnector; +import com.vaadin.client.Util; +import com.vaadin.client.metadata.Property; +import com.vaadin.client.metadata.TypeDataStore; +import com.vaadin.client.ui.AbstractConnector; +import com.vaadin.client.ui.SubPartAware; +import com.vaadin.client.ui.VNotification; + +/** + * The VaadinFinder locator strategy implements an XPath-like syntax for + * locating elements in Vaadin applications. This is used in the new + * VaadinFinder API in TestBench 4. + * + * Examples of the supported syntax: + * <ul> + * <li>Find the third text field in the DOM: {@code //VTextField[2]}</li> + * <li>Find the second button inside the first vertical layout: + * {@code //VVerticalLayout/VButton[1]}</li> + * <li>Find the first column on the third row of the "Accounts" table: + * {@code //VScrollTable[caption="Accounts"]#row[2]/col[0]}</li> + * </ul> + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class VaadinFinderLocatorStrategy implements LocatorStrategy { + + public static final String SUBPART_SEPARATOR = "#"; + + private final ApplicationConnection client; + + /** + * Internal descriptor for connector/element/widget name combinations + */ + private static final class ConnectorPath { + private String name; + private ComponentConnector connector; + } + + public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) { + client = clientConnection; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPathForElement(Element targetElement) { + if (targetElement == null) { + return ""; + } + + List<ConnectorPath> hierarchy = getConnectorHierarchyForElement(targetElement); + List<String> path = new ArrayList<String>(); + + // Assemble longname path components back-to-forth with useful + // predicates - first try ID, then caption. + for (int i = 0; i < hierarchy.size(); ++i) { + ConnectorPath cp = hierarchy.get(i); + String pathFragment = cp.name; + String identifier = getPropertyValue(cp.connector, "id"); + + if (identifier != null) { + pathFragment += "[id=\"" + identifier + "\"]"; + } else { + identifier = getPropertyValue(cp.connector, "caption"); + if (identifier != null) { + pathFragment += "[caption=\"" + identifier + "\"]"; + } + } + path.add(pathFragment); + } + + if (path.size() == 0) { + // If we didn't find a single element, return null.. + return null; + } + + return getBestSelector(generateQueries(path), targetElement); + } + + /** + * Search different queries for the best one. Use the fact that the lowest + * possible index is with the last selector. Last selector is the full + * search path containing the complete Component hierarchy. + * + * @param selectors + * List of selectors + * @param target + * Target element + * @return Best selector string formatted with a post filter + */ + private String getBestSelector(List<String> selectors, Element target) { + // The last selector gives us smallest list index for target element. + String bestSelector = selectors.get(selectors.size() - 1); + int min = getElementsByPath(bestSelector).indexOf(target); + if (selectors.size() > 1 + && min == getElementsByPath(selectors.get(0)).indexOf(target)) { + // The first selector has same index as last. It's much shorter. + bestSelector = selectors.get(0); + } else if (selectors.size() > 2) { + // See if we get minimum from second last. If not then we already + // have the best one.. Second last one contains almost full + // component hierarchy. + if (getElementsByPath(selectors.get(selectors.size() - 2)).indexOf( + target) == min) { + for (int i = 1; i < selectors.size() - 2; ++i) { + // Loop through the remaining selectors and look for one + // with the same index + if (getElementsByPath(selectors.get(i)).indexOf(target) == min) { + bestSelector = selectors.get(i); + break; + } + } + + } + } + return "(" + bestSelector + ")[" + min + "]"; + + } + + /** + * Function to generate all possible search paths for given component list. + * Function strips out all the com.vaadin.ui. prefixes from elements as this + * functionality makes generating a query later on easier. + * + * @param components + * List of components + * @return List of Vaadin selectors + */ + private List<String> generateQueries(List<String> components) { + // Prepare to loop through all the elements. + List<String> paths = new ArrayList<String>(); + int compIdx = 0; + String basePath = components.get(compIdx).replace("com.vaadin.ui.", ""); + // Add a basic search for the first element (eg. //Button) + paths.add((components.size() == 1 ? "/" : "//") + basePath); + while (++compIdx < components.size()) { + // Loop through the remaining components + for (int i = components.size() - 1; i >= compIdx; --i) { + boolean recursive = false; + if (i > compIdx) { + recursive = true; + } + paths.add((i == components.size() - 1 ? "/" : "//") + + components.get(i).replace("com.vaadin.ui.", "") + + (recursive ? "//" : "/") + basePath); + } + // Add the element at index compIdx to the basePath so it is + // included in all the following searches. + basePath = components.get(compIdx).replace("com.vaadin.ui.", "") + + "/" + basePath; + } + + return paths; + } + + /** + * Helper method to get the string-form value of a named property of a + * component connector + * + * @since 7.2 + * @param c + * any ComponentConnector instance + * @param propertyName + * property name to test for + * @return a string, if the property is found, or null, if the property does + * not exist on the object (or some other error is encountered). + */ + private String getPropertyValue(ComponentConnector c, String propertyName) { + Property prop = AbstractConnector.getStateType(c).getProperty( + propertyName); + try { + return prop.getValue(c.getState()).toString(); + } catch (Exception e) { + return null; + } + } + + /** + * Generate a list representing the top-to-bottom connector hierarchy for + * any given element. ConnectorPath element provides long- and short names, + * as well as connector and widget root element references. + * + * @since 7.2 + * @param elem + * any Element that is part of a widget hierarchy + * @return a list of ConnectorPath objects, in descending order towards the + * common root container. + */ + private List<ConnectorPath> getConnectorHierarchyForElement(Element elem) { + Element e = elem; + ComponentConnector c = Util.findPaintable(client, e); + List<ConnectorPath> connectorHierarchy = new ArrayList<ConnectorPath>(); + + while (c != null) { + + for (String id : getIDsForConnector(c)) { + ConnectorPath cp = new ConnectorPath(); + cp.name = getFullClassName(id); + cp.connector = c; + + // We want to make an exception for the UI object, since it's + // our default search context (and can't be found inside itself) + if (!cp.name.equals("com.vaadin.ui.UI")) { + connectorHierarchy.add(cp); + } + } + + e = e.getParentElement(); + if (e != null) { + c = Util.findPaintable(client, e); + e = c != null ? c.getWidget().getElement() : null; + } + + } + + return connectorHierarchy; + } + + private boolean isNotificationExpression(String path) { + String[] starts = { "//", "/" }; + + String[] frags = { "com.vaadin.ui.Notification.class", + "com.vaadin.ui.Notification", "VNotification.class", + "VNotification", "Notification.class", "Notification" }; + + String[] ends = { "/", "[" }; + + for (String s : starts) { + for (String f : frags) { + if (path.equals(s + f)) { + return true; + } + + for (String e : ends) { + if (path.startsWith(s + f + e)) { + return true; + } + } + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPath(String path) { + List<SelectorPredicate> postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + path = path.substring(1, path.lastIndexOf(')')); + } + + List<Element> elements = new ArrayList<Element>(); + if (isNotificationExpression(path)) { + + for (VNotification n : findNotificationsByPath(path)) { + elements.add(n.getElement()); + } + + } else { + + elements.addAll(eliminateDuplicates(getElementsByPathStartingAtConnector( + path, client.getUIConnector()))); + } + + for (SelectorPredicate p : postFilters) { + // Post filtering supports only indexes and follows instruction + // blindly. Index that is outside of our list results into an empty + // list and multiple indexes are likely to ruin a search completely + if (p.getIndex() >= 0) { + if (p.getIndex() >= elements.size()) { + elements.clear(); + } else { + Element e = elements.get(p.getIndex()); + elements.clear(); + elements.add(e); + } + } + } + + return elements; + } + + /** + * {@inheritDoc} + */ + @Override + public Element getElementByPath(String path) { + List<Element> elements = getElementsByPath(path); + if (elements.isEmpty()) { + return null; + } + return elements.get(0); + } + + /** + * {@inheritDoc} + */ + @Override + public Element getElementByPathStartingAt(String path, Element root) { + List<Element> elements = getElementsByPathStartingAt(path, root); + if (elements.isEmpty()) { + return null; + } + return elements.get(0); + + } + + /** + * {@inheritDoc} + */ + @Override + public List<Element> getElementsByPathStartingAt(String path, Element root) { + List<SelectorPredicate> postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + path = path.substring(1, path.lastIndexOf(')')); + } + + List<Element> elements = getElementsByPathStartingAtConnector(path, + Util.findPaintable(client, root)); + + for (SelectorPredicate p : postFilters) { + // Post filtering supports only indexes and follows instruction + // blindly. Index that is outside of our list results into an empty + // list and multiple indexes are likely to ruin a search completely + if (p.getIndex() >= 0) { + if (p.getIndex() >= elements.size()) { + elements.clear(); + } else { + Element e = elements.get(p.getIndex()); + elements.clear(); + elements.add(e); + } + } + } + + return elements; + } + + /** + * Special case for finding notifications as they have no connectors and are + * directly attached to {@link RootPanel}. + * + * @param path + * The path of the notification, should be + * {@code "//VNotification"} optionally followed by an index in + * brackets. + * @return the notification element or null if not found. + */ + private List<VNotification> findNotificationsByPath(String path) { + + List<VNotification> notifications = new ArrayList<VNotification>(); + for (Widget w : RootPanel.get()) { + if (w instanceof VNotification) { + notifications.add((VNotification) w); + } + } + + List<SelectorPredicate> predicates = SelectorPredicate + .extractPredicates(path); + for (SelectorPredicate p : predicates) { + + if (p.getIndex() > -1) { + VNotification n = notifications.get(p.getIndex()); + notifications.clear(); + if (n != null) { + notifications.add(n); + } + } + + } + + return eliminateDuplicates(notifications); + } + + /** + * Finds a list of elements by the specified path, starting traversal of the + * connector hierarchy from the specified root. + * + * @param path + * the locator path + * @param root + * the root connector + * @return the list of elements identified by path or empty list if not + * found. + */ + private List<Element> getElementsByPathStartingAtConnector(String path, + ComponentConnector root) { + String[] pathComponents = path.split(SUBPART_SEPARATOR); + List<ComponentConnector> connectors; + if (pathComponents[0].length() > 0) { + connectors = findConnectorsByPath(pathComponents[0], + Arrays.asList(root)); + } else { + connectors = Arrays.asList(root); + } + + List<Element> output = new ArrayList<Element>(); + if (null != connectors && !connectors.isEmpty()) { + if (pathComponents.length > 1) { + // We have subparts + for (ComponentConnector connector : connectors) { + if (connector.getWidget() instanceof SubPartAware) { + output.add(((SubPartAware) connector.getWidget()) + .getSubPartElement(pathComponents[1])); + } + } + } else { + for (ComponentConnector connector : connectors) { + output.add(connector.getWidget().getElement()); + } + } + } + return eliminateDuplicates(output); + } + + /** + * Recursively finds connectors for the elements identified by the provided + * path by traversing the connector hierarchy starting from {@code parents} + * connectors. + * + * @param path + * The path identifying elements. + * @param parents + * The list of connectors to start traversing from. + * @return The list of connectors identified by {@code path} or empty list + * if no such connectors could be found. + */ + private List<ComponentConnector> findConnectorsByPath(String path, + List<ComponentConnector> parents) { + boolean findRecursively = path.startsWith("//"); + // Strip away the one or two slashes from the beginning of the path + path = path.substring(findRecursively ? 2 : 1); + + String[] fragments = splitFirstFragmentFromTheRest(path); + + List<ComponentConnector> connectors = new ArrayList<ComponentConnector>(); + for (ComponentConnector parent : parents) { + connectors.addAll(filterMatches( + collectPotentialMatches(parent, fragments[0], + findRecursively), SelectorPredicate + .extractPredicates(fragments[0]))); + } + + if (!connectors.isEmpty() && fragments.length > 1) { + return (findConnectorsByPath(fragments[1], connectors)); + } + return eliminateDuplicates(connectors); + } + + /** + * Go through a list of potentially matching components, modifying that list + * until all elements that remain in that list match the complete list of + * predicates. + * + * @param potentialMatches + * a list of component connectors. Will be changed. + * @param predicates + * an immutable list of predicates + * @return filtered list of component connectors. + */ + private List<ComponentConnector> filterMatches( + List<ComponentConnector> potentialMatches, + List<SelectorPredicate> predicates) { + + for (SelectorPredicate p : predicates) { + + if (p.getIndex() > -1) { + try { + ComponentConnector v = potentialMatches.get(p.getIndex()); + potentialMatches.clear(); + potentialMatches.add(v); + } catch (IndexOutOfBoundsException e) { + potentialMatches.clear(); + } + + continue; + } + + for (int i = 0, l = potentialMatches.size(); i < l; ++i) { + + String propData = getPropertyValue(potentialMatches.get(i), + p.getName()); + + if ((p.isWildcard() && propData == null) + || (!p.isWildcard() && !p.getValue().equals(propData))) { + potentialMatches.remove(i); + --l; + --i; + } + } + + } + + return eliminateDuplicates(potentialMatches); + } + + /** + * Collects all connectors that match the widget class name of the path + * fragment. If the {@code collectRecursively} parameter is true, a + * depth-first search of the connector hierarchy is performed. + * + * Searching depth-first ensure that we can return the matches in correct + * order for selecting based on index predicates. + * + * @param parent + * The {@link ComponentConnector} to start the search from. + * @param pathFragment + * The path fragment identifying which type of widget to search + * for. + * @param collectRecursively + * If true, all matches from all levels below {@code parent} will + * be collected. If false only direct children will be collected. + * @return A list of {@link ComponentConnector}s matching the widget type + * specified in the {@code pathFragment}. + */ + private List<ComponentConnector> collectPotentialMatches( + ComponentConnector parent, String pathFragment, + boolean collectRecursively) { + ArrayList<ComponentConnector> potentialMatches = new ArrayList<ComponentConnector>(); + if (parent instanceof HasComponentsConnector) { + List<ComponentConnector> children = ((HasComponentsConnector) parent) + .getChildComponents(); + for (ComponentConnector child : children) { + String widgetName = getWidgetName(pathFragment); + if (connectorMatchesPathFragment(child, widgetName)) { + potentialMatches.add(child); + } + if (collectRecursively) { + potentialMatches.addAll(collectPotentialMatches(child, + pathFragment, collectRecursively)); + } + } + } + return eliminateDuplicates(potentialMatches); + } + + private List<String> getIDsForConnector(ComponentConnector connector) { + Class<?> connectorClass = connector.getClass(); + List<String> ids = new ArrayList<String>(); + + TypeDataStore.get().findIdentifiersFor(connectorClass).addAllTo(ids); + + return ids; + } + + /** + * Determines whether a connector matches a path fragment. This is done by + * comparing the path fragment to the name of the widget type of the + * connector. + * + * @param connector + * The connector to compare. + * @param widgetName + * The name of the widget class. + * @return true if the widget type of the connector equals the widget type + * identified by the path fragment. + */ + private boolean connectorMatchesPathFragment(ComponentConnector connector, + String widgetName) { + + List<String> ids = getIDsForConnector(connector); + + Integer[] widgetTags = client.getConfiguration() + .getTagsForServerSideClassName(getFullClassName(widgetName)); + if (widgetTags.length == 0) { + widgetTags = client.getConfiguration() + .getTagsForServerSideClassName( + getFullClassName("com.vaadin.ui." + widgetName)); + } + + for (int i = 0, l = ids.size(); i < l; ++i) { + + // Fuzz the connector name, so that the client can provide (for + // example: /Button, /Button.class, /com.vaadin.ui.Button, + // /com.vaadin.ui.Button.class, etc) + + String name = ids.get(i); + final String simpleName = getSimpleClassName(name); + final String fullName = getFullClassName(name); + + if (widgetTags.length > 0) { + Integer[] foundTags = client.getConfiguration() + .getTagsForServerSideClassName(fullName); + for (int tag : foundTags) { + if (tagsMatch(widgetTags, tag)) { + return true; + } + } + } + + // Fallback if something failed before. + if (widgetName.equals(fullName + ".class") + || widgetName.equals(fullName) + || widgetName.equals(simpleName + ".class") + || widgetName.equals(simpleName) || widgetName.equals(name)) { + return true; + } + } + + // If the server-side class name didn't match, fall back to testing for + // the explicit widget name + String widget = Util.getSimpleName(connector.getWidget()); + return widgetName.equals(widget) + || widgetName.equals(widget + ".class"); + + } + + /** + * Extracts the name of the widget class from a path fragment + * + * @param pathFragment + * the path fragment + * @return the name of the widget class. + */ + private String getWidgetName(String pathFragment) { + String widgetName = pathFragment; + int ixBracket = pathFragment.indexOf('['); + if (ixBracket >= 0) { + widgetName = pathFragment.substring(0, ixBracket); + } + return widgetName; + } + + /** + * Splits off the first path fragment from a path and returns an array of + * two elements, where the first element is the first path fragment and the + * second element is the rest of the path (all remaining path fragments + * untouched). + * + * @param path + * The path to split. + * @return An array of two elements: The first path fragment and the rest of + * the path. + */ + private String[] splitFirstFragmentFromTheRest(String path) { + int ixOfSlash = LocatorUtil.indexOfIgnoringQuoted(path, '/'); + if (ixOfSlash > 0) { + return new String[] { path.substring(0, ixOfSlash), + path.substring(ixOfSlash) }; + } + return new String[] { path }; + } + + private String getSimpleClassName(String s) { + String[] parts = s.split("\\."); + if (s.endsWith(".class")) { + return parts[parts.length - 2]; + } + return parts.length > 0 ? parts[parts.length - 1] : s; + } + + private String getFullClassName(String s) { + if (s.endsWith(".class")) { + return s.substring(0, s.lastIndexOf(".class")); + } + return s; + } + + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.componentlocator.LocatorStrategy#validatePath(java. + * lang.String) + */ + @Override + public boolean validatePath(String path) { + // This syntax is so difficult to regexp properly, that we'll just try + // to find something with it regardless of the correctness of the + // syntax... + return true; + } + + /** + * Go through a list, removing all duplicate elements from it. This method + * is used to avoid accumulation of duplicate entries in result lists + * resulting from low-context recursion. + * + * Preserves first entry in list, removes others. Preserves list order. + * + * @return list passed as parameter, after modification + */ + private final <T> List<T> eliminateDuplicates(List<T> list) { + + int l = list.size(); + for (int j = 0; j < l; ++j) { + T ref = list.get(j); + + for (int i = j + 1; i < l; ++i) { + if (list.get(i) == ref) { + list.remove(i); + --i; + --l; + } + } + } + + return list; + } + + private boolean tagsMatch(Integer[] targets, Integer tag) { + for (int i = 0; i < targets.length; ++i) { + if (targets[i].equals(tag)) { + return true; + } + } + + try { + return tagsMatch(targets, + client.getConfiguration().getParentTag(tag)); + } catch (Exception e) { + return false; + } + } +} diff --git a/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java b/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java new file mode 100644 index 0000000000..7561bc2c03 --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java @@ -0,0 +1,267 @@ +/* + * 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.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.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.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.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.ServerConnector; +import com.vaadin.client.SimpleTree; +import com.vaadin.client.Util; +import com.vaadin.client.ValueMap; + +/** + * Analyze layouts view panel of the debug window. + * + * @since 7.1.4 + */ +public class AnalyzeLayoutsPanel extends FlowPanel { + + private List<SelectConnectorListener> listeners = new ArrayList<SelectConnectorListener>(); + + public void update() { + clear(); + add(new Label("Analyzing layouts...")); + List<ApplicationConnection> runningApplications = ApplicationConfiguration + .getRunningApplications(); + for (ApplicationConnection applicationConnection : runningApplications) { + applicationConnection.analyzeLayouts(); + } + } + + public void meta(ApplicationConnection ac, ValueMap meta) { + 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); + add(root); + } else { + 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) { + 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) { + add(new HTML("<p><strong>Vertically zero size:</strong></p>")); + printClientSideDetectedIssues(zeroHeightComponents, ac); + } + if (zeroWidthComponents.size() > 0) { + 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) { + fireSelectEvent(connector); + } + }); + + } + + Highlight.show(connector); + 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()) { + fireSelectEvent(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); + } + } + } + + public void addListener(SelectConnectorListener listener) { + listeners.add(listener); + } + + public void removeListener(SelectConnectorListener listener) { + listeners.remove(listener); + } + + private void fireSelectEvent(ServerConnector connector) { + for (SelectConnectorListener listener : listeners) { + listener.select(connector, null); + } + } + +} diff --git a/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java b/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java new file mode 100644 index 0000000000..fc7b55497e --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java @@ -0,0 +1,107 @@ +/* + * 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.Set; + +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.client.ComponentConnector; +import com.vaadin.client.JsArrayObject; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.Util; +import com.vaadin.client.VConsole; +import com.vaadin.client.metadata.NoDataException; +import com.vaadin.client.metadata.Property; +import com.vaadin.client.ui.AbstractConnector; +import com.vaadin.shared.AbstractComponentState; +import com.vaadin.shared.communication.SharedState; + +/** + * Connector information view panel of the debug window. + * + * @since 7.1.4 + */ +public class ConnectorInfoPanel extends FlowPanel { + + /** + * Update the panel to show information about a connector. + * + * @param connector + */ + public void update(ServerConnector 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); + } + + clear(); + 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>"; + } + + /** + * Clear the contents of the panel. + */ + public void clearContents() { + clear(); + } +} diff --git a/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java b/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java new file mode 100644 index 0000000000..755f076b7a --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/HierarchyPanel.java @@ -0,0 +1,178 @@ +/* + * 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.Iterator; +import java.util.List; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.HasDoubleClickHandlers; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasWidgets; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.ApplicationConfiguration; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.FastStringSet; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.SimpleTree; +import com.vaadin.client.Util; + +/** + * Hierarchy view panel of the debug window. This class can be used in various + * debug window sections to show the current connector hierarchy. + * + * @since 7.1.4 + */ +public class HierarchyPanel extends FlowPanel { + + // TODO separate click listeners for simple selection and doubleclick + private List<SelectConnectorListener> listeners = new ArrayList<SelectConnectorListener>(); + + public void update() { + // Try to keep track of currently open nodes and reopen them + FastStringSet openNodes = FastStringSet.create(); + Iterator<Widget> it = iterator(); + while (it.hasNext()) { + collectOpenNodes(it.next(), openNodes); + } + + clear(); + + SimplePanel trees = new SimplePanel(); + + for (ApplicationConnection application : ApplicationConfiguration + .getRunningApplications()) { + ServerConnector uiConnector = application.getUIConnector(); + Widget connectorTree = buildConnectorTree(uiConnector, openNodes); + + trees.add(connectorTree); + } + + add(trees); + } + + /** + * Adds the captions of all open (non-leaf) nodes in the hierarchy tree + * recursively. + * + * @param widget + * the widget in which to search for open nodes (if SimpleTree) + * @param openNodes + * the set in which open nodes should be added + */ + private void collectOpenNodes(Widget widget, FastStringSet openNodes) { + if (widget instanceof SimpleTree) { + SimpleTree tree = (SimpleTree) widget; + if (tree.isOpen()) { + openNodes.add(tree.getCaption()); + } else { + // no need to look inside closed nodes + return; + } + } + if (widget instanceof HasWidgets) { + Iterator<Widget> it = ((HasWidgets) widget).iterator(); + while (it.hasNext()) { + collectOpenNodes(it.next(), openNodes); + } + } + } + + private Widget buildConnectorTree(final ServerConnector connector, + FastStringSet openNodes) { + 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); + showServerDebugInfo(connector); + } + }); + widget = label; + } else { + SimpleTree tree = new SimpleTree(connectorString) { + @Override + protected void select(ClickEvent event) { + super.select(event); + Highlight.showOnly(connector); + showServerDebugInfo(connector); + } + }; + for (ServerConnector child : children) { + tree.add(buildConnectorTree(child, openNodes)); + } + if (openNodes.contains(connectorString)) { + tree.open(false); + } + widget = tree; + } + + if (widget instanceof HasDoubleClickHandlers) { + HasDoubleClickHandlers has = (HasDoubleClickHandlers) widget; + has.addDoubleClickHandler(new DoubleClickHandler() { + @Override + public void onDoubleClick(DoubleClickEvent event) { + fireSelectEvent(connector); + } + }); + } + + return widget; + } + + public void addListener(SelectConnectorListener listener) { + listeners.add(listener); + } + + public void removeListener(SelectConnectorListener listener) { + listeners.remove(listener); + } + + private void fireSelectEvent(ServerConnector connector) { + for (SelectConnectorListener listener : listeners) { + listener.select(connector, null); + } + } + + /** + * Outputs debug information on the server - usually in the console of an + * IDE, with a clickable reference to the relevant code location. + * + * @since 7.1 + * @param connector + * show debug info for this connector + */ + static void showServerDebugInfo(ServerConnector connector) { + if (connector != null) { + connector.getConnection().getUIConnector() + .showServerDebugInfo(connector); + } + } + +} diff --git a/client/src/com/vaadin/client/debug/internal/HierarchySection.java b/client/src/com/vaadin/client/debug/internal/HierarchySection.java index 90c9086d7d..1647a61256 100644 --- a/client/src/com/vaadin/client/debug/internal/HierarchySection.java +++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java @@ -15,53 +15,26 @@ */ 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.dom.client.Element; 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. @@ -73,7 +46,15 @@ public class HierarchySection implements Section { private final DebugButton tabButton = new DebugButton(Icon.HIERARCHY, "Examine component hierarchy"); - private final FlowPanel content = new FlowPanel(); + private final SimplePanel content = new SimplePanel(); + + // TODO highlighting logic is split between these, should be refactored + private final FlowPanel helpPanel = new FlowPanel(); + private final ConnectorInfoPanel infoPanel = new ConnectorInfoPanel(); + private final HierarchyPanel hierarchyPanel = new HierarchyPanel(); + private final OptimizedWidgetsetPanel widgetsetPanel = new OptimizedWidgetsetPanel(); + private final AnalyzeLayoutsPanel analyzeLayoutsPanel = new AnalyzeLayoutsPanel(); + private final FlowPanel controls = new FlowPanel(); private final Button find = new DebugButton(Icon.HIGHLIGHT, @@ -125,79 +106,42 @@ public class HierarchySection implements Section { } }); + hierarchyPanel.addListener(new SelectConnectorListener() { + @Override + public void select(ServerConnector connector, + Element element) { + printState(connector, true); + } + }); + + analyzeLayoutsPanel.addListener(new SelectConnectorListener() { + @Override + public void select(ServerConnector connector, + Element element) { + printState(connector, true); + } + }); + content.setStylePrimaryName(VDebugWindow.STYLENAME + "-hierarchy"); + initializeHelpPanel(); + content.setWidget(helpPanel); + } + + private void initializeHelpPanel() { HTML info = new HTML(showHierarchy.getHTML() + " " + showHierarchy.getTitle() + "<br/>" + find.getHTML() + " " + find.getTitle() + "<br/>" + analyze.getHTML() + " " + analyze.getTitle() + "<br/>" + generateWS.getHTML() + " " + generateWS.getTitle() + "<br/>"); info.setStyleName(VDebugWindow.STYLENAME + "-info"); - content.add(info); + helpPanel.add(info); } 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); - Highlight.showServerDebugInfo(connector); - } - }); - widget = label; - } else { - SimpleTree tree = new SimpleTree(connectorString) { - @Override - protected void select(ClickEvent event) { - super.select(event); - Highlight.showOnly(connector); - Highlight.showServerDebugInfo(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, true); - } - }); - } - - return widget; + hierarchyPanel.update(); + content.setWidget(hierarchyPanel); } @Override @@ -226,302 +170,19 @@ public class HierarchySection implements Section { } 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; + widgetsetPanel.update(); + content.setWidget(widgetsetPanel); } private void analyzeLayouts() { - content.clear(); - content.add(new Label("Analyzing layouts...")); - List<ApplicationConnection> runningApplications = ApplicationConfiguration - .getRunningApplications(); - for (ApplicationConnection applicationConnection : runningApplications) { - applicationConnection.analyzeLayouts(); - } + analyzeLayoutsPanel.update(); + content.setWidget(analyzeLayoutsPanel); } @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, true); - } - }); - - } - - 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, true); - } - } - }, 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); - } - } + // show the results of analyzeLayouts + analyzeLayoutsPanel.meta(ac, meta); } @Override @@ -561,60 +222,11 @@ public class HierarchySection implements Section { private void printState(ServerConnector connector, boolean serverDebug) { Highlight.showOnly(connector); if (serverDebug) { - Highlight.showServerDebugInfo(connector); + HierarchyPanel.showServerDebugInfo(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>"; + infoPanel.update(connector); + content.setWidget(infoPanel); } private final NativePreviewHandler highlightModeHandler = new NativePreviewHandler() { @@ -634,7 +246,7 @@ public class HierarchySection implements Section { .getNativeEvent().getClientX(), event.getNativeEvent() .getClientY()); if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) { - content.clear(); + infoPanel.clear(); return; } @@ -654,7 +266,7 @@ public class HierarchySection implements Section { return; } } - content.clear(); + infoPanel.clear(); } if (event.getTypeInt() == Event.ONCLICK) { Highlight.hideAll(); diff --git a/client/src/com/vaadin/client/debug/internal/Highlight.java b/client/src/com/vaadin/client/debug/internal/Highlight.java index 3c1af445a9..262313b9b3 100644 --- a/client/src/com/vaadin/client/debug/internal/Highlight.java +++ b/client/src/com/vaadin/client/debug/internal/Highlight.java @@ -17,11 +17,11 @@ package com.vaadin.client.debug.internal; import java.util.HashSet; +import com.google.gwt.dom.client.Element; 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; @@ -144,20 +144,55 @@ public class Highlight { */ static Element show(Widget widget, String color) { if (widget != null) { + show(widget.getElement(), color); + } + return null; + } + + /** + * Highlights the given {@link Element}. + * <p> + * Pass the returned {@link Element} to {@link #hide(Element)} to remove + * this particular highlight. + * </p> + * + * @param element + * Element to highlight + * @return Highlight element + */ + static Element show(Element element) { + return show(element, DEFAULT_COLOR); + } + + /** + * Highlight the given {@link Element} using the given color. + * <p> + * Pass the returned highlight {@link Element} to {@link #hide(Element)} to + * remove this particular highlight. + * </p> + * + * @param element + * Element to highlight + * @param color + * Color of highlight + * @return Highlight element + */ + static Element show(Element element, String color) { + if (element != 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(); + style.setTop(element.getAbsoluteTop(), Unit.PX); + style.setLeft(element.getAbsoluteLeft(), Unit.PX); + int width = element.getOffsetWidth(); if (width < MIN_WIDTH) { width = MIN_WIDTH; } style.setWidth(width, Unit.PX); - int height = widget.getOffsetHeight(); + int height = element.getOffsetHeight(); if (height < MIN_HEIGHT) { height = MIN_HEIGHT; } @@ -207,19 +242,4 @@ public class Highlight { } } - /** - * Outputs debug information on the server - usually in the console of an - * IDE, with a clickable reference to the relevant code location. - * - * @since 7.1 - * @param connector - * show debug info for this connector - */ - static void showServerDebugInfo(ServerConnector connector) { - if (connector != null) { - connector.getConnection().getUIConnector() - .showServerDebugInfo(connector); - } - } - } diff --git a/client/src/com/vaadin/client/debug/internal/Icon.java b/client/src/com/vaadin/client/debug/internal/Icon.java index cc2ef3b348..70bac11175 100644 --- a/client/src/com/vaadin/client/debug/internal/Icon.java +++ b/client/src/com/vaadin/client/debug/internal/Icon.java @@ -32,6 +32,8 @@ public enum Icon { LOG(""), // OPTIMIZE(""), // HIERARCHY(""), // + // TODO create more appropriate icon + SELECTOR("≣"), // MENU(""), // NETWORK(""), // ANALYZE(""), // @@ -42,7 +44,9 @@ public enum Icon { // BAN_CIRCLE(""), // MAXIMIZE(""), // RESET(""), // - PERSIST(""); // + PERSIST(""), // + TESTBENCH(""), // + ; private String id; diff --git a/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java b/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java new file mode 100644 index 0000000000..a8d8aad888 --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java @@ -0,0 +1,137 @@ +/* + * 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 java.util.Set; + +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.vaadin.client.ApplicationConfiguration; +import com.vaadin.client.ApplicationConnection; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.VConsole; +import com.vaadin.client.ui.UnknownComponentConnector; + +/** + * Optimized widgetset view panel of the debug window. + * + * @since 7.1.4 + */ +public class OptimizedWidgetsetPanel extends FlowPanel { + + /** + * Update the panel contents based on the connectors that have been used so + * far on this execution of the application. + */ + public void update() { + clear(); + HTML h = new HTML("Getting used connectors"); + 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; + } + +} diff --git a/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java new file mode 100644 index 0000000000..ec3a36c7c4 --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java @@ -0,0 +1,38 @@ +/* + * 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.dom.client.Element; +import com.vaadin.client.ServerConnector; + +/** + * Listener for the selection of a connector in the debug window. + * + * @since 7.1.4 + */ +public interface SelectConnectorListener { + /** + * Listener method called when a connector has been selected. If a specific + * element of the connector was selected, it is also given. + * + * @param connector + * selected connector + * @param element + * selected element of the connector or null if unknown + */ + public void select(ServerConnector connector, + Element element); +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/debug/internal/SelectorPath.java b/client/src/com/vaadin/client/debug/internal/SelectorPath.java new file mode 100644 index 0000000000..56b48b2447 --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/SelectorPath.java @@ -0,0 +1,268 @@ +/* + * 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.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.dom.client.Element; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.componentlocator.ComponentLocator; +import com.vaadin.client.componentlocator.SelectorPredicate; + +/** + * A single segment of a selector path pointing to an Element. + * <p> + * This class should be considered internal to the framework and may change at + * any time. + * <p> + * + * @since 7.1.x + */ +public class SelectorPath { + private final String path; + private final Element element; + private final ComponentLocator locator; + private static Map<String, Integer> counter = new HashMap<String, Integer>(); + private static Map<String, String> legacyNames = new HashMap<String, String>(); + + static { + legacyNames.put("FilterSelect", "ComboBox"); + legacyNames.put("ScrollTable", "Table"); + } + + protected SelectorPath(ServerConnector c, Element e) { + element = e; + locator = new ComponentLocator(c.getConnection()); + path = locator.getPathForElement(e); + } + + public String getPath() { + return path; + } + + public Element getElement() { + return element; + } + + public ComponentLocator getLocator() { + return locator; + } + + /** + * Generate ElementQuery code for Java. Fallback to By.vaadin(path) if + * dealing with LegacyLocator + * + * @return String containing Java code for finding the element described by + * path + */ + public String getElementQuery() { + if (path.isEmpty() || locator.isValidForLegacyLocator(path)) { + return getLegacyLocatorQuery(); + } + + String[] fragments; + String tmpPath = path; + List<SelectorPredicate> postFilters = SelectorPredicate + .extractPostFilterPredicates(path); + if (postFilters.size() > 0) { + tmpPath = tmpPath.substring(1, tmpPath.lastIndexOf(')')); + } + + // Generate an ElementQuery + fragments = tmpPath.split("/"); + String elementQueryString = ""; + int index = 0; + for (SelectorPredicate p : postFilters) { + if (p.getIndex() > 0) { + index = p.getIndex(); + } + } + + for (int i = 1; i < fragments.length; ++i) { + if (fragments[i].isEmpty()) { + // Recursive searches cause empty fragments + continue; + } + + // if i == 1 or previous fragment was empty, search is recursive + boolean recursive = (i > 1 ? fragments[i - 1].isEmpty() : false); + + // if elementQueryString is not empty, join the next query with . + String queryFragment = (!elementQueryString.isEmpty() ? "." : ""); + // if search is not recursive, add another $ in front of fragment + queryFragment += (!recursive ? "$" : "") + + generateFragment(fragments[i]); + + elementQueryString += queryFragment; + } + + if (!hasId(fragments[fragments.length - 1])) { + if (index == 0) { + elementQueryString += ".first()"; + } else { + elementQueryString += ".get(" + index + ")"; + } + } + + // Return full Java variable assignment and eQuery + return generateJavaVariable(fragments[fragments.length - 1]) + + elementQueryString + ";"; + } + + /** + * Finds out if the given query fragment has a defined id + * + * @param fragment + * Query fragment + * @return true if has id + */ + private boolean hasId(String fragment) { + for (SelectorPredicate p : SelectorPredicate + .extractPredicates(fragment)) { + if (p.getName().equals("id")) { + return true; + } + } + return false; + } + + /** + * Generates a recursive ElementQuery for given path fragment + * + * @param fragment + * Query fragment + * @return ElementQuery java code as a String + */ + private String generateFragment(String fragment) { + // Get Element.class -name + String elementClass = getComponentName(fragment) + "Element.class"; + + String queryFragment = "$(" + elementClass + ")"; + + for (SelectorPredicate p : SelectorPredicate + .extractPredicates(fragment)) { + // Add in predicates like .caption and .id + queryFragment += "." + p.getName() + "(\"" + p.getValue() + "\")"; + } + return queryFragment; + } + + /** + * Returns the name of the component described by given query fragment + * + * @param fragment + * Query fragment + * @return Class part of fragment + */ + protected String getComponentName(String fragment) { + return fragment.split("\\[")[0]; + } + + /** + * Generates a legacy locator for SelectorPath. + * + * @return String containing Java code for element search and assignment + */ + private String getLegacyLocatorQuery() { + String name; + if (!path.isEmpty()) { + String[] frags = path.split("/"); + name = getComponentName(frags[frags.length - 1]).substring(1); + } else { + name = "root"; + } + + if (legacyNames.containsKey(name)) { + name = legacyNames.get(name); + } + + name = getNameWithCount(name); + + // Use direct path and elementX naming style. + return "WebElement " + name.substring(0, 1).toLowerCase() + + name.substring(1) + " = getDriver().findElement(By.vaadin(\"" + + path + "\"));"; + } + + /** + * Get variable name with counter for given component name. + * + * @param name + * Component name + * @return name followed by count + */ + protected String getNameWithCount(String name) { + if (!counter.containsKey(name)) { + counter.put(name, 0); + } + counter.put(name, counter.get(name) + 1); + name += counter.get(name); + return name; + } + + /** + * Generate Java variable assignment from given selector fragment + * + * @param pathFragment + * Selector fragment + * @return piece of java code + */ + private String generateJavaVariable(String pathFragment) { + // Get element type and predicates from fragment + List<SelectorPredicate> predicates = SelectorPredicate + .extractPredicates(pathFragment); + String elementType = pathFragment.split("\\[")[0]; + String name = getNameFromPredicates(predicates, elementType); + + if (name.equals(elementType)) { + name = getNameWithCount(name); + } + + // Replace unusable characters + name = name.replaceAll("\\W", ""); + + // Lowercase the first character of name + return elementType + "Element " + name.substring(0, 1).toLowerCase() + + name.substring(1) + " = "; + } + + /** + * Get variable name based on predicates. Fallback to elementType + * + * @param predicates + * Predicates related to element + * @param elementType + * Element type + * @return name for Variable + */ + private String getNameFromPredicates(List<SelectorPredicate> predicates, + String elementType) { + String name = elementType; + for (SelectorPredicate p : predicates) { + if ("caption".equals(p.getName())) { + // Caption + elementType is a suitable name + name = p.getValue() + elementType; + } else if ("id".equals(p.getName())) { + // Just id. This is unique, use it. + return p.getValue(); + } + } + return name; + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/debug/internal/TestBenchSection.java b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java new file mode 100644 index 0000000000..d35c575568 --- /dev/null +++ b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java @@ -0,0 +1,281 @@ +/* + * 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.HashMap; +import java.util.Map; + +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.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.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.RootPanel; +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.ServerConnector; +import com.vaadin.client.Util; +import com.vaadin.client.ValueMap; + +/** + * Provides functionality for picking selectors for Vaadin TestBench. + * + * @since 7.1.x + * @author Vaadin Ltd + */ +public class TestBenchSection implements Section { + + /** + * Selector widget showing a selector in a program-usable form. + */ + private static class SelectorWidget extends HTML implements + MouseOverHandler, MouseOutHandler { + private final SelectorPath path; + + public SelectorWidget(final SelectorPath path) { + this.path = path; + + String html = "<div class=\"" + VDebugWindow.STYLENAME + + "-selector\"><span class=\"tb-selector\">" + + Util.escapeHTML(path.getElementQuery()) + "</span></div>"; + setHTML(html); + + addMouseOverHandler(this); + addMouseOutHandler(this); + } + + @Override + public void onMouseOver(MouseOverEvent event) { + Highlight.hideAll(); + + Element element = path.getElement(); + if (null != element) { + Highlight.show(element); + } + } + + @Override + public void onMouseOut(MouseOutEvent event) { + Highlight.hideAll(); + } + } + + private final DebugButton tabButton = new DebugButton(Icon.TESTBENCH, + "Pick Vaadin TestBench selectors"); + + private final FlowPanel content = new FlowPanel(); + + private final FlowPanel selectorPanel = new FlowPanel(); + // map from full path to SelectorWidget to enable reuse of old selectors + private Map<SelectorPath, SelectorWidget> selectorWidgets = new HashMap<SelectorPath, SelectorWidget>(); + + private final FlowPanel controls = new FlowPanel(); + + private final Button find = new DebugButton(Icon.HIGHLIGHT, + "Pick an element and generate a query for it"); + + private final Button clear = new DebugButton(Icon.CLEAR, + "Clear current elements"); + + private HandlerRegistration highlightModeRegistration = null; + + public TestBenchSection() { + + controls.add(find); + find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON); + find.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + toggleFind(); + } + }); + + controls.add(clear); + clear.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON); + clear.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + clearResults(); + } + }); + + content.setStylePrimaryName(VDebugWindow.STYLENAME + "-testbench"); + content.add(selectorPanel); + } + + @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(); + } + + @Override + public void meta(ApplicationConnection ac, ValueMap meta) { + // NOP + } + + @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); + } + Highlight.hideAll(); + } + + private void pickSelector(ServerConnector connector, Element element) { + + SelectorPath p = new SelectorPath(connector, Util + .findPaintable(connector.getConnection(), element).getWidget() + .getElement()); + SelectorWidget w = new SelectorWidget(p); + + content.add(w); + } + + 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 + || event.getTypeInt() == Event.ONCLICK) { + Element eventTarget = Util.getElementFromPoint(event + .getNativeEvent().getClientX(), event.getNativeEvent() + .getClientY()); + if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) { + if (isFindMode() && event.getTypeInt() == Event.ONCLICK) { + stopFind(); + event.cancel(); + } + return; + } + + // make sure that not finding the highlight element only + Highlight.hideAll(); + + eventTarget = Util.getElementFromPoint(event.getNativeEvent() + .getClientX(), event.getNativeEvent().getClientY()); + ComponentConnector connector = findConnector(eventTarget); + + if (event.getTypeInt() == Event.ONMOUSEMOVE) { + if (connector != null) { + Highlight.showOnly(connector); + event.cancel(); + event.consume(); + event.getNativeEvent().stopPropagation(); + return; + } + } else if (event.getTypeInt() == Event.ONCLICK) { + event.cancel(); + event.consume(); + event.getNativeEvent().stopPropagation(); + if (connector != null) { + Highlight.showOnly(connector); + pickSelector(connector, eventTarget); + return; + } + } + } + event.cancel(); + } + + }; + + private ComponentConnector findConnector(Element element) { + for (ApplicationConnection a : ApplicationConfiguration + .getRunningApplications()) { + ComponentConnector connector = Util.getConnectorForElement(a, a + .getUIConnector().getWidget(), element); + if (connector == null) { + connector = Util.getConnectorForElement(a, RootPanel.get(), + element); + } + if (connector != null) { + return connector; + } + } + return null; + } + + private void clearResults() { + content.clear(); + } + +} diff --git a/client/src/com/vaadin/client/event/PointerCancelEvent.java b/client/src/com/vaadin/client/event/PointerCancelEvent.java new file mode 100644 index 0000000000..bd29ca7dfd --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerCancelEvent.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.event; + +import com.google.gwt.event.dom.client.DomEvent; + +/** + * Represents a native PointerCancelEvent. + * + * @since 7.2 + */ +public class PointerCancelEvent extends PointerEvent<PointerCancelHandler> { + + /** + * Event type for PointerCancelEvent. Represents the meta-data associated + * with this event. + */ + private static final Type<PointerCancelHandler> TYPE = new Type<PointerCancelHandler>( + EventType.PointerCancel.getNativeEventName(), + new PointerCancelEvent()); + + /** + * Gets the event type associated with pointer cancel events. + * + * @return the handler type + */ + public static Type<PointerCancelHandler> getType() { + return TYPE; + } + + /** + * Protected constructor, use + * {@link DomEvent#fireNativeEvent(com.google.gwt.dom.client.NativeEvent, com.google.gwt.event.shared.HasHandlers)} + * to fire pointer up events. + */ + protected PointerCancelEvent() { + } + + @Override + public final Type<PointerCancelHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(PointerCancelHandler handler) { + handler.onPointerCancel(this); + } + +} diff --git a/client/src/com/vaadin/client/event/PointerCancelHandler.java b/client/src/com/vaadin/client/event/PointerCancelHandler.java new file mode 100644 index 0000000000..58da738c2a --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerCancelHandler.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler interface for {@link PointerCancelEvent} events. + * + * @since 7.2 + */ +public interface PointerCancelHandler extends EventHandler { + + /** + * Called when PointerCancelEvent is fired. + * + * @param event + * the {@link PointerCancelEvent} that was fired + */ + void onPointerCancel(PointerCancelEvent event); +} diff --git a/client/src/com/vaadin/client/event/PointerDownEvent.java b/client/src/com/vaadin/client/event/PointerDownEvent.java new file mode 100644 index 0000000000..b9df1bd852 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerDownEvent.java @@ -0,0 +1,61 @@ +/* + * 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.event; + +import com.google.gwt.event.dom.client.DomEvent; + +/** + * Represents a native PointerDownEvent. + * + * @since 7.2 + */ +public class PointerDownEvent extends PointerEvent<PointerDownHandler> { + + /** + * Event type for PointerDownEvent. Represents the meta-data associated with + * this event. + */ + private static final Type<PointerDownHandler> TYPE = new Type<PointerDownHandler>( + EventType.PointerDown.getNativeEventName(), new PointerDownEvent()); + + /** + * Gets the event type associated with PointerDownEvent events. + * + * @return the handler type + */ + public static Type<PointerDownHandler> getType() { + return TYPE; + } + + /** + * Protected constructor, use + * {@link DomEvent#fireNativeEvent(com.google.gwt.dom.client.NativeEvent, com.google.gwt.event.shared.HasHandlers)} + * to fire pointer down events. + */ + protected PointerDownEvent() { + } + + @Override + public final Type<PointerDownHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(PointerDownHandler handler) { + handler.onPointerDown(this); + } + +} diff --git a/client/src/com/vaadin/client/event/PointerDownHandler.java b/client/src/com/vaadin/client/event/PointerDownHandler.java new file mode 100644 index 0000000000..631fe3c716 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerDownHandler.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler interface for {@link PointerDownEvent} events. + * + * @since 7.2 + */ +public interface PointerDownHandler extends EventHandler { + + /** + * Called when PointerDownEvent is fired. + * + * @param event + * the {@link PointerDownEvent} that was fired + */ + void onPointerDown(PointerDownEvent event); +} diff --git a/client/src/com/vaadin/client/event/PointerEvent.java b/client/src/com/vaadin/client/event/PointerEvent.java new file mode 100644 index 0000000000..7aac68abf4 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerEvent.java @@ -0,0 +1,173 @@ +/* + * 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.event; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.MouseEvent; +import com.google.gwt.event.shared.EventHandler; + +/** + * Abstract class representing Pointer events. + * + * @param <H> + * handler type + * + * @since 7.2 + */ +public abstract class PointerEvent<H extends EventHandler> extends + MouseEvent<H> { + + enum EventType { + PointerDown, PointerMove, PointerOut, PointerOver, PointerUp, PointerCancel; + + String getNativeEventName() { + return PointerEventSupport.getNativeEventName(this); + } + } + + public static final String TYPE_UNKNOWN = ""; + public static final String TYPE_TOUCH = "touch"; + public static final String TYPE_PEN = "pen"; + public static final String TYPE_MOUSE = "mouse"; + + /** + * Gets a unique identifier for the pointer that caused this event. The + * identifiers of previously active but retired pointers may be recycled. + * + * @return unique pointer id + */ + public final int getPointerId() { + return getPointerId(getNativeEvent()); + } + + /** + * Gets the width of the contact geometry of the pointer in CSS pixels. + * + * @return width in CSS pixels + */ + public final int getWidth() { + return getWidth(getNativeEvent()); + } + + /** + * Gets the height of the contact geometry of the pointer in CSS pixels. + * + * @return height in CSS pixels. + */ + public final int getHeight() { + return getHeight(getNativeEvent()); + } + + /** + * Gets the pressure of the pointer input as a value in the range of [0, 1] + * where 0 and 1 represent the minimum and maximum, respectively. + * + * @return input pressure as a value between 0 and 1 + */ + public final double getPressure() { + return getPressure(getNativeEvent()); + } + + /** + * Gets the angle between the Y-Z plane and the plane containing both the + * transducer and the Y axis. A positive tilt is to the right. + * + * @return the tilt along the X axis as degrees in the range of [-90, 90], + * or 0 if the device does not support tilt + */ + public final double getTiltX() { + return getTiltX(getNativeEvent()); + } + + /** + * Gets the angle between the X-Z plane and the plane containing both the + * transducer and the X axis. A positive tilt is towards the user. + * + * @return the tilt along the Y axis as degrees in the range of [-90, 90], + * or 0 if the device does not support tilt + */ + public final double getTiltY() { + return getTiltY(getNativeEvent()); + } + + /** + * Gets the type of the pointer device that caused this event. + * + * @see PointerEvent#TYPE_UNKNOWN + * @see PointerEvent#TYPE_TOUCH + * @see PointerEvent#TYPE_PEN + * @see PointerEvent#TYPE_MOUSE + * + * @return a String indicating the type of the pointer device + */ + public final String getPointerType() { + return getPointerType(getNativeEvent()); + } + + /** + * Indicates whether the pointer is the primary pointer of this type. + * + * @return true if the pointer is the primary pointer, otherwise false + */ + public final boolean isPrimary() { + return isPrimary(getNativeEvent()); + } + + private static native final int getPointerId(NativeEvent e) + /*-{ + return e.pointerId; + }-*/; + + private static native final int getWidth(NativeEvent e) + /*-{ + return e.width; + }-*/; + + private static native final int getHeight(NativeEvent e) + /*-{ + return e.height; + }-*/; + + private static native final double getPressure(NativeEvent e) + /*-{ + return e.pressure; + }-*/; + + private static native final double getTiltX(NativeEvent e) + /*-{ + return e.tiltX; + }-*/; + + private static native final double getTiltY(NativeEvent e) + /*-{ + return e.tiltY; + }-*/; + + private static native final String getPointerType(NativeEvent e) + /*-{ + var pointerType = e.pointerType; + if (typeof pointerType === "number") { + pointerType = [ , , "touch", "pen", "mouse" ][pointerType]; + } + return pointerType || ""; + }-*/; + + private static native final boolean isPrimary(NativeEvent e) + /*-{ + return e.isPrimary; + }-*/; + +} diff --git a/client/src/com/vaadin/client/event/PointerEventSupport.java b/client/src/com/vaadin/client/event/PointerEventSupport.java new file mode 100644 index 0000000000..af8a444247 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerEventSupport.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.event; + +import com.google.gwt.core.client.GWT; +import com.vaadin.client.event.PointerEvent.EventType; + +/** + * Main class for pointer event support. Contains functionality for determining + * if pointer events are available or not. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class PointerEventSupport { + + private static final PointerEventSupportImpl impl = GWT + .create(PointerEventSupportImpl.class); + + private PointerEventSupport() { + } + + public static void init() { + impl.init(); + } + + /** + * @return true if pointer events are supported by the browser, false + * otherwise + */ + public static boolean isSupported() { + return impl.isSupported(); + } + + /** + * @param eventType + * @return the native event name of the given event + */ + public static String getNativeEventName(EventType eventType) { + return impl.getNativeEventName(eventType); + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/event/PointerEventSupportImpl.java b/client/src/com/vaadin/client/event/PointerEventSupportImpl.java new file mode 100644 index 0000000000..75cbfce690 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerEventSupportImpl.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.event; + +import com.vaadin.client.event.PointerEvent.EventType; + +/** + * Main pointer event support implementation class. Made for browser without + * pointer event support. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class PointerEventSupportImpl { + + /** + * @return true if the pointer events are supported, false otherwise + */ + protected boolean isSupported() { + return false; + } + + /** + * @param events + * @return the native event name of the given event + */ + public String getNativeEventName(EventType eventName) { + return eventName.toString().toLowerCase(); + } + + /** + * Initializes event support + */ + protected void init() { + + } + +} diff --git a/client/src/com/vaadin/client/event/PointerEventSupportImplIE10.java b/client/src/com/vaadin/client/event/PointerEventSupportImplIE10.java new file mode 100644 index 0000000000..8f60f0bcf3 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerEventSupportImplIE10.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +import com.vaadin.client.event.PointerEvent.EventType; + +/** + * Pointer event support class for IE 10 ("ms" prefixed pointer events) + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class PointerEventSupportImplIE10 extends + PointerEventSupportImplModernIE { + + @Override + public String getNativeEventName(EventType eventName) { + return "MS" + eventName; + } + +} diff --git a/client/src/com/vaadin/client/event/PointerEventSupportImplModernIE.java b/client/src/com/vaadin/client/event/PointerEventSupportImplModernIE.java new file mode 100644 index 0000000000..9c06062866 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerEventSupportImplModernIE.java @@ -0,0 +1,72 @@ +/* + * 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.event; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.user.client.impl.DOMImplStandard; +import com.vaadin.client.event.PointerEvent.EventType; + +/** + * Pointer event support class for IE 11+ (unprefixed pointer events) + * + * @since 7.2 + * @author Vaadin Ltd + */ + +public class PointerEventSupportImplModernIE extends PointerEventSupportImpl { + + protected static boolean inited = false; + + @Override + protected boolean isSupported() { + return true; + } + + @Override + protected void init() { + if (!inited) { + JavaScriptObject eventDispatcherMapExtensions = JavaScriptObject + .createObject(); + JavaScriptObject captureEventDispatcherMapExtensions = JavaScriptObject + .createObject(); + for (EventType e : EventType.values()) { + addEventDispatcher(e.getNativeEventName(), + eventDispatcherMapExtensions); + getPointerEventCaptureDispatchers(e.getNativeEventName(), + captureEventDispatcherMapExtensions); + } + DOMImplStandard + .addBitlessEventDispatchers(eventDispatcherMapExtensions); + DOMImplStandard + .addCaptureEventDispatchers(captureEventDispatcherMapExtensions); + + inited = true; + } + } + + private static native void addEventDispatcher(String eventName, + JavaScriptObject jso) + /*-{ + jso[eventName] = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchEvent(*); + }-*/; + + private static native void getPointerEventCaptureDispatchers( + String eventName, JavaScriptObject jso) + /*-{ + jso[eventName] = @com.google.gwt.user.client.impl.DOMImplStandard::dispatchCapturedMouseEvent(*); + }-*/; + +} diff --git a/client/src/com/vaadin/client/event/PointerMoveEvent.java b/client/src/com/vaadin/client/event/PointerMoveEvent.java new file mode 100644 index 0000000000..dd34e979e2 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerMoveEvent.java @@ -0,0 +1,61 @@ +/* + * 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.event; + +import com.google.gwt.event.dom.client.DomEvent; + +/** + * Represents a native PointerMoveEvent event. + * + * @since 7.2 + */ +public class PointerMoveEvent extends PointerEvent<PointerMoveHandler> { + + /** + * Event type for PointerMoveEvent. Represents the meta-data associated with + * this event. + */ + private static final Type<PointerMoveHandler> TYPE = new Type<PointerMoveHandler>( + EventType.PointerMove.getNativeEventName(), new PointerMoveEvent()); + + /** + * Gets the event type associated with PointerMoveEvent. + * + * @return the handler type + */ + public static Type<PointerMoveHandler> getType() { + return TYPE; + } + + /** + * Protected constructor, use + * {@link DomEvent#fireNativeEvent(com.google.gwt.dom.client.NativeEvent, com.google.gwt.event.shared.HasHandlers)} + * to fire pointer down events. + */ + protected PointerMoveEvent() { + } + + @Override + public final Type<PointerMoveHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(PointerMoveHandler handler) { + handler.onPointerMove(this); + } + +} diff --git a/client/src/com/vaadin/client/event/PointerMoveHandler.java b/client/src/com/vaadin/client/event/PointerMoveHandler.java new file mode 100644 index 0000000000..99f693ecf9 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerMoveHandler.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler interface for {@link PointerMoveEvent} events. + * + * @since 7.2 + */ +public interface PointerMoveHandler extends EventHandler { + + /** + * Called when PointerMoveEvent is fired. + * + * @param event + * the {@link PointerMoveEvent} that was fired + */ + void onPointerMove(PointerMoveEvent event); +} diff --git a/client/src/com/vaadin/client/event/PointerUpEvent.java b/client/src/com/vaadin/client/event/PointerUpEvent.java new file mode 100644 index 0000000000..565bf5b644 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerUpEvent.java @@ -0,0 +1,61 @@ +/* + * 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.event; + +import com.google.gwt.event.dom.client.DomEvent; + +/** + * Represents a native PointerUpEvent. + * + * @since 7.2 + */ +public class PointerUpEvent extends PointerEvent<PointerUpHandler> { + + /** + * Event type for PointerUpEvent. Represents the meta-data associated with + * this event. + */ + private static final Type<PointerUpHandler> TYPE = new Type<PointerUpHandler>( + EventType.PointerUp.getNativeEventName(), new PointerUpEvent()); + + /** + * Gets the event type associated with PointerUpEvent. + * + * @return the handler type + */ + public static Type<PointerUpHandler> getType() { + return TYPE; + } + + /** + * Protected constructor, use + * {@link DomEvent#fireNativeEvent(com.google.gwt.dom.client.NativeEvent, com.google.gwt.event.shared.HasHandlers)} + * to fire pointer down events. + */ + protected PointerUpEvent() { + } + + @Override + public final Type<PointerUpHandler> getAssociatedType() { + return TYPE; + } + + @Override + protected void dispatch(PointerUpHandler handler) { + handler.onPointerUp(this); + } + +} diff --git a/client/src/com/vaadin/client/event/PointerUpHandler.java b/client/src/com/vaadin/client/event/PointerUpHandler.java new file mode 100644 index 0000000000..1ffa8abf93 --- /dev/null +++ b/client/src/com/vaadin/client/event/PointerUpHandler.java @@ -0,0 +1,34 @@ +/* + * 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.event; + +import com.google.gwt.event.shared.EventHandler; + +/** + * Handler interface for {@link PointerUpEvent} events. + * + * @since 7.2 + */ +public interface PointerUpHandler extends EventHandler { + + /** + * Called when PointerUpEvent is fired. + * + * @param event + * the {@link PointerUpEvent} that was fired + */ + void onPointerUp(PointerUpEvent event); +} diff --git a/client/src/com/vaadin/client/extensions/ResponsiveConnector.java b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java new file mode 100644 index 0000000000..500e4a0916 --- /dev/null +++ b/client/src/com/vaadin/client/extensions/ResponsiveConnector.java @@ -0,0 +1,406 @@ +/* + * 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.extensions; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.dom.client.Element; +import com.vaadin.client.BrowserInfo; +import com.vaadin.client.LayoutManager; +import com.vaadin.client.ServerConnector; +import com.vaadin.client.ui.AbstractComponentConnector; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; +import com.vaadin.server.Responsive; +import com.vaadin.shared.ui.Connect; + +/** + * The client side connector for the Responsive extension. + * + * @author Vaadin Ltd + * @since 7.2 + */ +@SuppressWarnings("GwtInconsistentSerializableClass") +@Connect(Responsive.class) +public class ResponsiveConnector extends AbstractExtensionConnector implements + ElementResizeListener { + + /** + * The target component which we will monitor for width changes + */ + protected AbstractComponentConnector target; + + /** + * All the width breakpoints found for this particular instance + */ + protected JavaScriptObject widthBreakpoints; + + /** + * All the height breakpoints found for this particular instance + */ + protected JavaScriptObject heightBreakpoints; + + /** + * All width-range breakpoints found from the style sheets on the page. + * Common for all instances. + */ + protected static JavaScriptObject widthRangeCache; + + /** + * All height-range breakpoints found from the style sheets on the page. + * Common for all instances. + */ + protected static JavaScriptObject heightRangeCache; + + private static Logger getLogger() { + return Logger.getLogger(ResponsiveConnector.class.getName()); + } + + private static void error(String message) { + getLogger().log(Level.SEVERE, message); + } + + @Override + protected void extend(ServerConnector target) { + // Initialize cache if not already done + if (widthRangeCache == null) { + searchForBreakPoints(); + } + + this.target = (AbstractComponentConnector) target; + + // Get any breakpoints from the styles defined for this widget + getBreakPointsFor(constructSelectorsForTarget()); + + // Start listening for size changes + LayoutManager.get(getConnection()).addElementResizeListener( + this.target.getWidget().getElement(), this); + } + + /** + * Construct the list of selectors that should be matched against in the + * range selectors + * + * @return The selectors in a comma delimited string. + */ + protected String constructSelectorsForTarget() { + String primaryStyle = this.target.getState().primaryStyleName; + StringBuilder selectors = new StringBuilder(); + selectors.append(".").append(primaryStyle); + + if (this.target.getState().styles != null + && this.target.getState().styles.size() > 0) { + for (String style : this.target.getState().styles) { + selectors.append(",.").append(style); + selectors.append(",.").append(primaryStyle).append(".") + .append(style); + selectors.append(",.").append(style).append(".") + .append(primaryStyle); + selectors.append(",.").append(primaryStyle).append("-") + .append(style); + } + } + + // Allow the ID to be used as the selector as well for ranges + if (this.target.getState().id != null) { + selectors.append(",#").append(this.target.getState().id); + } + return selectors.toString(); + } + + @Override + public void onUnregister() { + super.onUnregister(); + LayoutManager.get(getConnection()).removeElementResizeListener( + this.target.getWidget().getElement(), this); + } + + /** + * Build a cache of all 'width-range' and 'height-range' attribute selectors + * found in the stylesheets. + */ + private static native void searchForBreakPoints() + /*-{ + + // Initialize variables + @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache = []; + @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache = []; + + var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache; + var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache; + + // Can't do squat if we can't parse stylesheets + if(!$doc.styleSheets) + return; + + var sheets = $doc.styleSheets; + + // Loop all stylesheets on the page and process them individually + for(var i = 0, len = sheets.length; i < len; i++) { + var sheet = sheets[i]; + @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(sheet); + } + + }-*/; + + /** + * Process an individual stylesheet object. Any @import statements are + * handled recursively. Regular rule declarations are searched for + * 'width-range' and 'height-range' attribute selectors. + * + * @param sheet + */ + private static native void searchStylesheetForBreakPoints( + final JavaScriptObject sheet) + /*-{ + + // Inline variables for easier reading + var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache; + var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache; + + // Get all the rulesets from the stylesheet + var theRules = new Array(); + var IE = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE()(); + var IE8 = @com.vaadin.client.BrowserInfo::get()().@com.vaadin.client.BrowserInfo::isIE8()(); + + if (sheet.cssRules) { + theRules = sheet.cssRules + } else if (sheet.rules) { + theRules = sheet.rules + } + + // Special import handling for IE8 + if (IE8) { + try { + for(var i = 0, len = sheet.imports.length; i < len; i++) { + @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(sheet.imports[i]); + } + } catch(e) { + // This is added due to IE8 failing to handle imports of some sheets for unknown reason (throws a permission denied exception) + @com.vaadin.client.extensions.ResponsiveConnector::error(Ljava/lang/String;)("Failed to handle imports of CSS style sheet: " + sheet.href); + } + } + + // Loop through the rulesets + for(var i = 0, len = theRules.length; i < len; i++) { + var rule = theRules[i]; + + if(rule.type == 3) { + // @import rule, traverse recursively + @com.vaadin.client.extensions.ResponsiveConnector::searchStylesheetForBreakPoints(Lcom/google/gwt/core/client/JavaScriptObject;)(rule.styleSheet); + + } else if(rule.type == 1 ||Â !rule.type) { + // Regular selector rule + + // IE parses CSS like .class[attr="val"] into [attr="val"].class so we need to check for both + + // Pattern for matching [width-range] selectors + var widths = IE? /\[width-range~?=["|'](.*)-(.*)["|']\]([\.|#]\S+)/i : /([\.|#]\S+)\[width-range~?=["|'](.*)-(.*)["|']\]/i; + + // Patter for matching [height-range] selectors + var heights = IE? /\[height-range~?=["|'](.*)-(.*)["|']\]([\.|#]\S+)/i : /([\.|#]\S+)\[height-range~?=["|'](.*)-(.*)["|']\]/i; + + // Array of all of the separate selectors in this ruleset + var haystack = rule.selectorText.split(","); + + // Loop all the selectors in this ruleset + for(var k = 0, len2 = haystack.length; k < len2; k++) { + var result; + + // Check for width-range matches + if(result = haystack[k].match(widths)) { + var selector = IE? result[3] : result[1] + var min = IE? result[1] : result[2]; + var max = IE? result[2] : result[3]; + + // Avoid adding duplicates + var duplicate = false; + for(var l = 0, len3 = widthRanges.length; l < len3; l++) { + var bp = widthRanges[l]; + if(selector == bp[0] && min == bp[1] && max == bp[2]) { + duplicate = true; + break; + } + } + if(!duplicate) { + widthRanges.push([selector, min, max]); + } + } + + // Check for height-range matches + if(result = haystack[k].match(heights)) { + var selector = IE? result[3] : result[1] + var min = IE? result[1] : result[2]; + var max = IE? result[2] : result[3]; + + // Avoid adding duplicates + var duplicate = false; + for(var l = 0, len3 = heightRanges.length; l < len3; l++) { + var bp = heightRanges[l]; + if(selector == bp[0] && min == bp[1] && max == bp[2]) { + duplicate = true; + break; + } + } + if(!duplicate) { + heightRanges.push([selector, min, max]); + } + } + } + } + } + + }-*/; + + /** + * Get all matching ranges from the cache for this particular instance. + * + * @param selectors + */ + private native void getBreakPointsFor(final String selectors) + /*-{ + + var selectors = selectors.split(","); + + var widthBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints = []; + var heightBreakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints = []; + + var widthRanges = @com.vaadin.client.extensions.ResponsiveConnector::widthRangeCache; + var heightRanges = @com.vaadin.client.extensions.ResponsiveConnector::heightRangeCache; + + for(var i = 0, len = widthRanges.length; i < len; i++) { + var bp = widthRanges[i]; + for(var j = 0, len2 = selectors.length; j < len2; j++) { + if(bp[0] == selectors[j]) + widthBreakpoints.push(bp); + } + } + + for(var i = 0, len = heightRanges.length; i < len; i++) { + var bp = heightRanges[i]; + for(var j = 0, len2 = selectors.length; j < len2; j++) { + if(bp[0] == selectors[j]) + heightBreakpoints.push(bp); + } + } + + // Only for debugging + // console.log("Breakpoints for", selectors.join(","), widthBreakpoints, heightBreakpoints); + + }-*/; + + private String currentWidthRanges; + private String currentHeightRanges; + + @Override + public void onElementResize(ElementResizeEvent event) { + int width = event.getLayoutManager().getOuterWidth(event.getElement()); + int height = event.getLayoutManager() + .getOuterHeight(event.getElement()); + + com.google.gwt.user.client.Element element = this.target.getWidget() + .getElement(); + boolean forceRedraw = false; + + // Loop through breakpoints and see which one applies to this width + currentWidthRanges = resolveBreakpoint("width", width, + event.getElement()); + + if (!"".equals(currentWidthRanges)) { + this.target.getWidget().getElement() + .setAttribute("width-range", currentWidthRanges); + forceRedraw = true; + } else { + element.removeAttribute("width-range"); + } + + // Loop through breakpoints and see which one applies to this height + currentHeightRanges = resolveBreakpoint("height", height, + event.getElement()); + + if (!"".equals(currentHeightRanges)) { + this.target.getWidget().getElement() + .setAttribute("height-range", currentHeightRanges); + forceRedraw = true; + } else { + element.removeAttribute("height-range"); + } + + if (forceRedraw) { + forceRedrawIfIE8(element); + } + } + + /** + * Forces IE8 to reinterpret CSS rules. + * {@link com.vaadin.client.Util#forceIE8Redraw(com.google.gwt.dom.client.Element)} + * doesn't work in this case. + * + * @param element + * the element to redraw + */ + private void forceRedrawIfIE8(Element element) { + if (BrowserInfo.get().isIE8()) { + element.addClassName("foo"); + element.removeClassName("foo"); + } + } + + private native String resolveBreakpoint(String which, int size, + Element element) + /*-{ + + // Default to "width" breakpoints + var breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::widthBreakpoints; + + // Use height breakpoints if we're measuring the height + if(which == "height") + breakpoints = this.@com.vaadin.client.extensions.ResponsiveConnector::heightBreakpoints; + + // Output string that goes into either the "width-range" or "height-range" attribute in the element + var ranges = ""; + + // Loop the breakpoints + for(var i = 0, len = breakpoints.length; i < len; i++) { + var bp = breakpoints[i]; + + var min = parseInt(bp[1]); + var max = parseInt(bp[2]); + + if(min && max) { + if(min <= size && size <= max) { + ranges += " " + bp[1] + "-" + bp[2]; + } + } else if (min) { + if(min <= size) { + ranges += " " + bp[1] + "-"; + } + } else if (max) { + if (size <= max) { + ranges += " -" + bp[2]; + } + } + } + + // Trim the output and return it + return ranges.replace(/^\s+/, ""); + + }-*/; + +} diff --git a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java index f1a9fa1ee7..8148010b54 100644 --- a/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java +++ b/client/src/com/vaadin/client/metadata/ConnectorBundleLoader.java @@ -15,11 +15,10 @@ */ package com.vaadin.client.metadata; -import java.util.HashMap; import java.util.List; -import java.util.Map; import com.google.gwt.core.shared.GWT; +import com.vaadin.client.FastStringMap; import com.vaadin.client.metadata.AsyncBundleLoader.State; public abstract class ConnectorBundleLoader { @@ -28,8 +27,9 @@ public abstract class ConnectorBundleLoader { private static ConnectorBundleLoader impl; - private Map<String, AsyncBundleLoader> asyncBlockLoaders = new HashMap<String, AsyncBundleLoader>(); - private Map<String, String> identifierToBundle = new HashMap<String, String>(); + private FastStringMap<AsyncBundleLoader> asyncBlockLoaders = FastStringMap + .create(); + private FastStringMap<String> identifierToBundle = FastStringMap.create(); private final TypeDataStore datStore = new TypeDataStore(); diff --git a/client/src/com/vaadin/client/metadata/JsniInvoker.java b/client/src/com/vaadin/client/metadata/JsniInvoker.java new file mode 100644 index 0000000000..4692a18cfe --- /dev/null +++ b/client/src/com/vaadin/client/metadata/JsniInvoker.java @@ -0,0 +1,53 @@ +/* + * 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.metadata; + +import com.google.gwt.core.client.JavaScriptObject; +import com.vaadin.client.JsArrayObject; + +/** + * Special {@link Invoker} that uses JSNI to invoke methods with limited + * visibility. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public abstract class JsniInvoker implements Invoker { + + @Override + public Object invoke(Object target, Object... params) { + JsArrayObject<Object> jsParams = JavaScriptObject.createArray().cast(); + for (Object object : params) { + jsParams.add(object); + } + return jsniInvoke(target, jsParams); + } + + /** + * Abstract method that will be generated to contain JSNI for invoking the + * actual method. + * + * @param target + * the object upon which to invoke the method + * @param params + * a js array with arguments to pass to the method + * @return the value returned by the invoked method, or <code>null</code> if + * the target method return type is <code>void</code>. + */ + protected abstract Object jsniInvoke(Object target, + JsArrayObject<Object> params); + +} diff --git a/client/src/com/vaadin/client/metadata/Method.java b/client/src/com/vaadin/client/metadata/Method.java index aea2fd7f85..390574cdf8 100644 --- a/client/src/com/vaadin/client/metadata/Method.java +++ b/client/src/com/vaadin/client/metadata/Method.java @@ -19,13 +19,10 @@ public class Method { private final Type type; private final String name; - private String signature; public Method(Type type, String name) { this.type = type; this.name = name; - // Cache derived signature value - signature = type.getSignature() + "." + name; } public Type getType() { @@ -53,7 +50,20 @@ public class Method { * @return the unique signature of this method */ public String getSignature() { - return signature; + return type.getSignature() + "." + name; + } + + /** + * Gets the string that is internally used when looking up generated support + * code for this method. This is the same as {@link #getSignature()}, but + * without any type parameters. + * + * @return the string to use for looking up generated support code + * + * @since 7.2 + */ + public String getLookupKey() { + return type.getBaseTypeName() + "." + name; } @Override diff --git a/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java b/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java new file mode 100644 index 0000000000..2ba06fd4eb --- /dev/null +++ b/client/src/com/vaadin/client/metadata/OnStateChangeMethod.java @@ -0,0 +1,111 @@ +/* + * 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.metadata; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import com.vaadin.client.ServerConnector; +import com.vaadin.client.annotations.OnStateChange; +import com.vaadin.client.communication.StateChangeEvent; + +/** + * Encapsulates the data that the widgetset compiler generates for supporting a + * connector method annotated with {@link OnStateChange} + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class OnStateChangeMethod { + + private final String methodName; + private final List<String> properties; + private final Class<?> declaringClass; + + /** + * Creates a new instance based on a method name, a list of parameters names + * and a list of properties to listen for. + * + * @param methodName + * the name of the method to call + * @param properties + * an array of state property names to listen to + */ + public OnStateChangeMethod(String methodName, String[] properties) { + this(null, methodName, properties); + } + + /** + * Creates a new instance based on declaring class, a method name, a list of + * parameters names and a list of properties to listen for. + * <p> + * If the declaring class is <code>null</code>, the method is found based on + * the type of the connector that fired the state change event. + * + * @param declaringClass + * the class in which the target method is declared, or + * <code>null</code> to use the class of the connector firing the + * event + * @param methodName + * the name of the method to call + * @param properties + * an array of state property names to listen to + */ + public OnStateChangeMethod(Class<?> declaringClass, String methodName, + String[] properties) { + + this.methodName = methodName; + + this.properties = Collections.unmodifiableList(Arrays + .asList(properties)); + + this.declaringClass = declaringClass; + } + + /** + * Invokes the listener method for a state change. + * + * @param stateChangeEvent + * the state change event + */ + public void invoke(StateChangeEvent stateChangeEvent) { + ServerConnector connector = (ServerConnector) stateChangeEvent + .getSource(); + + Class<?> declaringClass = this.declaringClass; + if (declaringClass == null) { + declaringClass = connector.getClass(); + } + Type declaringType = TypeDataStore.getType(declaringClass); + + try { + declaringType.getMethod(methodName).invoke(connector); + } catch (NoDataException e) { + throw new RuntimeException("Couldn't invoke @OnStateChange method " + + declaringType.getSignature() + "." + methodName, e); + } + } + + /** + * Gets the list of state property names to listen for. + * + * @return the list of state property names to listen for + */ + public List<String> getProperties() { + return properties; + } +} diff --git a/client/src/com/vaadin/client/metadata/Property.java b/client/src/com/vaadin/client/metadata/Property.java index 2e0ea49c88..72ed7fec41 100644 --- a/client/src/com/vaadin/client/metadata/Property.java +++ b/client/src/com/vaadin/client/metadata/Property.java @@ -20,21 +20,18 @@ import com.vaadin.shared.annotations.DelegateToWidget; public class Property { private final Type bean; private final String name; - private final String signature; public Property(Type bean, String name) { this.bean = bean; this.name = name; - // Cache derived signature value - signature = bean.getSignature() + "." + name; } public Object getValue(Object bean) throws NoDataException { - return TypeDataStore.getGetter(this).invoke(bean); + return TypeDataStore.getValue(this, bean); } public void setValue(Object bean, Object value) throws NoDataException { - TypeDataStore.getSetter(this).invoke(bean, value); + TypeDataStore.setValue(this, bean, value); } public String getDelegateToWidgetMethodName() { @@ -50,6 +47,10 @@ public class Property { return TypeDataStore.getType(this); } + public Type getBeanType() { + return bean; + } + /** * The unique signature used to identify this property. The structure of the * returned string may change without notice and should not be used for any @@ -59,7 +60,20 @@ public class Property { * @return the unique signature of this property */ public String getSignature() { - return signature; + return bean.getSignature() + "." + name; + } + + /** + * Gets the string that is internally used when looking up generated support + * code for this method. This is the same as {@link #getSignature()}, but + * without any type parameters. + * + * @return the string to use for looking up generated support code + * + * @since 7.2 + */ + public String getLookupKey() { + return bean.getBaseTypeName() + "." + name; } @Override diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java index aa37d75dc8..bc6610a6ff 100644 --- a/client/src/com/vaadin/client/metadata/TypeDataStore.java +++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java @@ -23,6 +23,7 @@ import com.google.gwt.core.client.JsArrayString; import com.vaadin.client.FastStringMap; import com.vaadin.client.FastStringSet; import com.vaadin.client.JsArrayObject; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.JSONSerializer; public class TypeDataStore { @@ -34,11 +35,15 @@ public class TypeDataStore { .create(); private final FastStringMap<ProxyHandler> proxyHandlers = FastStringMap .create(); - private final FastStringMap<JsArrayObject<Property>> properties = FastStringMap - .create(); private final FastStringMap<JsArrayString> delegateToWidgetProperties = FastStringMap .create(); + /** + * Maps connector class -> state property name -> hander method data + */ + private final FastStringMap<FastStringMap<JsArrayObject<OnStateChangeMethod>>> onStateChangeMethods = FastStringMap + .create(); + private final FastStringSet delayedMethods = FastStringSet.create(); private final FastStringSet lastOnlyMethods = FastStringSet.create(); @@ -46,12 +51,11 @@ public class TypeDataStore { private final FastStringMap<Invoker> invokers = FastStringMap.create(); private final FastStringMap<Type[]> paramTypes = FastStringMap.create(); - private final FastStringMap<Type> propertyTypes = FastStringMap.create(); - private final FastStringMap<Invoker> setters = FastStringMap.create(); - private final FastStringMap<Invoker> getters = FastStringMap.create(); private final FastStringMap<String> delegateToWidget = FastStringMap .create(); + private final JavaScriptObject jsTypeData = JavaScriptObject.createObject(); + public static TypeDataStore get() { return ConnectorBundleLoader.get().getTypeDataStore(); } @@ -69,12 +73,28 @@ public class TypeDataStore { return class1; } + // this is a very inefficient implementation for getting all the identifiers + // for a class + public FastStringSet findIdentifiersFor(Class<?> type) { + FastStringSet result = FastStringSet.create(); + + JsArrayString keys = identifiers.getKeys(); + for (int i = 0; i < keys.length(); i++) { + String key = keys.get(i); + if (identifiers.get(key) == type) { + result.add(key); + } + } + + return result; + } + public static Type getType(Class<?> clazz) { return new Type(clazz); } public static Type getReturnType(Method method) throws NoDataException { - Type type = get().returnTypes.get(method.getSignature()); + Type type = get().returnTypes.get(method.getLookupKey()); if (type == null) { throw new NoDataException("There is no return type for " + method.getSignature()); @@ -83,7 +103,7 @@ public class TypeDataStore { } public static Invoker getInvoker(Method method) throws NoDataException { - Invoker invoker = get().invokers.get(method.getSignature()); + Invoker invoker = get().invokers.get(method.getLookupKey()); if (invoker == null) { throw new NoDataException("There is no invoker for " + method.getSignature()); @@ -93,7 +113,7 @@ public class TypeDataStore { public static Invoker getConstructor(Type type) throws NoDataException { Invoker invoker = get().invokers.get(new Method(type, CONSTRUCTOR_NAME) - .getSignature()); + .getLookupKey()); if (invoker == null) { throw new NoDataException("There is no constructor for " + type.getSignature()); @@ -101,45 +121,37 @@ public class TypeDataStore { return invoker; } - public static Invoker getGetter(Property property) throws NoDataException { - Invoker getter = get().getters.get(property.getSignature()); - if (getter == null) { - throw new NoDataException("There is no getter for " - + property.getSignature()); - } - - return getter; - } - - public void setGetter(Class<?> clazz, String propertyName, Invoker invoker) { - getters.put(new Property(getType(clazz), propertyName).getSignature(), - invoker); + public static Object getValue(Property property, Object target) + throws NoDataException { + return getJsPropertyValue(get().jsTypeData, property.getBeanType() + .getBaseTypeName(), property.getName(), target); } public static String getDelegateToWidget(Property property) { - return get().delegateToWidget.get(property.getSignature()); + return get().delegateToWidget.get(property.getLookupKey()); } public static JsArrayString getDelegateToWidgetProperites(Type type) { - return get().delegateToWidgetProperties.get(type.getSignature()); + return get().delegateToWidgetProperties.get(type.getBaseTypeName()); } public void setDelegateToWidget(Class<?> clazz, String propertyName, String delegateValue) { Type type = getType(clazz); - delegateToWidget.put(new Property(type, propertyName).getSignature(), + delegateToWidget.put(new Property(type, propertyName).getLookupKey(), delegateValue); JsArrayString typeProperties = delegateToWidgetProperties.get(type - .getSignature()); + .getBaseTypeName()); if (typeProperties == null) { typeProperties = JavaScriptObject.createArray().cast(); - delegateToWidgetProperties.put(type.getSignature(), typeProperties); + delegateToWidgetProperties.put(type.getBaseTypeName(), + typeProperties); } typeProperties.push(propertyName); } public void setReturnType(Class<?> type, String methodName, Type returnType) { - returnTypes.put(new Method(getType(type), methodName).getSignature(), + returnTypes.put(new Method(getType(type), methodName).getLookupKey(), returnType); } @@ -148,12 +160,12 @@ public class TypeDataStore { } public void setInvoker(Class<?> type, String methodName, Invoker invoker) { - invokers.put(new Method(getType(type), methodName).getSignature(), + invokers.put(new Method(getType(type), methodName).getLookupKey(), invoker); } public static Type[] getParamTypes(Method method) throws NoDataException { - Type[] types = get().paramTypes.get(method.getSignature()); + Type[] types = get().paramTypes.get(method.getLookupKey()); if (types == null) { throw new NoDataException("There are no parameter type data for " + method.getSignature()); @@ -164,7 +176,7 @@ public class TypeDataStore { public void setParamTypes(Class<?> type, String methodName, Type[] paramTypes) { this.paramTypes.put( - new Method(getType(type), methodName).getSignature(), + new Method(getType(type), methodName).getLookupKey(), paramTypes); } @@ -174,8 +186,8 @@ public class TypeDataStore { public static ProxyHandler getProxyHandler(Type type) throws NoDataException { - ProxyHandler proxyHandler = get().proxyHandlers - .get(type.getSignature()); + ProxyHandler proxyHandler = get().proxyHandlers.get(type + .getBaseTypeName()); if (proxyHandler == null) { throw new NoDataException("No proxy handler for " + type.getSignature()); @@ -184,24 +196,24 @@ public class TypeDataStore { } public void setProxyHandler(Class<?> type, ProxyHandler proxyHandler) { - proxyHandlers.put(getType(type).getSignature(), proxyHandler); + proxyHandlers.put(getType(type).getBaseTypeName(), proxyHandler); } public static boolean isDelayed(Method method) { - return get().delayedMethods.contains(method.getSignature()); + return get().delayedMethods.contains(method.getLookupKey()); } public void setDelayed(Class<?> type, String methodName) { - delayedMethods.add(getType(type).getMethod(methodName).getSignature()); + delayedMethods.add(getType(type).getMethod(methodName).getLookupKey()); } public static boolean isLastOnly(Method method) { - return get().lastOnlyMethods.contains(method.getSignature()); + return get().lastOnlyMethods.contains(method.getLookupKey()); } public void setLastOnly(Class<?> clazz, String methodName) { lastOnlyMethods - .add(getType(clazz).getMethod(methodName).getSignature()); + .add(getType(clazz).getMethod(methodName).getLookupKey()); } /** @@ -227,60 +239,40 @@ public class TypeDataStore { public static JsArrayObject<Property> getPropertiesAsArray(Type type) throws NoDataException { - JsArrayObject<Property> properties = get().properties.get(type - .getSignature()); - if (properties == null) { - throw new NoDataException("No property list for " - + type.getSignature()); - } - return properties; - } + JsArrayString names = getJsPropertyNames(get().jsTypeData, + type.getBaseTypeName()); - public void setProperties(Class<?> clazz, String[] propertyNames) { + // Create Property instances for each property name JsArrayObject<Property> properties = JavaScriptObject.createArray() .cast(); - Type type = getType(clazz); - for (String name : propertyNames) { - properties.add(new Property(type, name)); + for (int i = 0; i < names.length(); i++) { + properties.add(new Property(type, names.get(i))); } - this.properties.put(type.getSignature(), properties); - } - public static Type getType(Property property) throws NoDataException { - Type type = get().propertyTypes.get(property.getSignature()); - if (type == null) { - throw new NoDataException("No return type for " - + property.getSignature()); - } - return type; + return properties; } - public void setPropertyType(Class<?> clazz, String propertName, Type type) { - propertyTypes.put( - new Property(getType(clazz), propertName).getSignature(), type); + public static Type getType(Property property) throws NoDataException { + return getJsPropertyType(get().jsTypeData, property.getBeanType() + .getBaseTypeName(), property.getName()); } - public static Invoker getSetter(Property property) throws NoDataException { - Invoker setter = get().setters.get(property.getSignature()); - if (setter == null) { - throw new NoDataException("No setter for " - + property.getSignature()); - } - return setter; + public void setPropertyType(Class<?> clazz, String propertyName, Type type) { + setJsPropertyType(jsTypeData, clazz.getName(), propertyName, type); } - public void setSetter(Class<?> clazz, String propertyName, Invoker setter) { - setters.put(new Property(getType(clazz), propertyName).getSignature(), - setter); + public static void setValue(Property property, Object target, Object value) { + setJsPropertyValue(get().jsTypeData, property.getBeanType() + .getBaseTypeName(), property.getName(), target, value); } public void setSerializerFactory(Class<?> clazz, Invoker factory) { - serializerFactories.put(getType(clazz).getSignature(), factory); + serializerFactories.put(getType(clazz).getBaseTypeName(), factory); } public static JSONSerializer<?> findSerializer(Type type) { Invoker factoryCreator = get().serializerFactories.get(type - .getSignature()); + .getBaseTypeName()); if (factoryCreator == null) { return null; } @@ -288,6 +280,142 @@ public class TypeDataStore { } public static boolean hasProperties(Type type) { - return get().properties.containsKey(type.getSignature()); + return hasJsProperties(get().jsTypeData, type.getBaseTypeName()); + } + + public void setSuperClass(Class<?> baseClass, Class<?> superClass) { + String superClassName = superClass == null ? null : superClass + .getName(); + setSuperClass(jsTypeData, baseClass.getName(), superClassName); + } + + public void setPropertyData(Class<?> type, String propertyName, + JavaScriptObject propertyData) { + setPropertyData(jsTypeData, type.getName(), propertyName, propertyData); + } + + private static native void setPropertyData(JavaScriptObject typeData, + String className, String propertyName, JavaScriptObject propertyData) + /*-{ + typeData[className][propertyName] = propertyData; + }-*/; + + /* + * This method sets up prototypes chain for <code>baseClassName</code>. + * Precondition is : <code>superClassName</code> had to be handled before + * its child <code>baseClassName</code>. + * + * It makes all properties defined in the <code>superClassName</code> + * available for <code>baseClassName</code> as well. + */ + private static native void setSuperClass(JavaScriptObject typeData, + String baseClassName, String superClassName) + /*-{ + var parentType = typeData[superClassName]; + if (parentType !== undefined ){ + var ctor = function () {}; + ctor.prototype = parentType; + typeData[baseClassName] = new ctor; + } + else { + typeData[baseClassName] = {}; + } + }-*/; + + private static native boolean hasGetter(JavaScriptObject typeData, + String beanName, String propertyName) + /*-{ + return typeData[beanName][propertyName].getter !== undefined; + }-*/; + + private static native boolean hasSetter(JavaScriptObject typeData, + String beanName, String propertyName) + /*-{ + return typeData[beanName][propertyName].setter !== undefined; + }-*/; + + private static native Object getJsPropertyValue(JavaScriptObject typeData, + String beanName, String propertyName, Object beanInstance) + /*-{ + return typeData[beanName][propertyName].getter(beanInstance); + }-*/; + + private static native void setJsPropertyValue(JavaScriptObject typeData, + String beanName, String propertyName, Object beanInstance, + Object value) + /*-{ + typeData[beanName][propertyName].setter(beanInstance, value); + }-*/; + + private static native Type getJsPropertyType(JavaScriptObject typeData, + String beanName, String propertyName) + /*-{ + return typeData[beanName][propertyName].type; + }-*/; + + private static native void setJsPropertyType(JavaScriptObject typeData, + String beanName, String propertyName, Type type) + /*-{ + typeData[beanName][propertyName].type = type; + }-*/; + + private static native JsArrayString getJsPropertyNames( + JavaScriptObject typeData, String beanName) + /*-{ + var names = []; + for(var name in typeData[beanName]) { + names.push(name); + } + return names; + }-*/; + + private static native boolean hasJsProperties(JavaScriptObject typeData, + String beanName) + /*-{ + return typeData[beanName] !== undefined ; + }-*/; + + /** + * Gets data for all methods annotated with {@link OnStateChange} in the + * given connector type. + * + * @since 7.2 + * @param type + * the connector type + * @return a map of state property names to handler method data + */ + public static FastStringMap<JsArrayObject<OnStateChangeMethod>> getOnStateChangeMethods( + Class<?> type) { + return get().onStateChangeMethods.get(getType(type).getSignature()); + } + + /** + * Adds data about a method annotated with {@link OnStateChange} for the + * given connector type. + * + * @since 7.2 + * @param clazz + * the connector type + * @param method + * the state change method data + */ + public void addOnStateChangeMethod(Class<?> clazz, + OnStateChangeMethod method) { + FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = getOnStateChangeMethods(clazz); + if (handlers == null) { + handlers = FastStringMap.create(); + onStateChangeMethods.put(getType(clazz).getSignature(), handlers); + } + + for (String property : method.getProperties()) { + JsArrayObject<OnStateChangeMethod> propertyHandlers = handlers + .get(property); + if (propertyHandlers == null) { + propertyHandlers = JsArrayObject.createArray().cast(); + handlers.put(property, propertyHandlers); + } + + propertyHandlers.add(method); + } } } diff --git a/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java b/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java index e91abe9663..32c10fac49 100644 --- a/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java +++ b/client/src/com/vaadin/client/ui/AbstractClickEventHandler.java @@ -15,6 +15,7 @@ */ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ContextMenuEvent; import com.google.gwt.event.dom.client.ContextMenuHandler; @@ -27,7 +28,6 @@ import com.google.gwt.event.dom.client.MouseUpEvent; import com.google.gwt.event.dom.client.MouseUpHandler; import com.google.gwt.event.shared.EventHandler; 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; @@ -238,7 +238,7 @@ public abstract class AbstractClickEventHandler implements MouseDownHandler, * @return The Element used for calculating relative coordinates for a click * or null if no relative coordinates can be calculated. */ - protected Element getRelativeToElement() { + protected com.google.gwt.user.client.Element getRelativeToElement() { return connector.getWidget().getElement(); } diff --git a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java index 8fdaf0a5be..f6c26cda05 100644 --- a/client/src/com/vaadin/client/ui/AbstractComponentConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractComponentConnector.java @@ -91,9 +91,8 @@ public abstract class AbstractComponentConnector extends AbstractConnector "Default implementation of createWidget() does not work for " + Util.getSimpleName(this) + ". This might be caused by explicitely using " - + "super.createWidget(), using a widget type with " - + "generics or some unspecified problem with the " - + "widgetset compilation.", e); + + "super.createWidget() or some unspecified " + + "problem with the widgetset compilation.", e); } } @@ -462,15 +461,24 @@ public abstract class AbstractComponentConnector extends AbstractConnector } /** - * Gets the icon set for this component. + * Gets the URI of the icon set for this component. * - * @return the URL of the icon, or <code>null</code> if no icon has been + * @return the URI of the icon, or <code>null</code> if no icon has been * defined. */ - protected String getIcon() { + protected String getIconUri() { return getResourceUrl(ComponentConstants.ICON_RESOURCE); } + /** + * Gets the icon set for this component. + * + * @return the icon, or <code>null</code> if no icon has been defined. + */ + protected Icon getIcon() { + return getConnection().getIcon(getIconUri()); + } + /* * (non-Javadoc) * diff --git a/client/src/com/vaadin/client/ui/AbstractConnector.java b/client/src/com/vaadin/client/ui/AbstractConnector.java index 6855c5cd2d..bd499ac4bc 100644 --- a/client/src/com/vaadin/client/ui/AbstractConnector.java +++ b/client/src/com/vaadin/client/ui/AbstractConnector.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -27,6 +28,7 @@ import com.google.gwt.event.shared.HandlerManager; import com.google.web.bindery.event.shared.HandlerRegistration; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.FastStringMap; +import com.vaadin.client.JsArrayObject; import com.vaadin.client.Profiler; import com.vaadin.client.ServerConnector; import com.vaadin.client.Util; @@ -35,8 +37,10 @@ import com.vaadin.client.communication.RpcProxy; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.metadata.NoDataException; +import com.vaadin.client.metadata.OnStateChangeMethod; import com.vaadin.client.metadata.Type; import com.vaadin.client.metadata.TypeData; +import com.vaadin.client.metadata.TypeDataStore; import com.vaadin.shared.communication.ClientRpc; import com.vaadin.shared.communication.ServerRpc; import com.vaadin.shared.communication.SharedState; @@ -290,6 +294,37 @@ public abstract class AbstractConnector implements ServerConnector, } updateEnabledState(isEnabled()); + + FastStringMap<JsArrayObject<OnStateChangeMethod>> handlers = TypeDataStore + .getOnStateChangeMethods(getClass()); + if (handlers != null) { + Profiler.enter("AbstractConnector.onStateChanged @OnStateChange"); + + HashSet<OnStateChangeMethod> invokedMethods = new HashSet<OnStateChangeMethod>(); + + JsArrayString propertyNames = handlers.getKeys(); + for (int i = 0; i < propertyNames.length(); i++) { + String propertyName = propertyNames.get(i); + + if (stateChangeEvent.hasPropertyChanged(propertyName)) { + JsArrayObject<OnStateChangeMethod> propertyMethods = handlers + .get(propertyName); + + for (int j = 0; j < propertyMethods.size(); j++) { + OnStateChangeMethod method = propertyMethods.get(j); + + if (invokedMethods.add(method)) { + + method.invoke(stateChangeEvent); + + } + } + } + } + + Profiler.leave("AbstractConnector.onStateChanged @OnStateChange"); + } + Profiler.leave("AbstractConnector.onStateChanged"); } diff --git a/client/src/com/vaadin/client/ui/Action.java b/client/src/com/vaadin/client/ui/Action.java index e1b85dcb82..84399f7611 100644 --- a/client/src/com/vaadin/client/ui/Action.java +++ b/client/src/com/vaadin/client/ui/Action.java @@ -17,7 +17,6 @@ package com.vaadin.client.ui; import com.google.gwt.user.client.Command; -import com.vaadin.client.Util; /** * @@ -43,9 +42,12 @@ public abstract class Action implements Command { public String getHTML() { final StringBuffer sb = new StringBuffer(); sb.append("<div>"); - if (getIconUrl() != null) { - sb.append("<img src=\"" + Util.escapeAttribute(getIconUrl()) - + "\" alt=\"icon\" />"); + // Could store the icon in a field instead, but it doesn't really matter + // right now because Actions are recreated every time they are needed + Icon icon = owner.getClient().getIcon(getIconUrl()); + if (icon != null) { + icon.setAlternateText("icon"); + sb.append(icon.getElement().getString()); } sb.append(getCaption()); sb.append("</div>"); @@ -78,5 +80,4 @@ public abstract class Action implements Command { return "Action [owner=" + owner + ", iconUrl=" + iconUrl + ", caption=" + caption + "]"; } - } diff --git a/client/src/com/vaadin/client/ui/FocusElementPanel.java b/client/src/com/vaadin/client/ui/FocusElementPanel.java index d439a4e56a..dd5544f016 100644 --- a/client/src/com/vaadin/client/ui/FocusElementPanel.java +++ b/client/src/com/vaadin/client/ui/FocusElementPanel.java @@ -21,7 +21,6 @@ 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.Event; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.FocusImpl; @@ -47,9 +46,7 @@ public class FocusElementPanel extends SimpleFocusablePanel { style.setLeft(0, Unit.PX); getElement().appendChild(focusElement); /* Sink from focusElement too as focus and blur don't bubble */ - DOM.sinkEvents( - (com.google.gwt.user.client.Element) focusElement.cast(), - Event.FOCUSEVENTS); + DOM.sinkEvents(focusElement, Event.FOCUSEVENTS); // revert to original, not focusable getElement().setPropertyObject("tabIndex", null); } else { @@ -67,11 +64,9 @@ public class FocusElementPanel extends SimpleFocusablePanel { @Override public void setFocus(boolean focus) { if (focus) { - FocusImpl.getFocusImplForPanel().focus( - (Element) focusElement.cast()); + FocusImpl.getFocusImplForPanel().focus(focusElement); } else { - FocusImpl.getFocusImplForPanel() - .blur((Element) focusElement.cast()); + FocusImpl.getFocusImplForPanel().blur(focusElement); } } @@ -86,7 +81,7 @@ public class FocusElementPanel extends SimpleFocusablePanel { /** * @return the focus element */ - public Element getFocusElement() { + public com.google.gwt.user.client.Element getFocusElement() { return focusElement.cast(); } } diff --git a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java index 01d39392af..d01ffc00ff 100644 --- a/client/src/com/vaadin/client/ui/FocusableScrollPanel.java +++ b/client/src/com/vaadin/client/ui/FocusableScrollPanel.java @@ -29,7 +29,6 @@ import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.shared.HandlerRegistration; 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.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; @@ -75,9 +74,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements style.setLeft(0, Unit.PX); getElement().appendChild(focusElement); /* Sink from focusElemet too as focusa and blur don't bubble */ - DOM.sinkEvents( - (com.google.gwt.user.client.Element) focusElement - .cast(), Event.FOCUSEVENTS); + DOM.sinkEvents(focusElement, Event.FOCUSEVENTS); // revert to original, not focusable getElement().setPropertyObject("tabIndex", null); @@ -98,11 +95,9 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements public void setFocus(boolean focus) { if (useFakeFocusElement()) { if (focus) { - FocusImpl.getFocusImplForPanel().focus( - (Element) focusElement.cast()); + FocusImpl.getFocusImplForPanel().focus(focusElement); } else { - FocusImpl.getFocusImplForPanel().blur( - (Element) focusElement.cast()); + FocusImpl.getFocusImplForPanel().blur(focusElement); } } else { super.setFocus(focus); @@ -192,7 +187,7 @@ public class FocusableScrollPanel extends SimpleFocusablePanel implements }); } - public Element getFocusElement() { + public com.google.gwt.user.client.Element getFocusElement() { if (useFakeFocusElement()) { return focusElement.cast(); } else { diff --git a/client/src/com/vaadin/client/ui/FontIcon.java b/client/src/com/vaadin/client/ui/FontIcon.java new file mode 100644 index 0000000000..04640fab06 --- /dev/null +++ b/client/src/com/vaadin/client/ui/FontIcon.java @@ -0,0 +1,149 @@ +/* + * 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 com.google.gwt.http.client.URL; +import com.google.gwt.user.client.DOM; +import com.vaadin.shared.ApplicationConstants; + +/** + * A font-based icon implementation. + * <p> + * The icon represents a specific character (identified by codepoint, + * {@link #getCodepoint()}, {@link #setCodepoint(int)}) within a specific font + * (identified by font-family, {@link #getFontFamily()}, + * {@link #setFontFamily(String)}). + * </p> + * + * @since 7.2 + * @author Vaadin Ltd + */ +public class FontIcon extends Icon { + + private int codepoint; + private String fontFamily; + + public FontIcon() { + setElement(DOM.createSpan()); + setStyleName(CLASSNAME); + } + + @Override + public void setUri(String uri) { + String[] parts = uri.substring( + ApplicationConstants.FONTICON_PROTOCOL_PREFIX.length()).split( + "/"); + setFontFamily(URL.decode(parts[0])); + setCodepoint(Integer.parseInt(parts[1], 16)); + } + + /** + * Not implemeted for {@link FontIcon} yet. + * + * @see com.vaadin.client.ui.Icon#setAlternateText(java.lang.String) + */ + @Override + public void setAlternateText(String alternateText) { + // TODO this is mostly for WAI-ARIA and should be implemented in an + // appropriate way. + + } + + /** + * Sets the font-family from which this icon comes. Use + * {@link #setCodepoint(int)} to specify a particular icon (character) + * within the font. + * + * @param fontFamily + * font-family name + */ + protected void setFontFamily(String fontFamily) { + if (this.fontFamily != null) { + removeStyleName(getFontStylename()); + } + this.fontFamily = fontFamily; + if (fontFamily != null) { + addStyleName(getFontStylename()); + } + } + + /** + * Gets the font-family from which this icon comes. Use + * {@link #getCodepoint()} to find out which particular icon (character) + * within the font this is. + * + * @return font-family name + */ + public String getFontFamily() { + return fontFamily; + } + + /** + * Sets the codepoint indicating which particular icon (character) within + * the font-family this is. + * + * @param codepoint + */ + protected void setCodepoint(int codepoint) { + this.codepoint = codepoint; + getElement().setInnerText(new String(Character.toChars(codepoint))); + } + + /** + * Gets the codepoint indicating which particular icon (character) within + * the font-family this is. + * + * @return + */ + public int getCodepoint() { + return codepoint; + } + + /** + * Get the font-family based stylename used to apply the font-family. + * + * @since 7.2 + * @return stylename used to apply font-family + */ + protected String getFontStylename() { + if (fontFamily == null) { + return null; + } + return fontFamily.replace(' ', '-'); + } + + /** + * Checks whether or not the given uri is a font icon uri. Does not check + * whether or not the font icon is available and can be rendered. + * + * @since 7.2 + * @param uri + * @return true if it's a fonticon uri + */ + public static boolean isFontIconUri(String uri) { + return uri != null + && uri.startsWith(ApplicationConstants.FONTICON_PROTOCOL_PREFIX); + } + + @Override + public String getUri() { + if (fontFamily == null) { + return null; + } + return ApplicationConstants.FONTICON_PROTOCOL_PREFIX + fontFamily + "/" + + codepoint; + } +} diff --git a/client/src/com/vaadin/client/ui/Icon.java b/client/src/com/vaadin/client/ui/Icon.java index 9d5829c079..219692161e 100644 --- a/client/src/com/vaadin/client/ui/Icon.java +++ b/client/src/com/vaadin/client/ui/Icon.java @@ -13,45 +13,40 @@ * License for the specific language governing permissions and limitations under * the License. */ - package com.vaadin.client.ui; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.UIObject; import com.vaadin.client.ApplicationConnection; -public class Icon extends UIObject { - public static final String CLASSNAME = "v-icon"; - private final ApplicationConnection client; - private String myUri; - - public Icon(ApplicationConnection client) { - setElement(DOM.createImg()); - DOM.setElementProperty(getElement(), "alt", ""); - setStyleName(CLASSNAME); - this.client = client; - } +/** + * An abstract representation of an icon. + * + * @since 7.2 + * @author Vaadin Ltd + */ +public abstract class Icon extends UIObject { - public Icon(ApplicationConnection client, String uidlUri) { - this(client); - setUri(uidlUri); - } + public static final String CLASSNAME = "v-icon"; - public void setUri(String uidlUri) { - if (!uidlUri.equals(myUri)) { - /* - * Start sinking onload events, widgets responsibility to react. We - * must do this BEFORE we set src as IE fires the event immediately - * if the image is found in cache (#2592). - */ - sinkEvents(Event.ONLOAD); + /** + * Sets the URI for the icon. The URI should be run trough + * {@link ApplicationConnection#translateVaadinUri(String)} before setting. + * <p> + * This might be a URL referencing a image (e.g {@link ImageIcon}) or a + * custom URI (e.g {@link FontIcon}). + * </p> + * + * @param uri + * the URI for this icon + */ + public abstract void setUri(String uri); - String uri = client.translateVaadinUri(uidlUri); - DOM.setElementProperty(getElement(), "src", uri); - myUri = uidlUri; - } - } + /** + * Gets the current URI for this icon. + * + * @return URI in use + */ + public abstract String getUri(); /** * Sets the alternate text for the icon. @@ -59,9 +54,6 @@ public class Icon extends UIObject { * @param alternateText * with the alternate text. */ - public void setAlternateText(String alternateText) { - getElement().setAttribute("alt", - alternateText == null ? "" : alternateText); - } + public abstract void setAlternateText(String alternateText); } diff --git a/client/src/com/vaadin/client/ui/ImageIcon.java b/client/src/com/vaadin/client/ui/ImageIcon.java new file mode 100644 index 0000000000..787b0175aa --- /dev/null +++ b/client/src/com/vaadin/client/ui/ImageIcon.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.ui; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.vaadin.client.BrowserInfo; + +/** + * A image based implementation of {@link Icon}. + * <p> + * The image is loaded from the given URL ( {@link #setUri(String)}) and + * displayed in full. + * </p> + * + * @author Vaadin Ltd + */ +public class ImageIcon extends Icon { + public static final String CLASSNAME = "v-icon"; + + public ImageIcon() { + setElement(DOM.createImg()); + setStyleName(CLASSNAME); + } + + @Override + public void setUri(final String imageUrl) { + /* + * Start sinking onload events, widgets responsibility to react. We must + * do this BEFORE we set src as IE fires the event immediately if the + * image is found in cache (#2592). + */ + sinkEvents(Event.ONLOAD); + + if (BrowserInfo.get().isIE()) { + // apply src later for IE, to ensure onload is fired + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + DOM.setElementProperty(getElement(), "src", imageUrl); + } + }); + } + + DOM.setElementProperty(getElement(), "src", imageUrl); + + } + + @Override + public void setAlternateText(String alternateText) { + getElement().setAttribute("alt", + alternateText == null ? "" : alternateText); + } + + @Override + public String getUri() { + return DOM.getElementProperty(getElement(), "src"); + } + +} diff --git a/client/src/com/vaadin/client/ui/LayoutClickEventHandler.java b/client/src/com/vaadin/client/ui/LayoutClickEventHandler.java index adff5233fd..af398a1e07 100644 --- a/client/src/com/vaadin/client/ui/LayoutClickEventHandler.java +++ b/client/src/com/vaadin/client/ui/LayoutClickEventHandler.java @@ -16,7 +16,6 @@ package com.vaadin.client.ui; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.Element; import com.vaadin.client.ComponentConnector; import com.vaadin.client.MouseEventDetailsBuilder; import com.vaadin.shared.EventId; @@ -34,10 +33,12 @@ public abstract class LayoutClickEventHandler extends AbstractClickEventHandler super(connector, clickEventIdentifier); } - protected abstract ComponentConnector getChildComponent(Element element); + protected abstract ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element); protected ComponentConnector getChildComponent(NativeEvent event) { - return getChildComponent((Element) event.getEventTarget().cast()); + return getChildComponent((com.google.gwt.user.client.Element) event + .getEventTarget().cast()); } @Override diff --git a/client/src/com/vaadin/client/ui/ShortcutActionHandler.java b/client/src/com/vaadin/client/ui/ShortcutActionHandler.java index 525900732f..1ed044ffda 100644 --- a/client/src/com/vaadin/client/ui/ShortcutActionHandler.java +++ b/client/src/com/vaadin/client/ui/ShortcutActionHandler.java @@ -20,9 +20,9 @@ import java.util.ArrayList; import java.util.Iterator; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; 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.Event; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.KeyboardListener; diff --git a/client/src/com/vaadin/client/ui/SubPartAware.java b/client/src/com/vaadin/client/ui/SubPartAware.java index a7d72fab01..fea8c2dc4a 100644 --- a/client/src/com/vaadin/client/ui/SubPartAware.java +++ b/client/src/com/vaadin/client/ui/SubPartAware.java @@ -15,9 +15,8 @@ */ package com.vaadin.client.ui; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ComponentLocator; +import com.vaadin.client.componentlocator.ComponentLocator; /** * Interface implemented by {@link Widget}s which can provide identifiers for at @@ -36,7 +35,7 @@ public interface SubPartAware { * @return The element identified by subPart or null if the element could * not be found. */ - Element getSubPartElement(String subPart); + com.google.gwt.user.client.Element getSubPartElement(String subPart); /** * Provides an identifier that identifies the element within the component. @@ -57,6 +56,6 @@ public interface SubPartAware { * @return An identifier that uniquely identifies {@code subElement} or null * if no identifier could be provided. */ - String getSubPartName(Element subElement); + String getSubPartName(com.google.gwt.user.client.Element subElement); -}
\ No newline at end of file +} diff --git a/client/src/com/vaadin/client/ui/VAbsoluteLayout.java b/client/src/com/vaadin/client/ui/VAbsoluteLayout.java index dc080bcf7d..bbec8b9e6c 100644 --- a/client/src/com/vaadin/client/ui/VAbsoluteLayout.java +++ b/client/src/com/vaadin/client/ui/VAbsoluteLayout.java @@ -17,9 +17,9 @@ 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.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; diff --git a/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java b/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java index 1cc25c741e..a2de144ad2 100644 --- a/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java +++ b/client/src/com/vaadin/client/ui/VAbstractSplitPanel.java @@ -19,6 +19,7 @@ package com.vaadin.client.ui; import java.util.Collections; import java.util.List; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.event.dom.client.TouchCancelEvent; @@ -32,7 +33,6 @@ import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.GwtEvent; 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.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; diff --git a/client/src/com/vaadin/client/ui/VAccordion.java b/client/src/com/vaadin/client/ui/VAccordion.java index f87186fe37..d348e6863b 100644 --- a/client/src/com/vaadin/client/ui/VAccordion.java +++ b/client/src/com/vaadin/client/ui/VAccordion.java @@ -15,56 +15,53 @@ */ package com.vaadin.client.ui; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +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.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; -import com.vaadin.client.UIDL; import com.vaadin.client.Util; import com.vaadin.client.VCaption; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; -import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants; -import com.vaadin.shared.ui.tabsheet.TabsheetConstants; +import com.vaadin.shared.ComponentConstants; +import com.vaadin.shared.ui.accordion.AccordionState; +import com.vaadin.shared.ui.tabsheet.TabState; +import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; public class VAccordion extends VTabsheetBase { - public static final String CLASSNAME = "v-accordion"; + public static final String CLASSNAME = AccordionState.PRIMARY_STYLE_NAME; private Set<Widget> widgets = new HashSet<Widget>(); - /** For internal use only. May be removed or replaced in the future. */ - public HashMap<StackItem, UIDL> lazyUpdateMap = new HashMap<StackItem, UIDL>(); - - private StackItem openTab = null; + private StackItem openTab; /** For internal use only. May be removed or replaced in the future. */ - public int selectedUIDLItemIndex = -1; + public int selectedItemIndex = -1; private final TouchScrollHandler touchScrollHandler; public VAccordion() { super(CLASSNAME); + touchScrollHandler = TouchScrollDelegate.enableTouchScrolling(this); } @Override - public void renderTab(UIDL tabUidl, int index, boolean selected, - boolean hidden) { + public void renderTab(TabState tabState, int index) { StackItem item; int itemIndex; + if (getWidgetCount() <= index) { // Create stackItem and render caption - item = new StackItem(tabUidl); + item = new StackItem(); if (getWidgetCount() == 0) { item.addStyleDependentName("first"); } @@ -72,23 +69,19 @@ public class VAccordion extends VTabsheetBase { add(item, getElement()); } else { item = getStackItem(index); - item = moveStackItemIfNeeded(item, index, tabUidl); + itemIndex = index; } - item.updateCaption(tabUidl); + item.updateCaption(tabState); - item.updateTabStyleName(tabUidl - .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME)); + item.updateTabStyleName(tabState.styleName); - item.setVisible(!hidden); - - if (selected) { - selectedUIDLItemIndex = itemIndex; - } + item.setVisible(tabState.visible); + } - if (tabUidl.getChildCount() > 0) { - lazyUpdateMap.put(item, tabUidl.getChildUIDL(0)); - } + @Override + public void selectTab(int index) { + selectedItemIndex = index; } @Override @@ -112,69 +105,6 @@ public class VAccordion extends VTabsheetBase { } } - /** - * This method tries to find out if a tab has been rendered with a different - * index previously. If this is the case it re-orders the children so the - * same StackItem is used for rendering this time. E.g. if the first tab has - * been removed all tabs which contain cached content must be moved 1 step - * up to preserve the cached content. - * - * @param item - * @param newIndex - * @param tabUidl - * @return - */ - private StackItem moveStackItemIfNeeded(StackItem item, int newIndex, - UIDL tabUidl) { - UIDL tabContentUIDL = null; - ComponentConnector tabContent = null; - if (tabUidl.getChildCount() > 0) { - tabContentUIDL = tabUidl.getChildUIDL(0); - tabContent = client.getPaintable(tabContentUIDL); - } - - Widget itemWidget = item.getComponent(); - if (tabContent != null) { - if (tabContent.getWidget() != itemWidget) { - /* - * This is not the same widget as before, find out if it has - * been moved - */ - int oldIndex = -1; - StackItem oldItem = null; - for (int i = 0; i < getWidgetCount(); i++) { - Widget w = getWidget(i); - oldItem = (StackItem) w; - if (tabContent == oldItem.getComponent()) { - oldIndex = i; - break; - } - } - - if (oldIndex != -1 && oldIndex > newIndex) { - /* - * The tab has previously been rendered in another position - * so we must move the cached content to correct position. - * We move only items with oldIndex > newIndex to prevent - * moving items already rendered in this update. If for - * instance tabs 1,2,3 are removed and added as 3,2,1 we - * cannot re-use "1" when we get to the third tab. - */ - insert(oldItem, getElement(), newIndex, true); - return oldItem; - } - } - } else { - // Tab which has never been loaded. Must assure we use an empty - // StackItem - Widget oldWidget = item.getComponent(); - if (oldWidget != null) { - oldWidget.removeFromParent(); - } - } - return item; - } - /** For internal use only. May be removed or replaced in the future. */ public void open(int itemIndex) { StackItem item = (StackItem) getWidget(itemIndex); @@ -210,10 +140,14 @@ public class VAccordion extends VTabsheetBase { public void onSelectTab(StackItem item) { final int index = getWidgetIndex(item); + if (index != activeTabIndex && !disabled && !readonly && !disabledTabKeys.contains(tabKeys.get(index))) { + addStyleDependentName("loading"); - client.updateVariable(id, "selected", "" + tabKeys.get(index), true); + + connector.getRpcProxy(TabsheetServerRpc.class).setSelected( + tabKeys.get(index).toString()); } } @@ -296,7 +230,7 @@ public class VAccordion extends VTabsheetBase { private Element captionNode = DOM.createDiv(); private String styleName; - public StackItem(UIDL tabUidl) { + public StackItem() { setElement(DOM.createDiv()); caption = new VCaption(client); caption.addClickHandler(this); @@ -328,8 +262,8 @@ public class VAccordion extends VTabsheetBase { onSelectTab(this); } - public Element getContainerElement() { - return content; + public com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(content); } public Widget getChildWidget() { @@ -343,6 +277,7 @@ public class VAccordion extends VTabsheetBase { public void replaceWidget(Widget newWidget) { if (getWidgetCount() > 1) { Widget oldWidget = getWidget(1); + remove(oldWidget); widgets.remove(oldWidget); } add(newWidget, content); @@ -375,10 +310,17 @@ public class VAccordion extends VTabsheetBase { return open; } - public void setContent(UIDL contentUidl) { - final ComponentConnector newPntbl = client - .getPaintable(contentUidl); - Widget newWidget = newPntbl.getWidget(); + /** + * Updates the content of the open tab of the accordion. + * + * This method is mostly for internal use and may change in future + * versions. + * + * @since 7.2 + * @param newWidget + * new content + */ + public void setContent(Widget newWidget) { if (getChildWidget() == null) { add(newWidget, content); widgets.add(newWidget); @@ -395,14 +337,19 @@ public class VAccordion extends VTabsheetBase { onSelectTab(this); } - public void updateCaption(UIDL uidl) { + public void updateCaption(TabState tabState) { // TODO need to call this because the caption does not have an owner caption.updateCaptionWithoutOwner( - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE), - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON)); + tabState.caption, + !tabState.enabled, + hasAttribute(tabState.description), + hasAttribute(tabState.componentError), + connector.getResourceUrl(ComponentConstants.ICON_RESOURCE + + tabState.key)); + } + + private boolean hasAttribute(String string) { + return string != null && !string.trim().isEmpty(); } /** @@ -450,20 +397,7 @@ public class VAccordion extends VTabsheetBase { clear(); } - boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedHeight(); - } - @Override - @SuppressWarnings("unchecked") public Iterator<Widget> getWidgetIterator() { return widgets.iterator(); } @@ -489,7 +423,7 @@ public class VAccordion extends VTabsheetBase { } Widget w = stackItem.getChildWidget(); if (w != null) { - return ConnectorMap.get(client).getConnector(w); + return getConnectorForWidget(w); } } diff --git a/client/src/com/vaadin/client/ui/VButton.java b/client/src/com/vaadin/client/ui/VButton.java index 840ed39114..98df258f57 100644 --- a/client/src/com/vaadin/client/ui/VButton.java +++ b/client/src/com/vaadin/client/ui/VButton.java @@ -225,10 +225,16 @@ public class VButton extends FocusWidget implements ClickHandler { DOM.eventPreventDefault(event); } break; + case Event.ONMOUSEOVER: + if (isCapturing && isTargetInsideButton(event)) { + // This means a mousedown happened on the button and a mouseup + // has not happened yet + setHovering(true); + addStyleName(CLASSNAME_PRESSED); + } + break; case Event.ONMOUSEOUT: - Element to = event.getRelatedTarget(); - if (getElement().isOrHasChild(DOM.eventGetTarget(event)) - && (to == null || !getElement().isOrHasChild(to))) { + if (isTargetInsideButton(event)) { if (clickPending && Math.abs(mousedownX - event.getClientX()) < MOVE_THRESHOLD && Math.abs(mousedownY - event.getClientY()) < MOVE_THRESHOLD) { @@ -236,8 +242,6 @@ public class VButton extends FocusWidget implements ClickHandler { break; } clickPending = false; - if (isCapturing) { - } setHovering(false); removeStyleName(CLASSNAME_PRESSED); } @@ -290,6 +294,15 @@ public class VButton extends FocusWidget implements ClickHandler { } } + /** + * Check if the event occurred over an element which is part of this button + */ + private boolean isTargetInsideButton(Event event) { + Element to = event.getRelatedTarget(); + return getElement().isOrHasChild(DOM.eventGetTarget(event)) + && (to == null || !getElement().isOrHasChild(to)); + } + final void setHovering(boolean hovering) { if (hovering != isHovering()) { isHovering = hovering; diff --git a/client/src/com/vaadin/client/ui/VCalendar.java b/client/src/com/vaadin/client/ui/VCalendar.java index 38bcc0b14f..965d2a148e 100644 --- a/client/src/com/vaadin/client/ui/VCalendar.java +++ b/client/src/com/vaadin/client/ui/VCalendar.java @@ -22,10 +22,10 @@ import java.util.Comparator; import java.util.Date; import java.util.List; +import com.google.gwt.dom.client.Element; 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; diff --git a/client/src/com/vaadin/client/ui/VCalendarPanel.java b/client/src/com/vaadin/client/ui/VCalendarPanel.java index c6832226a4..74462e501d 100644 --- a/client/src/com/vaadin/client/ui/VCalendarPanel.java +++ b/client/src/com/vaadin/client/ui/VCalendarPanel.java @@ -21,6 +21,7 @@ import java.util.Iterator; import com.google.gwt.aria.client.Roles; import com.google.gwt.aria.client.SelectedValue; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; @@ -43,7 +44,6 @@ 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; import com.google.gwt.user.client.ui.FlexTable; @@ -170,8 +170,6 @@ public class VCalendarPanel extends FocusableFlexTable implements private Resolution resolution = Resolution.YEAR; - private int focusedRow; - private Timer mouseTimer; private Date value; @@ -256,7 +254,6 @@ public class VCalendarPanel extends FocusableFlexTable implements if (curday.getDate().equals(date)) { curday.addStyleDependentName(CN_FOCUSED); focusedDay = curday; - focusedRow = i; return; } } @@ -741,7 +738,6 @@ public class VCalendarPanel extends FocusableFlexTable implements } if (curr.equals(focusedDate)) { focusedDay = day; - focusedRow = weekOfMonth; if (hasFocus) { day.addStyleDependentName(CN_FOCUSED); } @@ -1800,10 +1796,8 @@ public class VCalendarPanel extends FocusableFlexTable implements * Updates the valus to correspond to the values in value */ public void updateTimes() { - boolean selected = true; if (value == null) { value = new Date(); - selected = false; } if (getDateTimeService().isTwelveHourClock()) { int h = value.getHours(); @@ -1838,10 +1832,6 @@ public class VCalendarPanel extends FocusableFlexTable implements } - private int getMilliseconds() { - return DateTimeService.getMilliseconds(value); - } - private DateTimeService getDateTimeService() { if (dateTimeService == null) { dateTimeService = new DateTimeService(); @@ -2039,7 +2029,6 @@ public class VCalendarPanel extends FocusableFlexTable implements private static final String SUBPART_HOUR_SELECT = "h"; private static final String SUBPART_MINUTE_SELECT = "m"; private static final String SUBPART_SECS_SELECT = "s"; - private static final String SUBPART_MSECS_SELECT = "ms"; private static final String SUBPART_AMPM_SELECT = "ampm"; private static final String SUBPART_DAY = "day"; private static final String SUBPART_MONTH_YEAR_HEADER = "header"; @@ -2049,7 +2038,7 @@ public class VCalendarPanel extends FocusableFlexTable implements private Date rangeEnd; @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (contains(nextMonth, subElement)) { return SUBPART_NEXT_MONTH; } else if (contains(prevMonth, subElement)) { @@ -2108,7 +2097,7 @@ public class VCalendarPanel extends FocusableFlexTable implements } @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if (SUBPART_NEXT_MONTH.equals(subPart)) { return nextMonth.getElement(); } @@ -2153,7 +2142,8 @@ public class VCalendarPanel extends FocusableFlexTable implements } if (SUBPART_MONTH_YEAR_HEADER.equals(subPart)) { - return (Element) getCellFormatter().getElement(0, 2).getChild(0); + return DOM.asOld((Element) getCellFormatter().getElement(0, 2) + .getChild(0)); } return null; } diff --git a/client/src/com/vaadin/client/ui/VCheckBox.java b/client/src/com/vaadin/client/ui/VCheckBox.java index 94b37f418e..59058caf81 100644 --- a/client/src/com/vaadin/client/ui/VCheckBox.java +++ b/client/src/com/vaadin/client/ui/VCheckBox.java @@ -16,10 +16,10 @@ package com.vaadin.client.ui; +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.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; @@ -93,7 +93,7 @@ public class VCheckBox extends com.google.gwt.user.client.ui.CheckBox implements */ private Element getCheckBoxElement() { // FIXME: Would love to use a better way to access the checkbox element - return (Element) getElement().getFirstChildElement(); + return getElement().getFirstChildElement(); } @Override diff --git a/client/src/com/vaadin/client/ui/VContextMenu.java b/client/src/com/vaadin/client/ui/VContextMenu.java index 02ee5b07c5..7aa4035f10 100644 --- a/client/src/com/vaadin/client/ui/VContextMenu.java +++ b/client/src/com/vaadin/client/ui/VContextMenu.java @@ -18,6 +18,7 @@ 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.Element; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; @@ -43,7 +44,6 @@ 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; @@ -279,7 +279,7 @@ public class VContextMenu extends VOverlay implements SubPartAware { } @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { int index = Integer.parseInt(subPart.substring(6)); // ApplicationConnection.getConsole().log( // "Searching element for selection index " + index); @@ -290,7 +290,7 @@ public class VContextMenu extends VOverlay implements SubPartAware { } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (getElement().isOrHasChild(subElement)) { com.google.gwt.dom.client.Element e = subElement; { diff --git a/client/src/com/vaadin/client/ui/VCustomLayout.java b/client/src/com/vaadin/client/ui/VCustomLayout.java index 7a33f17314..7f5e4d4028 100644 --- a/client/src/com/vaadin/client/ui/VCustomLayout.java +++ b/client/src/com/vaadin/client/ui/VCustomLayout.java @@ -19,6 +19,7 @@ package com.vaadin.client.ui; import java.util.HashMap; import java.util.Iterator; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.Style; @@ -26,7 +27,6 @@ import com.google.gwt.dom.client.Style.BorderStyle; 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.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; @@ -242,12 +242,10 @@ public class VCustomLayout extends ComplexPanel { * parent about possible size change. */ private void initImgElements() { - NodeList<com.google.gwt.dom.client.Element> nodeList = getElement() - .getElementsByTagName("IMG"); + NodeList<Element> nodeList = getElement().getElementsByTagName("IMG"); for (int i = 0; i < nodeList.getLength(); i++) { - com.google.gwt.dom.client.ImageElement img = (ImageElement) nodeList - .getItem(i); - DOM.sinkEvents((Element) img.cast(), Event.ONLOAD); + ImageElement img = ImageElement.as(nodeList.getItem(i)); + DOM.sinkEvents(img, Event.ONLOAD); } } @@ -419,7 +417,7 @@ public class VCustomLayout extends ComplexPanel { * @return true if layout function exists and was run successfully, else * false. */ - public native boolean iLayoutJS(Element el) + public native boolean iLayoutJS(com.google.gwt.user.client.Element el) /*-{ if(el && el.iLayoutJS) { try { diff --git a/client/src/com/vaadin/client/ui/VDateField.java b/client/src/com/vaadin/client/ui/VDateField.java index 5b49711df1..cd9052dc73 100644 --- a/client/src/com/vaadin/client/ui/VDateField.java +++ b/client/src/com/vaadin/client/ui/VDateField.java @@ -19,11 +19,12 @@ package com.vaadin.client.ui; import java.util.Date; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HasEnabled; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.DateTimeService; import com.vaadin.shared.ui.datefield.Resolution; -public class VDateField extends FlowPanel implements Field { +public class VDateField extends FlowPanel implements Field, HasEnabled { public static final String CLASSNAME = "v-datefield"; @@ -161,10 +162,12 @@ public class VDateField extends FlowPanel implements Field { this.readonly = readonly; } + @Override public boolean isEnabled() { return enabled; } + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; } diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java index ccd7e2758e..237d2b55c2 100644 --- a/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java +++ b/client/src/com/vaadin/client/ui/VDragAndDropWrapper.java @@ -22,13 +22,14 @@ import java.util.Map; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.TouchStartEvent; import com.google.gwt.event.dom.client.TouchStartHandler; import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Widget; @@ -81,6 +82,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements @Override public void onMouseDown(MouseDownEvent event) { if (getConnector().isEnabled() + && event.getNativeEvent().getButton() == Event.BUTTON_LEFT && startDrag(event.getNativeEvent())) { event.preventDefault(); // prevent text selection } @@ -117,7 +119,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements transferable.setDragSource(getConnector()); ComponentConnector paintable = Util.findPaintable(client, - (Element) event.getEventTarget().cast()); + Element.as(event.getEventTarget())); Widget widget = paintable.getWidget(); transferable.setData("component", paintable); VDragEvent dragEvent = VDragAndDropManager.get().startDrag( @@ -182,7 +184,7 @@ public class VDragAndDropWrapper extends VCustomComponent implements } } - protected Element getDragStartElement() { + protected com.google.gwt.user.client.Element getDragStartElement() { return getElement(); } @@ -548,7 +550,13 @@ public class VDragAndDropWrapper extends VCustomComponent implements return ConnectorMap.get(client).getConnector(this); } - protected native void hookHtml5DragStart(Element el) + /** + * @deprecated As of 7.2, call or override + * {@link #hookHtml5DragStart(Element)} instead + */ + @Deprecated + protected native void hookHtml5DragStart( + com.google.gwt.user.client.Element el) /*-{ var me = this; el.addEventListener("dragstart", $entry(function(ev) { @@ -557,11 +565,21 @@ public class VDragAndDropWrapper extends VCustomComponent implements }-*/; /** + * @since 7.2 + */ + protected void hookHtml5DragStart(Element el) { + hookHtml5DragStart(DOM.asOld(el)); + } + + /** * Prototype code, memory leak risk. * * @param el + * @deprecated As of 7.2, call or override {@link #hookHtml5Events(Element)} + * instead */ - protected native void hookHtml5Events(Element el) + @Deprecated + protected native void hookHtml5Events(com.google.gwt.user.client.Element el) /*-{ var me = this; @@ -582,6 +600,17 @@ public class VDragAndDropWrapper extends VCustomComponent implements }), false); }-*/; + /** + * Prototype code, memory leak risk. + * + * @param el + * + * @since 7.2 + */ + protected void hookHtml5Events(Element el) { + hookHtml5Events(DOM.asOld(el)); + } + public boolean updateDropDetails(VDragEvent drag) { VerticalDropLocation oldVL = verticalDropLocation; verticalDropLocation = DDUtil.getVerticalDropLocation(getElement(), diff --git a/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java b/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java index d66856e857..d434a4adb1 100644 --- a/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java +++ b/client/src/com/vaadin/client/ui/VDragAndDropWrapperIE.java @@ -18,14 +18,15 @@ package com.vaadin.client.ui; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.Document; -import com.google.gwt.user.client.Element; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; import com.vaadin.client.VConsole; public class VDragAndDropWrapperIE extends VDragAndDropWrapper { private AnchorElement anchor = null; @Override - protected Element getDragStartElement() { + protected com.google.gwt.user.client.Element getDragStartElement() { VConsole.log("IE get drag start element..."); Element div = getElement(); if (dragStartMode == HTML5) { @@ -36,18 +37,20 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { div.appendChild(anchor); } VConsole.log("IE get drag start element..."); - return (Element) anchor.cast(); + return anchor.cast(); } else { if (anchor != null) { div.removeChild(anchor); anchor = null; } - return div; + return DOM.asOld(div); } } + @Deprecated @Override - protected native void hookHtml5DragStart(Element el) + protected native void hookHtml5DragStart( + com.google.gwt.user.client.Element el) /*-{ var me = this; @@ -57,7 +60,13 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { }-*/; @Override - protected native void hookHtml5Events(Element el) + protected void hookHtml5DragStart(Element el) { + hookHtml5DragStart(DOM.asOld(el)); + } + + @Deprecated + @Override + protected native void hookHtml5Events(com.google.gwt.user.client.Element el) /*-{ var me = this; @@ -78,4 +87,9 @@ public class VDragAndDropWrapperIE extends VDragAndDropWrapper { })); }-*/; + @Override + protected void hookHtml5Events(Element el) { + hookHtml5Events(DOM.asOld(el)); + } + } diff --git a/client/src/com/vaadin/client/ui/VEmbedded.java b/client/src/com/vaadin/client/ui/VEmbedded.java index 0dd81efe82..d38d74f394 100644 --- a/client/src/com/vaadin/client/ui/VEmbedded.java +++ b/client/src/com/vaadin/client/ui/VEmbedded.java @@ -20,8 +20,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import com.google.gwt.dom.client.Element; 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.ui.HTML; import com.vaadin.client.ApplicationConnection; diff --git a/client/src/com/vaadin/client/ui/VFilterSelect.java b/client/src/com/vaadin/client/ui/VFilterSelect.java index 0bc4e0d75b..e0ced98394 100644 --- a/client/src/com/vaadin/client/ui/VFilterSelect.java +++ b/client/src/com/vaadin/client/ui/VFilterSelect.java @@ -27,6 +27,7 @@ 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.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Unit; @@ -47,18 +48,17 @@ import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; 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.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; -import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; import com.google.gwt.user.client.ui.SuggestOracle.Suggestion; import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; @@ -122,10 +122,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, @Override public String getDisplayString() { final StringBuffer sb = new StringBuffer(); - if (iconUri != null) { - sb.append("<img src=\""); - sb.append(Util.escapeAttribute(iconUri)); - sb.append("\" alt=\"\" class=\"v-icon\" />"); + final Icon icon = client.getIcon(iconUri); + if (icon != null) { + sb.append(icon.getElement().getString()); } String content; if ("".equals(caption)) { @@ -556,8 +555,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, offsetHeight = getOffsetHeight(); final int desiredWidth = getMainWidth(); - Element menuFirstChild = menu.getElement().getFirstChildElement() - .cast(); + Element menuFirstChild = menu.getElement().getFirstChildElement(); int naturalMenuWidth = menuFirstChild.getOffsetWidth(); if (popupOuterPadding == -1) { @@ -853,7 +851,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, private static final String SUBPART_PREFIX = "item"; @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement( + String subPart) { int index = Integer.parseInt(subPart.substring(SUBPART_PREFIX .length())); @@ -863,7 +862,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName( + com.google.gwt.user.client.Element subElement) { if (!getElement().isOrHasChild(subElement)) { return null; } @@ -989,7 +989,13 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } }; - private final Image selectedItemIcon = new Image(); + private class IconWidget extends Widget { + IconWidget(Icon icon) { + setElement(icon.getElement()); + } + } + + private IconWidget selectedItemIcon; /** For internal use only. May be removed or replaced in the future. */ public ApplicationConnection client; @@ -1031,7 +1037,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, /** For internal use only. May be removed or replaced in the future. */ public enum Select { NONE, FIRST, LAST - }; + } /** For internal use only. May be removed or replaced in the future. */ public Select selectPopupItemWhenResponseIsReceived = Select.NONE; @@ -1112,21 +1118,6 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, tb = createTextBox(); suggestionPopup = createSuggestionPopup(); - selectedItemIcon.setStyleName("v-icon"); - selectedItemIcon.addLoadHandler(new LoadHandler() { - - @Override - public void onLoad(LoadEvent event) { - if (BrowserInfo.get().isIE8()) { - // IE8 needs some help to discover it should reposition the - // text field - forceReflow(); - } - updateRootWidth(); - updateSelectedIconPosition(); - } - }); - popupOpener.sinkEvents(Event.ONMOUSEDOWN); Roles.getButtonRole() .setAriaHiddenState(popupOpener.getElement(), true); @@ -1412,20 +1403,36 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * The URI of the icon */ public void setSelectedItemIcon(String iconUri) { + if (iconUri == null || iconUri.length() == 0) { - if (selectedItemIcon.isAttached()) { + if (selectedItemIcon != null) { panel.remove(selectedItemIcon); - if (BrowserInfo.get().isIE8()) { - // IE8 needs some help to discover it should reposition the - // text field - forceReflow(); - } - updateRootWidth(); + selectedItemIcon = null; + afterSelectedItemIconChange(); } } else { + if (selectedItemIcon != null) { + panel.remove(selectedItemIcon); + } + selectedItemIcon = new IconWidget(client.getIcon(iconUri)); + selectedItemIcon.addDomHandler(new LoadHandler() { + @Override + public void onLoad(LoadEvent event) { + afterSelectedItemIconChange(); + } + }, LoadEvent.getType()); panel.insert(selectedItemIcon, 0); - selectedItemIcon.setUrl(iconUri); - updateRootWidth(); + afterSelectedItemIconChange(); + } + } + + private void afterSelectedItemIconChange() { + if (BrowserInfo.get().isWebkit() || BrowserInfo.get().isIE8()) { + // Some browsers need a nudge to reposition the text field + forceReflow(); + } + updateRootWidth(); + if (selectedItemIcon != null) { updateSelectedIconPosition(); } } @@ -1662,7 +1669,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, case KeyCodes.KEY_PAGEDOWN: case KeyCodes.KEY_PAGEUP: case KeyCodes.KEY_ESCAPE: - ; // NOP + // NOP break; default: if (textInputEnabled) { @@ -1812,6 +1819,11 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, if (client.hasEventListeners(this, EventId.FOCUS)) { client.updateVariable(paintableId, EventId.FOCUS, "", true); } + + ComponentConnector connector = ConnectorMap.get(client).getConnector( + this); + client.getVTooltip().showAssistive( + connector.getTooltipInfo(getElement())); } /** @@ -1945,8 +1957,9 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, * locked */ if (!tb.getElement().getStyle().getWidth().endsWith("px")) { - tb.setWidth((tb.getOffsetWidth() - selectedItemIcon - .getOffsetWidth()) + "px"); + int iconWidth = selectedItemIcon == null ? 0 : selectedItemIcon + .getOffsetWidth(); + tb.setWidth((tb.getOffsetWidth() - iconWidth) + "px"); } } } @@ -2004,7 +2017,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if ("textbox".equals(subPart)) { return tb.getElement(); } else if ("button".equals(subPart)) { @@ -2016,7 +2029,7 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (tb.getElement().isOrHasChild(subElement)) { return "textbox"; } else if (popupOpener.getElement().isOrHasChild(subElement)) { @@ -2038,7 +2051,8 @@ public class VFilterSelect extends Composite implements Field, KeyDownHandler, } @Override - public void bindAriaCaption(Element captionElement) { + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { AriaHelper.bindCaption(tb, captionElement); } } diff --git a/client/src/com/vaadin/client/ui/VForm.java b/client/src/com/vaadin/client/ui/VForm.java index f88bc8d1c0..94379a5611 100644 --- a/client/src/com/vaadin/client/ui/VForm.java +++ b/client/src/com/vaadin/client/ui/VForm.java @@ -16,11 +16,11 @@ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; 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.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.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; @@ -136,10 +136,4 @@ public class VForm extends ComplexPanel implements KeyDownHandler { } lo = newLayoutWidget; } - - /** For internal use only. May be removed or replaced in the future. */ - @Override - public void add(Widget child, Element container) { - super.add(child, container); - } } diff --git a/client/src/com/vaadin/client/ui/VFormLayout.java b/client/src/com/vaadin/client/ui/VFormLayout.java index 6c2661d1d8..9ce6f4b992 100644 --- a/client/src/com/vaadin/client/ui/VFormLayout.java +++ b/client/src/com/vaadin/client/ui/VFormLayout.java @@ -21,10 +21,10 @@ import java.util.HashMap; import java.util.List; import com.google.gwt.aria.client.Roles; +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.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.SimplePanel; @@ -260,21 +260,17 @@ public class VFormLayout extends SimplePanel { boolean isEmpty = true; + if (icon != null) { + getElement().removeChild(icon.getElement()); + icon = null; + } if (state.resources.containsKey(ComponentConstants.ICON_RESOURCE)) { - if (icon == null) { - icon = new Icon(owner.getConnection()); + icon = owner.getConnection().getIcon( + state.resources.get(ComponentConstants.ICON_RESOURCE) + .getURL()); + DOM.insertChild(getElement(), icon.getElement(), 0); - DOM.insertChild(getElement(), icon.getElement(), 0); - } - icon.setUri(state.resources.get( - ComponentConstants.ICON_RESOURCE).getURL()); isEmpty = false; - } else { - if (icon != null) { - DOM.removeChild(getElement(), icon.getElement()); - icon = null; - } - } if (state.caption != null) { diff --git a/client/src/com/vaadin/client/ui/VGridLayout.java b/client/src/com/vaadin/client/ui/VGridLayout.java index 46051655b8..07dba1f9b3 100644 --- a/client/src/com/vaadin/client/ui/VGridLayout.java +++ b/client/src/com/vaadin/client/ui/VGridLayout.java @@ -22,10 +22,11 @@ import java.util.List; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; 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.Element; +import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; @@ -665,9 +666,32 @@ public class VGridLayout extends ComplexPanel { * this layout * @return The Paintable which the element is a part of. Null if the element * belongs to the layout and not to a child. + * @deprecated As of 7.2, call or override {@link #getComponent(Element)} + * instead */ - public ComponentConnector getComponent(Element element) { + @Deprecated + public ComponentConnector getComponent( + com.google.gwt.user.client.Element element) { return Util.getConnectorForElement(client, this, element); + + } + + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + * <p> + * For internal use only. May be removed or replaced in the future. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + * + * @since 7.2 + */ + public ComponentConnector getComponent(Element element) { + return getComponent(DOM.asOld(element)); } /** For internal use only. May be removed or replaced in the future. */ diff --git a/client/src/com/vaadin/client/ui/VLabel.java b/client/src/com/vaadin/client/ui/VLabel.java index 8acd653778..35f47d540a 100644 --- a/client/src/com/vaadin/client/ui/VLabel.java +++ b/client/src/com/vaadin/client/ui/VLabel.java @@ -18,7 +18,6 @@ package com.vaadin.client.ui; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.Util; import com.vaadin.client.VTooltip; @@ -28,8 +27,6 @@ public class VLabel extends HTML { public static final String CLASSNAME = "v-label"; private static final String CLASSNAME_UNDEFINED_WIDTH = "v-label-undef-w"; - private ApplicationConnection connection; - public VLabel() { super(); setStyleName(CLASSNAME); @@ -71,9 +68,4 @@ public class VLabel extends HTML { super.setText(text); } } - - /** For internal use only. May be removed or replaced in the future. */ - public void setConnection(ApplicationConnection client) { - connection = client; - } } diff --git a/client/src/com/vaadin/client/ui/VLink.java b/client/src/com/vaadin/client/ui/VLink.java index fa4ee36bcf..b528e770d4 100644 --- a/client/src/com/vaadin/client/ui/VLink.java +++ b/client/src/com/vaadin/client/ui/VLink.java @@ -16,14 +16,13 @@ package com.vaadin.client.ui; +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.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HTML; -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.Util; import com.vaadin.shared.ui.BorderStyle; @@ -70,9 +69,6 @@ public class VLink extends HTML implements ClickHandler { /** For internal use only. May be removed or replaced in the future. */ public Icon icon; - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; - public VLink() { super(); getElement().appendChild(anchor); diff --git a/client/src/com/vaadin/client/ui/VMediaBase.java b/client/src/com/vaadin/client/ui/VMediaBase.java index b77e8bb161..eadc8258c6 100644 --- a/client/src/com/vaadin/client/ui/VMediaBase.java +++ b/client/src/com/vaadin/client/ui/VMediaBase.java @@ -17,11 +17,11 @@ package com.vaadin.client.ui; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.MediaElement; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.SourceElement; import com.google.gwt.dom.client.Text; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; public abstract class VMediaBase extends Widget { @@ -82,7 +82,7 @@ public abstract class VMediaBase extends Widget { } public void addSource(String sourceUrl, String sourceType) { - Element src = Document.get().createElement(SourceElement.TAG).cast(); + Element src = Document.get().createElement(SourceElement.TAG); src.setAttribute("src", sourceUrl); src.setAttribute("type", sourceType); media.appendChild(src); diff --git a/client/src/com/vaadin/client/ui/VMenuBar.java b/client/src/com/vaadin/client/ui/VMenuBar.java index 0aa26e4999..a2715fd786 100644 --- a/client/src/com/vaadin/client/ui/VMenuBar.java +++ b/client/src/com/vaadin/client/ui/VMenuBar.java @@ -21,6 +21,7 @@ import java.util.List; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; @@ -35,7 +36,6 @@ import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; 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.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.HasHTML; @@ -226,11 +226,9 @@ public class VMenuBar extends SimpleFocusablePanel implements itemHTML.append("<span class=\"" + getStylePrimaryName() + "-menuitem-caption\">"); - if (item.hasAttribute("icon")) { - itemHTML.append("<img src=\"" - + Util.escapeAttribute(client.translateVaadinUri(item - .getStringAttribute("icon"))) + "\" class=\"" - + Icon.CLASSNAME + "\" alt=\"\" />"); + Icon icon = client.getIcon(item.getStringAttribute("icon")); + if (icon != null) { + itemHTML.append(icon.getElement().getString()); } String itemText = item.getStringAttribute("text"); if (!htmlContentAllowed) { @@ -286,8 +284,8 @@ public class VMenuBar extends SimpleFocusablePanel implements * @return */ @Override - public Element getContainerElement() { - return containerElement; + public com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(containerElement); } /** @@ -1230,13 +1228,29 @@ public class VMenuBar extends SimpleFocusablePanel implements * Get the key that selects a menu item. By default it is the Enter key but * by overriding this you can change the key to whatever you want. * + * @deprecated use {@link #isNavigationSelectKey(int)} instead * @return */ + @Deprecated protected int getNavigationSelectKey() { return KeyCodes.KEY_ENTER; } /** + * Checks whether key code selects a menu item. By default it is the Enter + * and Space keys but by overriding this you can change the keys to whatever + * you want. + * + * @since 7.2 + * @param keycode + * @return true if key selects menu item + */ + protected boolean isNavigationSelectKey(int keycode) { + return keycode == getNavigationSelectKey() + || keycode == KeyCodes.KEY_SPACE; + } + + /** * Get the key that closes the menu. By default it is the escape key but by * overriding this yoy can change the key to whatever you want. * @@ -1434,7 +1448,7 @@ public class VMenuBar extends SimpleFocusablePanel implements hideChildren(); menuVisible = false; - } else if (keycode == getNavigationSelectKey()) { + } else if (isNavigationSelectKey(keycode)) { if (getSelected() == null) { // If nothing is selected then select the first item selectFirstItem(); @@ -1502,7 +1516,7 @@ public class VMenuBar extends SimpleFocusablePanel implements private final String SUBPART_PREFIX = "item"; @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { int index = Integer .parseInt(subPart.substring(SUBPART_PREFIX.length())); CustomMenuItem item = getItems().get(index); @@ -1511,7 +1525,7 @@ public class VMenuBar extends SimpleFocusablePanel implements } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (!getElement().isOrHasChild(subElement)) { return null; } @@ -1539,8 +1553,12 @@ public class VMenuBar extends SimpleFocusablePanel implements * @param element * Element used in search * @return Menu item or null if not found + * @deprecated As of 7.2, call or override + * {@link #getMenuItemWithElement(Element)} instead */ - public CustomMenuItem getMenuItemWithElement(Element element) { + @Deprecated + public CustomMenuItem getMenuItemWithElement( + com.google.gwt.user.client.Element element) { for (int i = 0; i < items.size(); i++) { CustomMenuItem item = items.get(i); if (DOM.isOrHasChild(item.getElement(), element)) { @@ -1557,4 +1575,17 @@ public class VMenuBar extends SimpleFocusablePanel implements return null; } + + /** + * Get menu item with given DOM element + * + * @param element + * Element used in search + * @return Menu item or null if not found + * + * @since 7.2 + */ + public CustomMenuItem getMenuItemWithElement(Element element) { + return getMenuItemWithElement(DOM.asOld(element)); + } } diff --git a/client/src/com/vaadin/client/ui/VNativeButton.java b/client/src/com/vaadin/client/ui/VNativeButton.java index 67fa6f2ee3..d38e4c3374 100644 --- a/client/src/com/vaadin/client/ui/VNativeButton.java +++ b/client/src/com/vaadin/client/ui/VNativeButton.java @@ -33,8 +33,6 @@ public class VNativeButton extends Button implements ClickHandler { public static final String CLASSNAME = "v-nativebutton"; - protected String width = null; - /** For internal use only. May be removed or replaced in the future. */ public String paintableId; @@ -123,12 +121,6 @@ public class VNativeButton extends Button implements ClickHandler { } } - @Override - public void setWidth(String width) { - this.width = width; - super.setWidth(width); - } - /* * (non-Javadoc) * diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java index 7019394e3b..a43f508f6e 100644 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ b/client/src/com/vaadin/client/ui/VNotification.java @@ -21,20 +21,26 @@ import java.util.Date; import java.util.EventObject; import java.util.Iterator; +import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.KeyCodes; 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.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.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; +import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.shared.Position; import com.vaadin.shared.ui.ui.UIConstants; +import com.vaadin.shared.ui.ui.UIState.NotificationTypeConfiguration; +import com.vaadin.shared.ui.ui.NotificationRole; public class VNotification extends VOverlay { @@ -46,6 +52,12 @@ public class VNotification extends VOverlay { public static final Position BOTTOM_LEFT = Position.BOTTOM_LEFT; public static final Position BOTTOM_RIGHT = Position.BOTTOM_RIGHT; + /** + * Position that is only accessible for assistive devices, invisible for + * visual users. + */ + public static final Position ASSISTIVE = Position.ASSISTIVE; + public static final int DELAY_FOREVER = -1; public static final int DELAY_NONE = 0; @@ -149,15 +161,74 @@ public class VNotification extends VOverlay { } public void show(Widget widget, Position position, String style) { - setWidget(widget); + NotificationTypeConfiguration styleSetup = getUiState(style); + setWaiAriaRole(styleSetup); + + FlowPanel panel = new FlowPanel(); + if (hasPrefix(styleSetup)) { + panel.add(new Label(styleSetup.prefix)); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + + panel.add(widget); + + if (hasPostfix(styleSetup)) { + panel.add(new Label(styleSetup.postfix)); + AriaHelper.setVisibleForAssistiveDevicesOnly(panel.getElement(), + true); + } + setWidget(panel); show(position, style); } + private boolean hasPostfix(NotificationTypeConfiguration styleSetup) { + return styleSetup != null && styleSetup.postfix != null && !styleSetup.postfix.isEmpty(); + } + + private boolean hasPrefix(NotificationTypeConfiguration styleSetup) { + return styleSetup != null && styleSetup.prefix != null && !styleSetup.prefix.isEmpty(); + } + public void show(String html, Position position, String style) { - setWidget(new HTML(html)); + NotificationTypeConfiguration styleSetup = getUiState(style); + String assistiveDeviceOnlyStyle = AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE; + + setWaiAriaRole(styleSetup); + + String type = ""; + String usage = ""; + + if (hasPrefix(styleSetup)) { + type = "<span class='" + assistiveDeviceOnlyStyle + "'>" + + styleSetup.prefix + "</span>"; + } + + if (hasPostfix(styleSetup)) { + usage = "<span class='" + assistiveDeviceOnlyStyle + "'>" + + styleSetup.postfix + "</span>"; + } + + setWidget(new HTML(type + html + usage)); show(position, style); } + private NotificationTypeConfiguration getUiState(String style) { + return getApplicationConnection() + .getUIConnector().getState().notificationConfigurations + .get(style); + } + + private void setWaiAriaRole(NotificationTypeConfiguration styleSetup) { + Roles.getAlertRole().set(getElement()); + + if (styleSetup != null && styleSetup.notificationRole != null) { + if (NotificationRole.STATUS == styleSetup.notificationRole) { + Roles.getStatusRole().set(getElement()); + } + } + } + public void show(Position position, String style) { setOpacity(getElement(), startOpacity); if (style != null) { @@ -239,6 +310,15 @@ public class VNotification extends VOverlay { DOM.setStyleAttribute(el, "top", "0px"); DOM.setStyleAttribute(el, "right", "0px"); break; + case MIDDLE_LEFT: + center(); + DOM.setStyleAttribute(el, "left", "0px"); + break; + case MIDDLE_RIGHT: + center(); + DOM.setStyleAttribute(el, "left", ""); + DOM.setStyleAttribute(el, "right", "0px"); + break; case BOTTOM_RIGHT: DOM.setStyleAttribute(el, "position", "absolute"); DOM.setStyleAttribute(el, "bottom", "0px"); @@ -257,6 +337,10 @@ public class VNotification extends VOverlay { DOM.setStyleAttribute(el, "top", ""); DOM.setStyleAttribute(el, "bottom", "0px"); break; + case ASSISTIVE: + DOM.setStyleAttribute(el, "top", "-2000px"); + DOM.setStyleAttribute(el, "left", "-2000px"); + break; default: case MIDDLE_CENTER: center(); @@ -374,10 +458,9 @@ public class VNotification extends VOverlay { .hasAttribute(UIConstants.NOTIFICATION_HTML_CONTENT_NOT_ALLOWED); String html = ""; if (notification.hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)) { - final String parsedUri = client - .translateVaadinUri(notification - .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON)); - html += "<img src=\"" + Util.escapeAttribute(parsedUri) + "\" />"; + String iconUri = notification + .getStringAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_ICON); + html += client.getIcon(iconUri).getElement().getString(); } if (notification .hasAttribute(UIConstants.ATTRIBUTE_NOTIFICATION_CAPTION)) { @@ -417,6 +500,8 @@ public class VNotification extends VOverlay { public static VNotification createNotification(int delayMsec, Widget owner) { final VNotification notification = GWT.create(VNotification.class); + notification.setWaiAriaRole(null); + notification.delayMsec = delayMsec; if (BrowserInfo.get().isTouchDevice()) { new Timer() { diff --git a/client/src/com/vaadin/client/ui/VOptionGroup.java b/client/src/com/vaadin/client/ui/VOptionGroup.java index fe4ef214cb..3e4b357be3 100644 --- a/client/src/com/vaadin/client/ui/VOptionGroup.java +++ b/client/src/com/vaadin/client/ui/VOptionGroup.java @@ -139,12 +139,10 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, itemHtml = Util.escapeHTML(itemHtml); } - String icon = opUidl.getStringAttribute("icon"); - if (icon != null && icon.length() != 0) { - String iconUrl = client.translateVaadinUri(icon); - itemHtml = "<img src=\"" + Util.escapeAttribute(iconUrl) - + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />" - + itemHtml; + String iconUrl = opUidl.getStringAttribute("icon"); + if (iconUrl != null && iconUrl.length() != 0) { + Icon icon = client.getIcon(iconUrl); + itemHtml = icon.getElement().getString() + itemHtml; } String key = opUidl.getStringAttribute("key"); @@ -161,7 +159,7 @@ public class VOptionGroup extends VOptionGroupBase implements FocusHandler, op = new RadioButton(paintableId); op.setStyleName("v-radiobutton"); } - if (icon != null && icon.length() != 0) { + if (iconUrl != null && iconUrl.length() != 0) { Util.sinkOnloadForImages(op.getElement()); op.addHandler(iconLoadHandler, LoadEvent.getType()); } diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java index e2c9001fed..7661fbfcf7 100644 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ b/client/src/com/vaadin/client/ui/VOverlay.java @@ -19,6 +19,7 @@ package com.vaadin.client.ui; import com.google.gwt.animation.client.Animation; 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.IFrameElement; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.BorderStyle; @@ -27,7 +28,6 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.RootPanel; @@ -172,7 +172,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * * See default theme 'shadow.css' for implementation example. */ - private static final String SHADOW_HTML = "<div class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>"; + private static final String SHADOW_HTML = "<div aria-hidden=\"true\" class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>"; /** * Matches {@link PopupPanel}.ANIMATION_DURATION @@ -482,7 +482,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { // Animate the size positionAndSize.setAnimationFromCenterProgress(progress); - Element container = getElement().getParentElement().cast(); + Element container = getElement().getParentElement(); if (isShadowEnabled()) { updateShadowPosition(progress, zIndex, positionAndSize); @@ -535,8 +535,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { } private void updateShimPosition(PositionAndSize positionAndSize) { - updatePositionAndSize((Element) Element.as(getShimElement()), - positionAndSize); + updatePositionAndSize(getShimElement(), positionAndSize); } /** @@ -666,7 +665,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * {@link ApplicationConnection} or another element if the current * {@link ApplicationConnection} cannot be determined. */ - public Element getOverlayContainer() { + public com.google.gwt.user.client.Element getOverlayContainer() { ApplicationConnection ac = getApplicationConnection(); if (ac == null) { // could not figure out which one we belong to, styling will @@ -688,7 +687,8 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * A reference to {@link ApplicationConnection} * @return The overlay container */ - public static Element getOverlayContainer(ApplicationConnection ac) { + public static com.google.gwt.user.client.Element getOverlayContainer( + ApplicationConnection ac) { String id = ac.getConfiguration().getRootPanelId(); id = id += "-overlays"; Element container = DOM.getElementById(id); @@ -701,7 +701,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { container.addClassName(CLASSNAME_CONTAINER); RootPanel.get().getElement().appendChild(container); } - return container; + return DOM.asOld(container); } /** diff --git a/client/src/com/vaadin/client/ui/VPanel.java b/client/src/com/vaadin/client/ui/VPanel.java index 6b02f079d1..3b263d6dbb 100644 --- a/client/src/com/vaadin/client/ui/VPanel.java +++ b/client/src/com/vaadin/client/ui/VPanel.java @@ -18,8 +18,8 @@ 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.Element; 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.ui.SimplePanel; import com.vaadin.client.ApplicationConnection; @@ -124,8 +124,8 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, } @Override - protected Element getContainerElement() { - return contentNode; + protected com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(contentNode); } /** For internal use only. May be removed or replaced in the future. */ @@ -152,17 +152,12 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, /** For internal use only. May be removed or replaced in the future. */ public void setIconUri(String iconUri, ApplicationConnection client) { - if (iconUri == null) { - if (icon != null) { - DOM.removeChild(captionNode, icon.getElement()); - icon = null; - } - } else { - if (icon == null) { - icon = new Icon(client); - DOM.insertChild(captionNode, icon.getElement(), 0); - } - icon.setUri(iconUri); + if (icon != null) { + captionNode.removeChild(icon.getElement()); + } + icon = client.getIcon(iconUri); + if (icon != null) { + DOM.insertChild(captionNode, icon.getElement(), 0); } } @@ -170,7 +165,6 @@ public class VPanel extends SimplePanel implements ShortcutActionHandlerOwner, public void onBrowserEvent(Event event) { super.onBrowserEvent(event); - final Element target = DOM.eventGetTarget(event); final int type = DOM.eventGetType(event); if (type == Event.ONKEYDOWN && shortcutHandler != null) { shortcutHandler.handleKeyboardEvent(event); diff --git a/client/src/com/vaadin/client/ui/VPopupCalendar.java b/client/src/com/vaadin/client/ui/VPopupCalendar.java index e2ad40c929..e180239fc1 100644 --- a/client/src/com/vaadin/client/ui/VPopupCalendar.java +++ b/client/src/com/vaadin/client/ui/VPopupCalendar.java @@ -22,6 +22,7 @@ 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.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.DomEvent; @@ -30,7 +31,6 @@ 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; @@ -255,7 +255,8 @@ public class VPopupCalendar extends VTextualDate implements Field, } @Override - public void bindAriaCaption(Element captionElement) { + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { if (captionElement == null) { captionId = null; } else { @@ -547,7 +548,7 @@ public class VPopupCalendar extends VTextualDate implements Field, private final String CALENDAR_TOGGLE_ID = "popupButton"; @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if (subPart.equals(CALENDAR_TOGGLE_ID)) { return calendarToggle.getElement(); } @@ -556,7 +557,7 @@ public class VPopupCalendar extends VTextualDate implements Field, } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (calendarToggle.getElement().isOrHasChild(subElement)) { return CALENDAR_TOGGLE_ID; } diff --git a/client/src/com/vaadin/client/ui/VPopupView.java b/client/src/com/vaadin/client/ui/VPopupView.java index dba4c8b092..384cb006ce 100644 --- a/client/src/com/vaadin/client/ui/VPopupView.java +++ b/client/src/com/vaadin/client/ui/VPopupView.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; +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.event.dom.client.KeyCodes; @@ -29,7 +30,6 @@ 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.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.Focusable; import com.google.gwt.user.client.ui.HTML; @@ -102,7 +102,8 @@ public class VPopupView extends HTML implements Iterable<Widget> { } }); - popup.setAnimationEnabled(true); + // TODO: Enable animations once GWT fix has been merged + popup.setAnimationEnabled(false); popup.setAutoHideOnHistoryEventsEnabled(false); } @@ -356,7 +357,7 @@ public class VPopupView extends HTML implements Iterable<Widget> { } @Override - public Element getContainerElement() { + public com.google.gwt.user.client.Element getContainerElement() { return super.getContainerElement(); } diff --git a/client/src/com/vaadin/client/ui/VProgressBar.java b/client/src/com/vaadin/client/ui/VProgressBar.java index 8cfc28005c..3efbbbd8a6 100644 --- a/client/src/com/vaadin/client/ui/VProgressBar.java +++ b/client/src/com/vaadin/client/ui/VProgressBar.java @@ -16,9 +16,9 @@ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; 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.HasEnabled; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; diff --git a/client/src/com/vaadin/client/ui/VRichTextArea.java b/client/src/com/vaadin/client/ui/VRichTextArea.java index 0b2c1e574c..cb3cba3f1d 100644 --- a/client/src/com/vaadin/client/ui/VRichTextArea.java +++ b/client/src/com/vaadin/client/ui/VRichTextArea.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.Map.Entry; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; @@ -29,7 +30,6 @@ import com.google.gwt.event.dom.client.KeyPressHandler; 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.Timer; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java index 261c50b73b..438a201984 100644 --- a/client/src/com/vaadin/client/ui/VScrollTable.java +++ b/client/src/com/vaadin/client/ui/VScrollTable.java @@ -30,6 +30,7 @@ import com.google.gwt.core.client.JavaScriptObject; 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.dom.client.Node; import com.google.gwt.dom.client.NodeList; @@ -61,9 +62,10 @@ import com.google.gwt.event.dom.client.ScrollHandler; 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.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; 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.Event; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; @@ -71,7 +73,6 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; @@ -80,6 +81,7 @@ import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.StyleConstants; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; @@ -123,7 +125,7 @@ import com.vaadin.shared.ui.table.TableConstants; */ public class VScrollTable extends FlowPanel implements HasWidgets, ScrollHandler, VHasDropHandler, FocusHandler, BlurHandler, Focusable, - ActionOwner { + ActionOwner, SubPartAware { public static final String STYLENAME = "v-table"; @@ -346,7 +348,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return startRow.getIndex() + length - 1; } - }; + } private final HashSet<SelectionRange> selectedRowRanges = new HashSet<SelectionRange>(); @@ -2318,7 +2320,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private int reqFirstRow = 0; private int reqRows = 0; - private boolean isRunning = false; + private boolean isRequestHandlerRunning = false; public void triggerRowFetch(int first, int rows) { setReqFirstRow(first); @@ -2336,12 +2338,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets, deferRowFetch(250); } - public boolean isRunning() { - return isRunning; + public boolean isRequestHandlerRunning() { + return isRequestHandlerRunning; } public void deferRowFetch(int msec) { - isRunning = true; + isRequestHandlerRunning = true; if (reqRows > 0 && reqFirstRow < totalRows) { schedule(msec); @@ -2483,7 +2485,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, unSyncedselectionsBeforeRowFetch = new HashSet<Object>( selectedRowKeys); } - isRunning = false; + isRequestHandlerRunning = false; } } @@ -2491,7 +2493,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, * Sends request to refresh content at this position. */ public void refreshContent() { - isRunning = true; + isRequestHandlerRunning = true; int first = (int) (firstRowInViewPort - pageLength * cache_rate); int reqRows = (int) (2 * pageLength * cache_rate + pageLength); if (first < 0) { @@ -2815,13 +2817,27 @@ public class VScrollTable extends FlowPanel implements HasWidgets, DOM.setInnerHTML(floatingCopyOfHeaderCell, DOM.getInnerHTML(td)); floatingCopyOfHeaderCell = DOM .getChild(floatingCopyOfHeaderCell, 2); - DOM.setElementProperty(floatingCopyOfHeaderCell, "className", - VScrollTable.this.getStylePrimaryName() + "-header-drag"); + // #12714 the shown "ghost element" should be inside + // v-overlay-container, and it should contain the same styles as the + // table to enable theming (except v-table & v-widget). + String stylePrimaryName = VScrollTable.this.getStylePrimaryName(); + StringBuilder sb = new StringBuilder(); + for (String s : VScrollTable.this.getStyleName().split(" ")) { + if (!s.equals(StyleConstants.UI_WIDGET)) { + sb.append(s); + if (s.equals(stylePrimaryName)) { + sb.append("-header-drag "); + } else { + sb.append(" "); + } + } + } + floatingCopyOfHeaderCell.setClassName(sb.toString().trim()); // otherwise might wrap or be cut if narrow column DOM.setStyleAttribute(floatingCopyOfHeaderCell, "width", "auto"); updateFloatingCopysPosition(DOM.getAbsoluteLeft(td), DOM.getAbsoluteTop(td)); - DOM.appendChild(RootPanel.get().getElement(), + DOM.appendChild(VOverlay.getOverlayContainer(client), floatingCopyOfHeaderCell); } @@ -2836,8 +2852,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } private void hideFloatingCopy() { - DOM.removeChild(RootPanel.get().getElement(), - floatingCopyOfHeaderCell); + floatingCopyOfHeaderCell.removeFromParent(); floatingCopyOfHeaderCell = null; } @@ -5083,6 +5098,20 @@ public class VScrollTable extends FlowPanel implements HasWidgets, } } + public int indexOf(Widget row) { + int relIx = -1; + for (int ix = 0; ix < renderedRows.size(); ix++) { + if (renderedRows.get(ix) == row) { + relIx = ix; + break; + } + } + if (relIx >= 0) { + return firstRendered + relIx; + } + return -1; + } + public class VScrollTableRow extends Panel implements ActionOwner { private static final int TOUCHSCROLL_TIMEOUT = 100; @@ -5529,7 +5558,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, com.google.gwt.dom.client.Element target) { TooltipInfo info = null; - final Element targetTdOrTr = getTdOrTr((Element) target.cast()); + final Element targetTdOrTr = getTdOrTr(target); if (targetTdOrTr != null && "td".equals(targetTdOrTr.getTagName().toLowerCase())) { TableCellElement td = (TableCellElement) targetTdOrTr @@ -5554,8 +5583,8 @@ public class VScrollTable extends FlowPanel implements HasWidgets, // Iterate upwards until we find the TR element Element element = target; while (element != null - && element.getParentElement().cast() != thisTrElement) { - element = element.getParentElement().cast(); + && element.getParentElement() != thisTrElement) { + element = element.getParentElement(); } return element; } @@ -6025,8 +6054,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, && rowKeyIsSelected(rowKey)) { // Create a drag image of ALL rows - ev.createDragImage( - (Element) scrollBody.tBodyElement.cast(), true); + ev.createDragImage(scrollBody.tBodyElement, true); // Hide rows which are not selected Element dragImage = ev.getDragImage(); @@ -6064,7 +6092,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private Element getEventTargetTdOrTr(Event event) { final Element eventTarget = event.getEventTarget().cast(); Widget widget = Util.findWidget(eventTarget, null); - final Element thisTrElement = getElement(); if (widget != this) { /* @@ -6896,10 +6923,9 @@ public class VScrollTable extends FlowPanel implements HasWidgets, String s = uidl.hasAttribute("caption") ? uidl .getStringAttribute("caption") : ""; if (uidl.hasAttribute("icon")) { - s = "<img src=\"" - + Util.escapeAttribute(client.translateVaadinUri(uidl - .getStringAttribute("icon"))) - + "\" alt=\"icon\" class=\"v-icon\">" + s; + Icon icon = client.getIcon(uidl.getStringAttribute("icon")); + icon.setAlternateText("icon"); + s = icon.getElement().getString() + s; } return s; } @@ -7080,7 +7106,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, Element tr = row.getElement(); Element element = elementOver; while (element != null && element.getParentElement() != tr) { - element = (Element) element.getParentElement(); + element = element.getParentElement(); } int childIndex = DOM.getChildIndex(tr, element); dropDetails.colkey = tHead.getHeaderCell(childIndex) @@ -7140,7 +7166,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private void deEmphasis() { UIObject.setStyleName(getElement(), - VScrollTable.this.getStylePrimaryName() + "-drag", false); + getStylePrimaryName() + "-drag", false); if (lastEmphasized == null) { return; } @@ -7166,7 +7192,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets, private void emphasis(TableDDDetails details) { deEmphasis(); UIObject.setStyleName(getElement(), - VScrollTable.this.getStylePrimaryName() + "-drag", true); + getStylePrimaryName() + "-drag", true); // iterate old and new emphasized row for (Widget w : scrollBody.renderedRows) { VScrollTableRow row = (VScrollTableRow) w; @@ -7798,4 +7824,101 @@ public class VScrollTable extends FlowPanel implements HasWidgets, return this; } + private static final String SUBPART_HEADER = "header"; + private static final String SUBPART_FOOTER = "footer"; + private static final String SUBPART_ROW = "row"; + private static final String SUBPART_COL = "col"; + /** + * Matches header[ix] - used for extracting the index of the targeted header + * cell + */ + private static final RegExp SUBPART_HEADER_REGEXP = RegExp + .compile(SUBPART_HEADER + "\\[(\\d+)\\]"); + /** + * Matches footer[ix] - used for extracting the index of the targeted footer + * cell + */ + private static final RegExp SUBPART_FOOTER_REGEXP = RegExp + .compile(SUBPART_FOOTER + "\\[(\\d+)\\]"); + /** Matches row[ix] - used for extracting the index of the targeted row */ + private static final RegExp SUBPART_ROW_REGEXP = RegExp.compile(SUBPART_ROW + + "\\[(\\d+)]"); + /** Matches col[ix] - used for extracting the index of the targeted column */ + private static final RegExp SUBPART_ROW_COL_REGEXP = RegExp + .compile(SUBPART_ROW + "\\[(\\d+)\\]/" + SUBPART_COL + + "\\[(\\d+)\\]"); + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if (SUBPART_ROW_COL_REGEXP.test(subPart)) { + MatchResult result = SUBPART_ROW_COL_REGEXP.exec(subPart); + int rowIx = Integer.valueOf(result.getGroup(1)); + int colIx = Integer.valueOf(result.getGroup(2)); + VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); + if (row != null) { + Element rowElement = row.getElement(); + if (colIx < rowElement.getChildCount()) { + return rowElement.getChild(colIx).getFirstChild().cast(); + } + } + + } else if (SUBPART_ROW_REGEXP.test(subPart)) { + MatchResult result = SUBPART_ROW_REGEXP.exec(subPart); + int rowIx = Integer.valueOf(result.getGroup(1)); + VScrollTableRow row = scrollBody.getRowByRowIndex(rowIx); + if (row != null) { + return row.getElement(); + } + + } else if (SUBPART_HEADER_REGEXP.test(subPart)) { + MatchResult result = SUBPART_HEADER_REGEXP.exec(subPart); + int headerIx = Integer.valueOf(result.getGroup(1)); + HeaderCell headerCell = tHead.getHeaderCell(headerIx); + if (headerCell != null) { + return headerCell.getElement(); + } + + } else if (SUBPART_FOOTER_REGEXP.test(subPart)) { + MatchResult result = SUBPART_FOOTER_REGEXP.exec(subPart); + int footerIx = Integer.valueOf(result.getGroup(1)); + FooterCell footerCell = tFoot.getFooterCell(footerIx); + if (footerCell != null) { + return footerCell.getElement(); + } + } + // Nothing found. + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + Widget widget = Util.findWidget(subElement, null); + if (widget instanceof HeaderCell) { + return SUBPART_HEADER + "[" + tHead.visibleCells.indexOf(widget) + + "]"; + } else if (widget instanceof FooterCell) { + return SUBPART_FOOTER + "[" + tFoot.visibleCells.indexOf(widget) + + "]"; + } else if (widget instanceof VScrollTableRow) { + // a cell in a row + VScrollTableRow row = (VScrollTableRow) widget; + int rowIx = scrollBody.indexOf(row); + if (rowIx >= 0) { + int colIx = -1; + for (int ix = 0; ix < row.getElement().getChildCount(); ix++) { + if (row.getElement().getChild(ix).isOrHasChild(subElement)) { + colIx = ix; + break; + } + } + if (colIx >= 0) { + return SUBPART_ROW + "[" + rowIx + "]/" + SUBPART_COL + "[" + + colIx + "]"; + } + return SUBPART_ROW + "[" + rowIx + "]"; + } + } + // Nothing found. + return null; + } } diff --git a/client/src/com/vaadin/client/ui/VSlider.java b/client/src/com/vaadin/client/ui/VSlider.java index 9d993964d3..7ac31fd85a 100644 --- a/client/src/com/vaadin/client/ui/VSlider.java +++ b/client/src/com/vaadin/client/ui/VSlider.java @@ -18,6 +18,7 @@ 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.Element; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Overflow; import com.google.gwt.dom.client.Style.Unit; @@ -28,20 +29,18 @@ import com.google.gwt.event.logical.shared.ValueChangeHandler; 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.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HasValue; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; -import com.vaadin.client.ContainerResizedListener; import com.vaadin.client.Util; import com.vaadin.client.VConsole; import com.vaadin.shared.ui.slider.SliderOrientation; public class VSlider extends SimpleFocusablePanel implements Field, - ContainerResizedListener, HasValue<Double> { + HasValue<Double> { public static final String CLASSNAME = "v-slider"; @@ -199,7 +198,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, return; } - final Element p = getElement().getParentElement().cast(); + final Element p = getElement().getParentElement(); if (p.getPropertyInt(domProperty) > 50) { if (isVertical()) { setHeight(); @@ -214,7 +213,7 @@ public class VSlider extends SimpleFocusablePanel implements Field, @Override public void execute() { - final Element p = getElement().getParentElement().cast(); + final Element p = getElement().getParentElement(); if (p.getPropertyInt(domProperty) > (MIN_SIZE + 5)) { if (isVertical()) { setHeight(); @@ -432,7 +431,6 @@ public class VSlider extends SimpleFocusablePanel implements Field, } } - @Override public void iLayout() { if (isVertical()) { setHeight(); diff --git a/client/src/com/vaadin/client/ui/VTabsheet.java b/client/src/com/vaadin/client/ui/VTabsheet.java index 172a25e3f3..a523287101 100644 --- a/client/src/com/vaadin/client/ui/VTabsheet.java +++ b/client/src/com/vaadin/client/ui/VTabsheet.java @@ -19,8 +19,13 @@ package com.vaadin.client.ui; import java.util.Iterator; import java.util.List; +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.aria.client.SelectedValue; import com.google.gwt.core.client.Scheduler; import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.dom.client.TableCellElement; @@ -38,9 +43,10 @@ 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.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; 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.Event; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.SimplePanel; @@ -49,20 +55,23 @@ import com.google.gwt.user.client.ui.impl.FocusImpl; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; import com.vaadin.client.Util; import com.vaadin.client.VCaption; +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.EventId; +import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; import com.vaadin.shared.ui.ComponentStateUtil; -import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants; -import com.vaadin.shared.ui.tabsheet.TabsheetConstants; +import com.vaadin.shared.ui.tabsheet.TabState; +import com.vaadin.shared.ui.tabsheet.TabsheetServerRpc; +import com.vaadin.shared.ui.tabsheet.TabsheetState; public class VTabsheet extends VTabsheetBase implements Focusable, - FocusHandler, BlurHandler, KeyDownHandler { + FocusHandler, BlurHandler, KeyDownHandler, SubPartAware { private static class VCloseEvent { private Tab tab; @@ -83,7 +92,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Representation of a single "tab" shown in the TabBar - * + * */ public static class Tab extends SimplePanel implements HasFocusHandlers, HasBlurHandlers, HasKeyDownHandlers { @@ -94,12 +103,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, + "-selected"; private static final String TD_SELECTED_FIRST_CLASSNAME = TD_SELECTED_CLASSNAME + "-first"; + private static final String TD_FOCUS_CLASSNAME = TD_CLASSNAME + + "-focus"; + private static final String TD_FOCUS_FIRST_CLASSNAME = TD_FOCUS_CLASSNAME + + "-first"; private static final String TD_DISABLED_CLASSNAME = TD_CLASSNAME + "-disabled"; private static final String DIV_CLASSNAME = CLASSNAME + "-tabitem"; private static final String DIV_SELECTED_CLASSNAME = DIV_CLASSNAME + "-selected"; + private static final String DIV_FOCUS_CLASSNAME = DIV_CLASSNAME + + "-focus"; private TabCaption tabCaption; Element td = getElement(); @@ -112,21 +127,29 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private String styleName; + private String id; + private Tab(TabBar tabBar) { super(DOM.createTD()); this.tabBar = tabBar; setStyleName(td, TD_CLASSNAME); + Roles.getTabRole().set(getElement()); + Roles.getTabRole().setAriaSelectedState(getElement(), + SelectedValue.FALSE); + div = DOM.createDiv(); setTabulatorIndex(-1); setStyleName(div, DIV_CLASSNAME); DOM.appendChild(td, div); - tabCaption = new TabCaption(this, getTabsheet() - .getApplicationConnection()); + tabCaption = new TabCaption(this); add(tabCaption); + Roles.getTabRole().setAriaLabelledbyProperty(getElement(), + Id.of(tabCaption.getElement())); + addFocusHandler(getTabsheet()); addBlurHandler(getTabsheet()); addKeyDownHandler(getTabsheet()); @@ -138,12 +161,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void setHiddenOnServer(boolean hiddenOnServer) { this.hiddenOnServer = hiddenOnServer; + Roles.getTabRole().setAriaHiddenState(getElement(), hiddenOnServer); } @Override - protected Element getContainerElement() { + protected com.google.gwt.user.client.Element getContainerElement() { // Attach caption element to div, not td - return div; + return DOM.asOld(div); } public boolean isEnabledOnServer() { @@ -152,6 +176,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void setEnabledOnServer(boolean enabled) { enabledOnServer = enabled; + Roles.getTabRole().setAriaDisabledState(getElement(), !enabled); + setStyleName(td, TD_DISABLED_CLASSNAME, !enabled); if (!enabled) { focusImpl.setTabIndex(td, -1); @@ -168,17 +194,25 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Toggles the style names for the Tab - * + * * @param selected * true if the Tab is selected * @param first * true if the Tab is the first visible Tab */ public void setStyleNames(boolean selected, boolean first) { + setStyleNames(selected, first, false); + } + + public void setStyleNames(boolean selected, boolean first, + boolean keyboardFocus) { setStyleName(td, TD_FIRST_CLASSNAME, first); setStyleName(td, TD_SELECTED_CLASSNAME, selected); setStyleName(td, TD_SELECTED_FIRST_CLASSNAME, selected && first); setStyleName(div, DIV_SELECTED_CLASSNAME, selected); + setStyleName(td, TD_FOCUS_CLASSNAME, keyboardFocus); + setStyleName(td, TD_FOCUS_FIRST_CLASSNAME, keyboardFocus && first); + setStyleName(div, DIV_FOCUS_CLASSNAME, keyboardFocus); } public void setTabulatorIndex(int tabIndex) { @@ -197,17 +231,15 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return tabBar.getTabsheet(); } - public void updateFromUIDL(UIDL tabUidl) { - tabCaption.updateCaption(tabUidl); - + private void updateFromState(TabState tabState) { + tabCaption.update(tabState); // Apply the styleName set for the tab - String newStyleName = tabUidl - .getStringAttribute(TabsheetConstants.TAB_STYLE_NAME); + String newStyleName = tabState.styleName; // Find the nth td element - if (newStyleName != null && newStyleName.length() != 0) { + if (newStyleName != null && !newStyleName.isEmpty()) { if (!newStyleName.equals(styleName)) { // If we have a new style name - if (styleName != null && styleName.length() != 0) { + if (styleName != null && !styleName.isEmpty()) { // Remove old style name if present td.removeClassName(TD_CLASSNAME + "-" + styleName); } @@ -221,6 +253,15 @@ public class VTabsheet extends VTabsheetBase implements Focusable, td.removeClassName(TD_CLASSNAME + "-" + styleName); styleName = null; } + + String newId = tabState.id; + if (newId != null && !newId.isEmpty()) { + td.setId(newId); + id = newId; + } else if (id != null) { + td.removeAttribute("id"); + id = null; + } } public void recalculateCaptionWidth() { @@ -251,15 +292,21 @@ public class VTabsheet extends VTabsheetBase implements Focusable, focusImpl.blur(td); } - public boolean isSelectable() { - VTabsheet ts = getTabsheet(); - if (ts.client == null || ts.disabled || ts.waitingForResponse) { - return false; - } - if (!isEnabledOnServer() || isHiddenOnServer()) { - return false; - } - return true; + public boolean hasTooltip() { + return tabCaption.getTooltipInfo() != null; + } + + public TooltipInfo getTooltipInfo() { + return tabCaption.getTooltipInfo(); + } + + public void setAssistiveDescription(String descriptionId) { + Roles.getTablistRole().setAriaDescribedbyProperty(getElement(), + Id.of(descriptionId)); + } + + public void removeAssistiveDescription() { + Roles.getTablistRole().removeAriaDescribedbyProperty(getElement()); } } @@ -268,38 +315,46 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private boolean closable = false; private Element closeButton; private Tab tab; - private ApplicationConnection client; - TabCaption(Tab tab, ApplicationConnection client) { - super(client); - this.client = client; + TabCaption(Tab tab) { + super(tab.getTabsheet().connector.getConnection()); this.tab = tab; + + AriaHelper.ensureHasId(getElement()); } - public boolean updateCaption(UIDL uidl) { - if (uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION) - || uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE)) { - setTooltipInfo(new TooltipInfo( - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION), - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE))); + private boolean update(TabState tabState) { + if (tabState.description != null + || tabState.componentError != null) { + setTooltipInfo(new TooltipInfo(tabState.description, + tabState.componentError)); } else { setTooltipInfo(null); } // TODO need to call this instead of super because the caption does // not have an owner - boolean ret = updateCaptionWithoutOwner( - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_CAPTION), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DESCRIPTION), - uidl.hasAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ERROR_MESSAGE), - uidl.getStringAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_ICON)); - - setClosable(uidl.hasAttribute("closable")); + String captionString = tabState.caption.isEmpty() ? null + : tabState.caption; + boolean ret = updateCaptionWithoutOwner(captionString, + !tabState.enabled, + hasAttribute(tabState.description), + hasAttribute(tabState.componentError), + tab.getTabsheet().connector + .getResourceUrl(ComponentConstants.ICON_RESOURCE + + tabState.key), + tabState.iconAltText + ); + + setClosable(tabState.closable); return ret; } + private boolean hasAttribute(String string) { + return string != null && !string.trim().isEmpty(); + } + private VTabsheet getTabsheet() { return tab.getTabsheet(); } @@ -331,6 +386,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, closeButton.setInnerHTML("×"); closeButton .setClassName(VTabsheet.CLASSNAME + "-caption-close"); + + Roles.getTabRole().setAriaHiddenState(closeButton, true); + Roles.getTabRole().setAriaDisabledState(closeButton, true); + getElement().appendChild(closeButton); } else if (!closable && closeButton != null) { getElement().removeChild(closeButton); @@ -356,8 +415,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return width; } - public Element getCloseButton() { - return closeButton; + public com.google.gwt.user.client.Element getCloseButton() { + return DOM.asOld(closeButton); } } @@ -377,6 +436,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, this.tabsheet = tabsheet; Element el = DOM.createTable(); + Roles.getPresentationRole().set(el); + Element tbody = DOM.createTBody(); DOM.appendChild(el, tbody); DOM.appendChild(tbody, tr); @@ -396,8 +457,8 @@ public class VTabsheet extends VTabsheetBase implements Focusable, getTabsheet().sendTabClosedEvent(tabIndex); } - protected Element getContainerElement() { - return tr; + protected com.google.gwt.user.client.Element getContainerElement() { + return DOM.asOld(tr); } public int getTabCount() { @@ -433,7 +494,12 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } int index = getWidgetIndex(caption.getParent()); - getTabsheet().onTabSelected(index); + + navigateTab(getTabsheet().focusedTabIndex, index); + getTabsheet().focusedTabIndex = index; + getTabsheet().focusedTab = getTab(index); + getTabsheet().focus(); + getTabsheet().loadTabSheet(index); } public VTabsheet getTabsheet() { @@ -451,13 +517,18 @@ public class VTabsheet extends VTabsheetBase implements Focusable, final Tab newSelected = getTab(index); final Tab oldSelected = selected; - newSelected.setStyleNames(true, isFirstVisibleTab(index)); + newSelected.setStyleNames(true, isFirstVisibleTab(index), true); newSelected.setTabulatorIndex(getTabsheet().tabulatorIndex); + Roles.getTabRole().setAriaSelectedState(newSelected.getElement(), + SelectedValue.TRUE); if (oldSelected != null && oldSelected != newSelected) { oldSelected.setStyleNames(false, isFirstVisibleTab(getWidgetIndex(oldSelected))); oldSelected.setTabulatorIndex(-1); + + Roles.getTabRole().setAriaSelectedState( + oldSelected.getElement(), SelectedValue.FALSE); } // Update the field holding the currently selected tab @@ -468,6 +539,23 @@ public class VTabsheet extends VTabsheetBase implements Focusable, getTab(tabsheet.activeTabIndex).recalculateCaptionWidth(); } + public void navigateTab(int fromIndex, int toIndex) { + Tab newNavigated = getTab(toIndex); + if (newNavigated == null) { + throw new IllegalArgumentException( + "Tab at provided index toIndex was not found"); + } + + Tab oldNavigated = getTab(fromIndex); + newNavigated.setStyleNames(newNavigated.equals(selected), + isFirstVisibleTab(toIndex), true); + + if (oldNavigated != null && fromIndex != toIndex) { + oldNavigated.setStyleNames(oldNavigated.equals(selected), + isFirstVisibleTab(fromIndex), false); + } + } + public void removeTab(int i) { Tab tab = getTab(i); if (tab == null) { @@ -493,7 +581,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Returns the index of the first visible tab - * + * * @return */ private int getFirstVisibleTab() { @@ -502,7 +590,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Find the next visible tab. Returns -1 if none is found. - * + * * @param i * @return */ @@ -521,7 +609,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Find the previous visible tab. Returns -1 if none is found. - * + * * @param i * @return */ @@ -557,15 +645,25 @@ public class VTabsheet extends VTabsheetBase implements Focusable, currentFirst.recalculateCaptionWidth(); return nextVisible; } + + private void recalculateCaptionWidths() { + for (int i = 0; i < getTabCount(); ++i) { + getTab(i).recalculateCaptionWidth(); + } + } } - public static final String CLASSNAME = "v-tabsheet"; + // TODO using the CLASSNAME directly makes primaryStyleName for TabSheet of + // very limited use - all use of style names should be refactored in the + // future + public static final String CLASSNAME = TabsheetState.PRIMARY_STYLE_NAME; - public static final String TABS_CLASSNAME = "v-tabsheet-tabcontainer"; - public static final String SCROLLER_CLASSNAME = "v-tabsheet-scroller"; + public static final String TABS_CLASSNAME = CLASSNAME + "-tabcontainer"; + public static final String SCROLLER_CLASSNAME = CLASSNAME + "-scroller"; /** For internal use only. May be removed or replaced in the future. */ - public final Element tabs; // tabbar and 'scroller' container + // tabbar and 'scroller' container + public final Element tabs; Tab focusedTab; /** * The tabindex property (position in the browser's focus cycle.) Named like @@ -575,9 +673,12 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private static final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel(); - private final Element scroller; // tab-scroller element - private final Element scrollerNext; // tab-scroller next button element - private final Element scrollerPrev; // tab-scroller prev button element + // tab-scroller element + private final Element scroller; + // tab-scroller next button element + private final Element scrollerNext; + // tab-scroller prev button element + private final Element scrollerPrev; /** * The index of the first visible tab (when scrolled) @@ -586,7 +687,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, final TabBar tb = new TabBar(this); /** For internal use only. May be removed or replaced in the future. */ - public final VTabsheetPanel tp = new VTabsheetPanel(); + protected final VTabsheetPanel tabPanel = new VTabsheetPanel(); /** For internal use only. May be removed or replaced in the future. */ public final Element contentNode; @@ -597,35 +698,92 @@ public class VTabsheet extends VTabsheetBase implements Focusable, private String currentStyle; - private void onTabSelected(final int tabIndex) { - if (activeTabIndex != tabIndex) { + /** For internal use only. May be removed or replaced in the future. */ + private int focusedTabIndex = 0; + + /** + * @return Whether the tab could be selected or not. + */ + private boolean canSelectTab(final int tabIndex) { + Tab tab = tb.getTab(tabIndex); + if (getApplicationConnection() == null || disabled + || waitingForResponse) { + return false; + } + if (!tab.isEnabledOnServer() || tab.isHiddenOnServer()) { + return false; + } + + // Note that we return true when tabIndex == activeTabIndex; the active + // tab could be selected, it's just a no-op. + return true; + } + + /** + * Load the content of a tab of the provided index. + * + * @param index + * of the tab to load + */ + public void loadTabSheet(int tabIndex) { + if (activeTabIndex != tabIndex && canSelectTab(tabIndex)) { tb.selectTab(tabIndex); - // If this TabSheet already has focus, set the new selected tab - // as focused. - if (focusedTab != null) { - focusedTab = tb.getTab(tabIndex); - } + activeTabIndex = tabIndex; addStyleDependentName("loading"); // Hide the current contents so a loading indicator can be shown // instead - Widget currentlyDisplayedWidget = tp.getWidget(tp - .getVisibleWidget()); - currentlyDisplayedWidget.getElement().getParentElement().getStyle() - .setVisibility(Visibility.HIDDEN); - client.updateVariable(id, "selected", tabKeys.get(tabIndex) - .toString(), true); + getCurrentlyDisplayedWidget().getElement().getParentElement() + .getStyle().setVisibility(Visibility.HIDDEN); + + getRpcProxy().setSelected(tabKeys.get(tabIndex).toString()); + waitingForResponse = true; tb.getTab(tabIndex).focus(); // move keyboard focus to active tab } } + /** + * Returns the currently displayed widget in the tab panel. + * + * @since 7.2 + * @return currently displayed content widget + */ + public Widget getCurrentlyDisplayedWidget() { + return tabPanel.getWidget(tabPanel.getVisibleWidget()); + } + + /** + * Returns the client to server RPC proxy for the tabsheet. + * + * @since 7.2 + * @return RPC proxy + */ + protected TabsheetServerRpc getRpcProxy() { + return connector.getRpcProxy(TabsheetServerRpc.class); + } + + /** + * For internal use only. + * + * Avoid using this method directly and use appropriate superclass methods + * where applicable. + * + * @deprecated since 7.2 - use more specific methods instead (getRpcProxy(), + * getConnectorForWidget(Widget) etc.) + * @return ApplicationConnection + */ + @Deprecated public ApplicationConnection getApplicationConnection() { return client; } + private VTooltip getVTooltip() { + return getApplicationConnection().getVTooltip(); + } + public void tabSizeMightHaveChanged(Tab tab) { // icon onloads may change total width of tabsheet if (isDynamicWidth()) { @@ -636,19 +794,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } void sendTabClosedEvent(int tabIndex) { - client.updateVariable(id, "close", tabKeys.get(tabIndex), true); - } - - boolean isDynamicWidth() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedWidth(); - } - - boolean isDynamicHeight() { - ComponentConnector paintable = ConnectorMap.get(client).getConnector( - this); - return paintable.isUndefinedHeight(); + getRpcProxy().closeTab(tabKeys.get(tabIndex)); } public VTabsheet() { @@ -661,22 +807,30 @@ public class VTabsheet extends VTabsheetBase implements Focusable, DOM.setStyleAttribute(getElement(), "overflow", "hidden"); tabs = DOM.createDiv(); DOM.setElementProperty(tabs, "className", TABS_CLASSNAME); + Roles.getTablistRole().set(tabs); + Roles.getTablistRole().setAriaLiveProperty(tabs, LiveValue.OFF); scroller = DOM.createDiv(); + Roles.getTablistRole().setAriaHiddenState(scroller, true); DOM.setElementProperty(scroller, "className", SCROLLER_CLASSNAME); scrollerPrev = DOM.createButton(); + scrollerPrev.setTabIndex(-1); DOM.setElementProperty(scrollerPrev, "className", SCROLLER_CLASSNAME + "Prev"); + Roles.getTablistRole().setAriaHiddenState(scrollerPrev, true); DOM.sinkEvents(scrollerPrev, Event.ONCLICK); scrollerNext = DOM.createButton(); + scrollerNext.setTabIndex(-1); DOM.setElementProperty(scrollerNext, "className", SCROLLER_CLASSNAME + "Next"); + Roles.getTablistRole().setAriaHiddenState(scrollerNext, true); DOM.sinkEvents(scrollerNext, Event.ONCLICK); DOM.appendChild(getElement(), tabs); // Tabs - tp.setStyleName(CLASSNAME + "-tabsheetpanel"); + tabPanel.setStyleName(CLASSNAME + "-tabsheetpanel"); contentNode = DOM.createDiv(); + Roles.getTabpanelRole().set(contentNode); deco = DOM.createDiv(); @@ -690,7 +844,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, DOM.appendChild(scroller, scrollerNext); DOM.appendChild(getElement(), contentNode); - add(tp, contentNode); + add(tabPanel, contentNode); DOM.appendChild(getElement(), deco); DOM.appendChild(tabs, scroller); @@ -731,7 +885,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, /** * Checks if the tab with the selected index has been scrolled out of the * view (on the left side). - * + * * @param index * @return */ @@ -740,7 +894,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } /** For internal use only. May be removed or replaced in the future. */ - public void handleStyleNames(UIDL uidl, AbstractComponentState state) { + public void handleStyleNames(AbstractComponentState state) { // Add proper stylenames for all elements (easier to prevent unwanted // style inheritance) if (ComponentStateUtil.hasStyles(state)) { @@ -771,14 +925,6 @@ public class VTabsheet extends VTabsheetBase implements Focusable, + "-content"); DOM.setElementProperty(deco, "className", CLASSNAME + "-deco"); } - - if (uidl.hasAttribute("hidetabs")) { - tb.setVisible(false); - addStyleName(CLASSNAME + "-hidetabs"); - } else { - tb.setVisible(true); - removeStyleName(CLASSNAME + "-hidetabs"); - } } /** For internal use only. May be removed or replaced in the future. */ @@ -795,16 +941,16 @@ public class VTabsheet extends VTabsheetBase implements Focusable, int tabsWidth = tb.getOffsetWidth() - spacerWidth + spacerMinWidth; // Find content width - Style style = tp.getElement().getStyle(); + Style style = tabPanel.getElement().getStyle(); String overflow = style.getProperty("overflow"); style.setProperty("overflow", "hidden"); style.setPropertyPx("width", tabsWidth); - boolean hasTabs = tp.getWidgetCount() > 0; + boolean hasTabs = tabPanel.getWidgetCount() > 0; Style wrapperstyle = null; if (hasTabs) { - wrapperstyle = tp.getWidget(tp.getVisibleWidget()).getElement() + wrapperstyle = getCurrentlyDisplayedWidget().getElement() .getParentElement().getStyle(); wrapperstyle.setPropertyPx("width", tabsWidth); } @@ -812,7 +958,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, int contentWidth = 0; if (hasTabs) { - contentWidth = tp.getWidget(tp.getVisibleWidget()).getOffsetWidth(); + contentWidth = getCurrentlyDisplayedWidget().getOffsetWidth(); } style.setProperty("overflow", overflow); @@ -835,26 +981,22 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @Override - public void renderTab(final UIDL tabUidl, int index, boolean selected, - boolean hidden) { + public void renderTab(final TabState tabState, int index) { Tab tab = tb.getTab(index); if (tab == null) { tab = tb.addTab(); } - if (selected) { - tb.selectTab(index); - renderContent(tabUidl.getChildUIDL(0)); - } - tab.updateFromUIDL(tabUidl); + + tab.updateFromState(tabState); tab.setEnabledOnServer((!disabledTabKeys.contains(tabKeys.get(index)))); - tab.setHiddenOnServer(hidden); + tab.setHiddenOnServer(!tabState.visible); if (scrolledOutOfView(index)) { // Should not set tabs visible if they are scrolled out of view - hidden = true; + tabState.visible = false; } // Set the current visibility of the tab (in the browser) - tab.setVisible(!hidden); + tab.setVisible(tabState.visible); /* * Force the width of the caption container so the content will not wrap @@ -874,29 +1016,45 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } } - private void renderContent(final UIDL contentUIDL) { - final ComponentConnector content = client.getPaintable(contentUIDL); - Widget newWidget = content.getWidget(); + /** + * Renders the widget content for a tab sheet. + * + * @param newWidget + */ + public void renderContent(Widget newWidget) { + assert tabPanel.getWidgetCount() <= 1; - assert tp.getWidgetCount() <= 1; + if (null == newWidget) { + newWidget = new SimplePanel(); + } - if (tp.getWidgetCount() == 0) { - tp.add(newWidget); - } else if (tp.getWidget(0) != newWidget) { - tp.remove(0); - tp.add(newWidget); + if (tabPanel.getWidgetCount() == 0) { + tabPanel.add(newWidget); + } else if (tabPanel.getWidget(0) != newWidget) { + tabPanel.remove(0); + tabPanel.add(newWidget); } - assert tp.getWidgetCount() <= 1; + assert tabPanel.getWidgetCount() <= 1; // There's never any other index than 0, but maintaining API for now - tp.showWidget(0); + tabPanel.showWidget(0); VTabsheet.this.iLayout(); updateOpenTabSize(); VTabsheet.this.removeStyleDependentName("loading"); } + /** + * Recalculates the sizes of tab captions, causing the tabs to be + * rendered the correct size. + */ + private void updateTabCaptionSizes() { + for (int tabIx = 0; tabIx < tb.getTabCount(); tabIx++) { + tb.getTab(tabIx).recalculateCaptionWidth(); + } + } + /** For internal use only. May be removed or replaced in the future. */ public void updateContentNodeHeight() { if (!isDynamicHeight()) { @@ -914,8 +1072,12 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } } + /** + * Run internal layouting. + */ public void iLayout() { updateTabScroller(); + updateTabCaptionSizes(); } /** @@ -947,7 +1109,7 @@ public class VTabsheet extends VTabsheetBase implements Focusable, */ minWidth = tb.getOffsetWidth() - getContentAreaBorderWidth(); } - tp.fixVisibleTabSize(width, height, minWidth); + tabPanel.fixVisibleTabSize(width, height, minWidth); } @@ -1038,13 +1200,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, while (i > 0) { tb.removeTab(--i); } - tp.clear(); + tabPanel.clear(); } @Override public Iterator<Widget> getWidgetIterator() { - return tp.iterator(); + return tabPanel.iterator(); } private int borderW = -1; @@ -1064,9 +1226,9 @@ public class VTabsheet extends VTabsheetBase implements Focusable, @Override public ComponentConnector getTab(int index) { - if (tp.getWidgetCount() > index) { - Widget widget = tp.getWidget(index); - return ConnectorMap.get(client).getConnector(widget); + if (tabPanel.getWidgetCount() > index) { + Widget widget = tabPanel.getWidget(index); + return getConnectorForWidget(widget); } return null; } @@ -1079,11 +1241,19 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } @Override + public void selectTab(int index) { + tb.selectTab(index); + } + + @Override public void onBlur(BlurEvent event) { + getVTooltip().hideTooltip(); + if (focusedTab != null && focusedTab == event.getSource()) { + focusedTab.removeAssistiveDescription(); focusedTab = null; - if (client.hasEventListeners(this, EventId.BLUR)) { - client.updateVariable(id, EventId.BLUR, "", true); + if (connector.hasEventListener(EventId.BLUR)) { + connector.getRpcProxy(FocusAndBlurServerRpc.class).blur(); } } } @@ -1092,8 +1262,13 @@ public class VTabsheet extends VTabsheetBase implements Focusable, public void onFocus(FocusEvent event) { if (focusedTab == null && event.getSource() instanceof Tab) { focusedTab = (Tab) event.getSource(); - if (client.hasEventListeners(this, EventId.FOCUS)) { - client.updateVariable(id, EventId.FOCUS, "", true); + if (connector.hasEventListener(EventId.FOCUS)) { + connector.getRpcProxy(FocusAndBlurServerRpc.class).focus(); + } + + if (focusedTab.hasTooltip()) { + focusedTab.setAssistiveDescription(getVTooltip().getUniqueId()); + getVTooltip().showAssistive(focusedTab.getTooltipInfo()); } } } @@ -1115,13 +1290,17 @@ public class VTabsheet extends VTabsheetBase implements Focusable, if (!event.isAnyModifierKeyDown()) { if (keycode == getPreviousTabKey()) { selectPreviousTab(); + event.stopPropagation(); } else if (keycode == getNextTabKey()) { selectNextTab(); + event.stopPropagation(); } else if (keycode == getCloseTabKey()) { Tab tab = tb.getTab(activeTabIndex); if (tab.isClosable()) { tab.onClose(); } + } else if (keycode == getSelectTabKey()) { + loadTabSheet(focusedTabIndex); } } } @@ -1135,6 +1314,10 @@ public class VTabsheet extends VTabsheetBase implements Focusable, return KeyCodes.KEY_LEFT; } + protected int getSelectTabKey() { + return 32; // Space key + } + /** * @return The key code of the keyboard shortcut that selects the next tab * in a focused tabsheet. @@ -1152,32 +1335,43 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } private void selectPreviousTab() { - int newTabIndex = activeTabIndex; - Tab newTab; + int newTabIndex = focusedTabIndex; // Find the previous visible and enabled tab if any. do { newTabIndex--; - newTab = tb.getTab(newTabIndex); - } while (newTabIndex >= 0 && !newTab.isSelectable()); + } while (newTabIndex >= 0 && !canSelectTab(newTabIndex)); if (newTabIndex >= 0) { - onTabSelected(newTabIndex); - activeTabIndex = newTabIndex; + tb.navigateTab(focusedTabIndex, newTabIndex); + focusedTabIndex = newTabIndex; + + // If this TabSheet already has focus, set the new selected tab + // as focused. + if (focusedTab != null) { + focusedTab = tb.getTab(focusedTabIndex); + focusedTab.focus(); + } } } private void selectNextTab() { - int newTabIndex = activeTabIndex; - Tab newTab; + int newTabIndex = focusedTabIndex; // Find the next visible and enabled tab if any. do { newTabIndex++; - newTab = tb.getTab(newTabIndex); - } while (newTabIndex < getTabCount() && !newTab.isSelectable()); + } while (newTabIndex < getTabCount() && !canSelectTab(newTabIndex)); if (newTabIndex < getTabCount()) { - onTabSelected(newTabIndex); - activeTabIndex = newTabIndex; + + tb.navigateTab(focusedTabIndex, newTabIndex); + focusedTabIndex = newTabIndex; + + // If this TabSheet already has focus, set the new selected tab + // as focused. + if (focusedTab != null) { + focusedTab = tb.getTab(focusedTabIndex); + focusedTab.focus(); + } } } @@ -1196,4 +1390,70 @@ public class VTabsheet extends VTabsheetBase implements Focusable, } } } + + /** + * Makes tab bar visible. + * + * @since 7.2 + */ + public void showTabs() { + tb.setVisible(true); + removeStyleName(CLASSNAME + "-hidetabs"); + tb.recalculateCaptionWidths(); + } + + /** + * Makes tab bar invisible. + * + * @since 7.2 + */ + public void hideTabs() { + tb.setVisible(false); + addStyleName(CLASSNAME + "-hidetabs"); + } + + /** Matches tab[ix] - used for extracting the index of the targeted tab */ + private static final RegExp SUBPART_TAB_REGEXP = RegExp + .compile("tab\\[(\\d+)](.*)"); + + @Override + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { + if ("tabpanel".equals(subPart)) { + return DOM.asOld(tabPanel.getElement().getFirstChildElement()); + } else if (SUBPART_TAB_REGEXP.test(subPart)) { + MatchResult result = SUBPART_TAB_REGEXP.exec(subPart); + int tabIx = Integer.valueOf(result.getGroup(1)); + Tab tab = tb.getTab(tabIx); + if (tab != null) { + if ("/close".equals(result.getGroup(2))) { + if (tab.isClosable()) { + return tab.tabCaption.getCloseButton(); + } + } else { + return tab.tabCaption.getElement(); + } + } + } + return null; + } + + @Override + public String getSubPartName(com.google.gwt.user.client.Element subElement) { + if (tabPanel.getElement().equals(subElement.getParentElement()) + || tabPanel.getElement().equals(subElement)) { + return "tabpanel"; + } else { + for (int i = 0; i < tb.getTabCount(); ++i) { + Tab tab = tb.getTab(i); + if (tab.isClosable() + && tab.tabCaption.getCloseButton().isOrHasChild( + subElement)) { + return "tab[" + i + "]/close"; + } else if (tab.getElement().isOrHasChild(subElement)) { + return "tab[" + i + "]"; + } + } + } + return null; + } } diff --git a/client/src/com/vaadin/client/ui/VTabsheetBase.java b/client/src/com/vaadin/client/ui/VTabsheetBase.java index 0923248115..6d9f78e87f 100644 --- a/client/src/com/vaadin/client/ui/VTabsheetBase.java +++ b/client/src/com/vaadin/client/ui/VTabsheetBase.java @@ -25,26 +25,28 @@ import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.UIDL; +import com.vaadin.client.ConnectorMap; +import com.vaadin.shared.ui.tabsheet.TabState; public abstract class VTabsheetBase extends ComplexPanel { /** For internal use only. May be removed or replaced in the future. */ - public String id; - /** For internal use only. May be removed or replaced in the future. */ - public ApplicationConnection client; + protected ApplicationConnection client; /** For internal use only. May be removed or replaced in the future. */ - public final ArrayList<String> tabKeys = new ArrayList<String>(); + protected final ArrayList<String> tabKeys = new ArrayList<String>(); /** For internal use only. May be removed or replaced in the future. */ - public Set<String> disabledTabKeys = new HashSet<String>(); + protected Set<String> disabledTabKeys = new HashSet<String>(); /** For internal use only. May be removed or replaced in the future. */ - public int activeTabIndex = 0; + protected int activeTabIndex = 0; + /** For internal use only. May be removed or replaced in the future. */ + protected boolean disabled; /** For internal use only. May be removed or replaced in the future. */ - public boolean disabled; + protected boolean readonly; + /** For internal use only. May be removed or replaced in the future. */ - public boolean readonly; + protected AbstractComponentConnector connector; public VTabsheetBase(String classname) { setElement(DOM.createDiv()); @@ -59,14 +61,13 @@ public abstract class VTabsheetBase extends ComplexPanel { /** * Clears current tabs and contents */ - abstract protected void clearPaintables(); + protected abstract void clearPaintables(); /** * Implement in extending classes. This method should render needed elements * and set the visibility of the tab according to the 'selected' parameter. */ - public abstract void renderTab(final UIDL tabUidl, int index, - boolean selected, boolean hidden); + public abstract void renderTab(TabState tabState, int index); /** * Implement in extending classes. This method should return the number of @@ -85,4 +86,79 @@ public abstract class VTabsheetBase extends ComplexPanel { * tab with the specified index. */ public abstract void removeTab(int index); + + /** + * Returns true if the width of the widget is undefined, false otherwise. + * + * @since 7.2 + * @return true if width of the widget is determined by its content + */ + protected boolean isDynamicWidth() { + return getConnectorForWidget(this).isUndefinedWidth(); + } + + /** + * Returns true if the height of the widget is undefined, false otherwise. + * + * @since 7.2 + * @return true if width of the height is determined by its content + */ + protected boolean isDynamicHeight() { + return getConnectorForWidget(this).isUndefinedHeight(); + } + + /** + * Sets the connector that should be notified of events etc. + * + * For internal use only. This method may be removed or replaced in the + * future. + * + * @since 7.2 + * @param connector + */ + public void setConnector(AbstractComponentConnector connector) { + this.connector = connector; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void clearTabKeys() { + tabKeys.clear(); + disabledTabKeys.clear(); + } + + /** For internal use only. May be removed or replaced in the future. */ + public void addTabKey(String key, boolean disabled) { + tabKeys.add(key); + if (disabled) { + disabledTabKeys.add(key); + } + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setClient(ApplicationConnection client) { + this.client = client; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setActiveTabIndex(int activeTabIndex) { + this.activeTabIndex = activeTabIndex; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setEnabled(boolean enabled) { + disabled = !enabled; + } + + /** For internal use only. May be removed or replaced in the future. */ + public void setReadonly(boolean readonly) { + this.readonly = readonly; + } + + /** For internal use only. May be removed or replaced in the future. */ + protected ComponentConnector getConnectorForWidget(Widget widget) { + return ConnectorMap.get(client).getConnector(widget); + } + + /** For internal use only. May be removed or replaced in the future. */ + public abstract void selectTab(int index); } diff --git a/client/src/com/vaadin/client/ui/VTabsheetPanel.java b/client/src/com/vaadin/client/ui/VTabsheetPanel.java index 10ef0aeb65..6bd63cdbd3 100644 --- a/client/src/com/vaadin/client/ui/VTabsheetPanel.java +++ b/client/src/com/vaadin/client/ui/VTabsheetPanel.java @@ -16,8 +16,8 @@ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.ComplexPanel; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; @@ -165,8 +165,7 @@ public class VTabsheetPanel extends ComplexPanel { width = minWidth; } - Element wrapperDiv = (Element) visibleWidget.getElement() - .getParentElement(); + Element wrapperDiv = visibleWidget.getElement().getParentElement(); // width first getElement().getStyle().setPropertyPx("width", width); diff --git a/client/src/com/vaadin/client/ui/VTextField.java b/client/src/com/vaadin/client/ui/VTextField.java index 9360a6e172..98c8699405 100644 --- a/client/src/com/vaadin/client/ui/VTextField.java +++ b/client/src/com/vaadin/client/ui/VTextField.java @@ -16,6 +16,7 @@ package com.vaadin.client.ui; +import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ChangeEvent; @@ -26,7 +27,6 @@ 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.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.ui.TextBoxBase; diff --git a/client/src/com/vaadin/client/ui/VTextualDate.java b/client/src/com/vaadin/client/ui/VTextualDate.java index 9d7e31faab..44a3321f6f 100644 --- a/client/src/com/vaadin/client/ui/VTextualDate.java +++ b/client/src/com/vaadin/client/ui/VTextualDate.java @@ -25,7 +25,6 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.FocusHandler; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.TextBox; import com.vaadin.client.Focusable; import com.vaadin.client.LocaleNotLoadedException; @@ -164,7 +163,8 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, } @Override - public void bindAriaCaption(Element captionElement) { + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { AriaHelper.bindCaption(text, captionElement); } @@ -364,7 +364,7 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, private final String TEXTFIELD_ID = "field"; @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if (subPart.equals(TEXTFIELD_ID)) { return text.getElement(); } @@ -373,7 +373,7 @@ public class VTextualDate extends VDateField implements Field, ChangeHandler, } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (text.getElement().isOrHasChild(subElement)) { return TEXTFIELD_ID; } diff --git a/client/src/com/vaadin/client/ui/VTree.java b/client/src/com/vaadin/client/ui/VTree.java index 51c00ca310..4979de6a47 100644 --- a/client/src/com/vaadin/client/ui/VTree.java +++ b/client/src/com/vaadin/client/ui/VTree.java @@ -31,6 +31,7 @@ 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; +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.event.dom.client.BlurEvent; @@ -46,7 +47,6 @@ import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler; 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.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.FlowPanel; @@ -70,6 +70,7 @@ import com.vaadin.client.ui.dd.VDragEvent; import com.vaadin.client.ui.dd.VDropHandler; import com.vaadin.client.ui.dd.VHasDropHandler; import com.vaadin.client.ui.dd.VTransferable; +import com.vaadin.client.ui.tree.TreeConnector; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.MouseEventDetails.MouseButton; import com.vaadin.shared.ui.MultiSelectMode; @@ -162,6 +163,9 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, /** For internal use only. May be removed or replaced in the future. */ public String[] bodyActionKeys; + /** For internal use only. May be removed or replaced in the future. */ + public TreeConnector connector; + public VLazyExecutor iconLoaded = new VLazyExecutor(50, new ScheduledCommand() { @@ -1120,23 +1124,15 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } 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) { - DOM.removeChild(DOM.getFirstChild(nodeCaptionDiv), - icon.getElement()); - icon = null; - } + if (icon != null) { + DOM.getFirstChild(nodeCaptionDiv) + .removeChild(icon.getElement()); + } + icon = client.getIcon(iconUrl); + if (icon != null) { + DOM.insertBefore(DOM.getFirstChild(nodeCaptionDiv), + icon.getElement(), nodeCaptionSpan); + icon.setAlternateText(altText); } } @@ -1729,6 +1725,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } } } + showTooltipForKeyboardNavigation(node); return true; } @@ -1754,6 +1751,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } } } + showTooltipForKeyboardNavigation(node); return true; } @@ -1774,6 +1772,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, focusAndSelectNode(focusedNode.getParentNode()); } } + showTooltipForKeyboardNavigation(focusedNode); return true; } @@ -1792,6 +1791,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, focusAndSelectNode(focusedNode.getChildren().get(0)); } } + showTooltipForKeyboardNavigation(focusedNode); return true; } @@ -1820,6 +1820,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); return true; } @@ -1836,12 +1837,20 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, selectNode(node, true); } sendSelectionToServer(); + showTooltipForKeyboardNavigation(node); return true; } return false; } + private void showTooltipForKeyboardNavigation(TreeNode node) { + if (connector != null) { + getClient().getVTooltip().showAssistive( + connector.getTooltipInfo(node.nodeCaptionSpan)); + } + } + private void focusAndSelectNode(TreeNode node) { /* * Keyboard navigation doesn't work reliably if the tree is in @@ -2051,7 +2060,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, * .lang.String) */ @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if ("fe".equals(subPart)) { if (BrowserInfo.get().isOpera() && focusedNode != null) { return focusedNode.getElement(); @@ -2087,7 +2096,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, if (expandCollapse) { return treeNode.getElement(); } else { - return treeNode.nodeCaptionSpan; + return DOM.asOld(treeNode.nodeCaptionSpan); } } catch (Exception e) { // Invalid locator string or node could not be found @@ -2104,7 +2113,7 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, * .gwt.user.client.Element) */ @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { // Supported identifiers: // // n[index]/n[index]/n[index]{/expand} @@ -2213,7 +2222,8 @@ public class VTree extends FocusElementPanel implements VHasDropHandler, } @Override - public void bindAriaCaption(Element captionElement) { + public void bindAriaCaption( + com.google.gwt.user.client.Element captionElement) { AriaHelper.bindCaption(body, captionElement); } } diff --git a/client/src/com/vaadin/client/ui/VTreeTable.java b/client/src/com/vaadin/client/ui/VTreeTable.java index 54c9c2d30c..591aa6b0de 100644 --- a/client/src/com/vaadin/client/ui/VTreeTable.java +++ b/client/src/com/vaadin/client/ui/VTreeTable.java @@ -25,6 +25,7 @@ import com.google.gwt.animation.client.Animation; 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.ImageElement; import com.google.gwt.dom.client.SpanElement; import com.google.gwt.dom.client.Style.Display; @@ -33,7 +34,6 @@ import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.event.dom.client.KeyCodes; 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.ui.Widget; import com.vaadin.client.ComputedStyle; @@ -663,8 +663,8 @@ public class VTreeTable extends VScrollTable { } private void insertAnimatingDiv() { - Element tableBody = getElement().cast(); - Element tableBodyParent = tableBody.getParentElement().cast(); + Element tableBody = getElement(); + Element tableBodyParent = tableBody.getParentElement(); tableBodyParent.insertAfter(cloneDiv, tableBody); } @@ -709,8 +709,7 @@ public class VTreeTable extends VScrollTable { resetCellWrapperDivsDisplayProperty(row); row.removeStyleName("v-table-row-animating"); } - Element tableBodyParent = (Element) getElement() - .getParentElement(); + Element tableBodyParent = getElement().getParentElement(); tableBodyParent.removeChild(cloneDiv); } diff --git a/client/src/com/vaadin/client/ui/VTwinColSelect.java b/client/src/com/vaadin/client/ui/VTwinColSelect.java index a53ed835d2..33f1afea31 100644 --- a/client/src/com/vaadin/client/ui/VTwinColSelect.java +++ b/client/src/com/vaadin/client/ui/VTwinColSelect.java @@ -33,7 +33,6 @@ import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.ListBox; @@ -569,20 +568,20 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, private static final String SUBPART_REMOVE_BUTTON = "remove"; @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if (SUBPART_OPTION_SELECT.equals(subPart)) { return options.getElement(); } else if (subPart.startsWith(SUBPART_OPTION_SELECT_ITEM)) { String idx = subPart.substring(SUBPART_OPTION_SELECT_ITEM.length()); - return (Element) options.getElement().getChild( - Integer.parseInt(idx)); + return (com.google.gwt.user.client.Element) options.getElement() + .getChild(Integer.parseInt(idx)); } else if (SUBPART_SELECTION_SELECT.equals(subPart)) { return selections.getElement(); } else if (subPart.startsWith(SUBPART_SELECTION_SELECT_ITEM)) { String idx = subPart.substring(SUBPART_SELECTION_SELECT_ITEM .length()); - return (Element) selections.getElement().getChild( - Integer.parseInt(idx)); + return (com.google.gwt.user.client.Element) selections.getElement() + .getChild(Integer.parseInt(idx)); } else if (optionsCaption != null && SUBPART_LEFT_CAPTION.equals(subPart)) { return optionsCaption.getElement(); @@ -599,7 +598,7 @@ public class VTwinColSelect extends VOptionGroupBase implements KeyDownHandler, } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (optionsCaption != null && optionsCaption.getElement().isOrHasChild(subElement)) { return SUBPART_LEFT_CAPTION; diff --git a/client/src/com/vaadin/client/ui/VUI.java b/client/src/com/vaadin/client/ui/VUI.java index ba3495743d..590263a5ed 100644 --- a/client/src/com/vaadin/client/ui/VUI.java +++ b/client/src/com/vaadin/client/ui/VUI.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui; import java.util.ArrayList; +import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.HasScrollHandlers; @@ -43,6 +44,7 @@ import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.LayoutManager; import com.vaadin.client.Profiler; +import com.vaadin.client.Util; import com.vaadin.client.VConsole; import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner; import com.vaadin.client.ui.TouchScrollDelegate.TouchScrollHandler; @@ -162,6 +164,8 @@ public class VUI extends SimplePanel implements ResizeHandler, }); + private Element storedFocus; + public VUI() { super(); // Allow focusing the view by using the focus() method, the view @@ -494,4 +498,36 @@ public class VUI extends SimplePanel implements ResizeHandler, FocusUtil.setTabIndex(this, index); } + /** + * Allows to store the currently focused Element. + * + * Current use case is to store the focus when a Window is opened. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @param focusedElement + */ + public void storeFocus() { + storedFocus = Util.getFocusedElement(); + } + + /** + * Restores the previously stored focus Element. + * + * Current use case is to restore the focus when a Window is closed. Does + * currently handle only a single value. Needs to be extended for #12158 + * + * @return the lastFocusElementBeforeDialogOpened + */ + public void focusStoredElement() { + if (storedFocus != null) { + storedFocus.focus(); + + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + storedFocus.focus(); + } + }); + } + } } diff --git a/client/src/com/vaadin/client/ui/VUnknownComponent.java b/client/src/com/vaadin/client/ui/VUnknownComponent.java index e77a4f76dd..ea97110aaa 100644 --- a/client/src/com/vaadin/client/ui/VUnknownComponent.java +++ b/client/src/com/vaadin/client/ui/VUnknownComponent.java @@ -22,7 +22,7 @@ import com.vaadin.client.SimpleTree; public class VUnknownComponent extends Composite { - com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label();; + com.google.gwt.user.client.ui.Label caption = new com.google.gwt.user.client.ui.Label(); SimpleTree uidlTree; protected VerticalPanel panel; diff --git a/client/src/com/vaadin/client/ui/VUpload.java b/client/src/com/vaadin/client/ui/VUpload.java index 8e55387d39..bcb4265d50 100644 --- a/client/src/com/vaadin/client/ui/VUpload.java +++ b/client/src/com/vaadin/client/ui/VUpload.java @@ -21,11 +21,11 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.FormElement; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Command; -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.ui.FileUpload; diff --git a/client/src/com/vaadin/client/ui/VVideo.java b/client/src/com/vaadin/client/ui/VVideo.java index 9d6a531a74..376c832bed 100644 --- a/client/src/com/vaadin/client/ui/VVideo.java +++ b/client/src/com/vaadin/client/ui/VVideo.java @@ -17,9 +17,9 @@ package com.vaadin.client.ui; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.dom.client.VideoElement; -import com.google.gwt.user.client.Element; import com.vaadin.client.Util; public class VVideo extends VMediaBase { diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 6afb6c2d8a..5b6595ea43 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -18,11 +18,18 @@ package com.vaadin.client.ui; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.List; +import com.google.gwt.aria.client.Id; +import com.google.gwt.aria.client.RelevantValue; +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.Document; +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.Position; import com.google.gwt.dom.client.Style.Unit; @@ -33,35 +40,43 @@ import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.KeyUpEvent; +import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; 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.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; +import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; +import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorMap; 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.client.ui.aria.AriaHelper; import com.vaadin.client.ui.window.WindowMoveEvent; import com.vaadin.client.ui.window.WindowMoveHandler; +import com.vaadin.shared.Connector; import com.vaadin.shared.EventId; import com.vaadin.shared.ui.window.WindowMode; +import com.vaadin.shared.ui.window.WindowRole; /** * "Sub window" component. * * @author Vaadin Ltd */ -public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, - ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable { +public class VWindow extends VWindowOverlay implements + ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler, + KeyUpHandler, FocusHandler, BlurHandler, Focusable { private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>(); @@ -145,6 +160,22 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, private boolean closable = true; + private Connector[] assistiveConnectors = new Connector[0]; + private String assistivePrefix; + private String assistivePostfix; + + private Element topTabStop; + private Element bottomTabStop; + + private NativePreviewHandler topEventBlocker; + private NativePreviewHandler bottomEventBlocker; + + private HandlerRegistration topBlockerRegistration; + private HandlerRegistration bottomBlockerRegistration; + + // Prevents leaving the window with the Tab key when true + private boolean doTabStop; + private boolean hasFocus; /** @@ -181,13 +212,74 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, // Different style of shadow for windows setShadowStyle("window"); + Roles.getDialogRole().set(getElement()); + Roles.getDialogRole().setAriaRelevantProperty(getElement(), + RelevantValue.ADDITIONS); + constructDOM(); contentPanel.addScrollHandler(this); contentPanel.addKeyDownHandler(this); + contentPanel.addKeyUpHandler(this); contentPanel.addFocusHandler(this); contentPanel.addBlurHandler(this); } + @Override + protected void onAttach() { + super.onAttach(); + + /* + * Stores the element that has focus in the application UI when the + * window is opened, so it can be restored when the window closes. + * + * This is currently implemented for the case when one non-modal window + * can be open at the same time, and the focus is not changed while the + * window is open. + */ + getApplicationConnection().getUIConnector().getWidget().storeFocus(); + + /* + * When this window gets reattached, set the tabstop to the previous + * state. + */ + setTabStopEnabled(doTabStop); + } + + @Override + protected void onDetach() { + super.onDetach(); + + /* + * Restores the previously stored focused element. + * + * When the focus was changed outside the window while the window was + * open, the originally stored element is restored. + */ + getApplicationConnection().getUIConnector().getWidget() + .focusStoredElement(); + + removeTabBlockHandlers(); + } + + private void addTabBlockHandlers() { + if (topBlockerRegistration == null) { + topBlockerRegistration = Event + .addNativePreviewHandler(topEventBlocker); + bottomBlockerRegistration = Event + .addNativePreviewHandler(bottomEventBlocker); + } + } + + private void removeTabBlockHandlers() { + if (topBlockerRegistration != null) { + topBlockerRegistration.removeHandler(); + topBlockerRegistration = null; + + bottomBlockerRegistration.removeHandler(); + bottomBlockerRegistration = null; + } + } + public void bringToFront() { int curIndex = windowOrder.indexOf(this); if (curIndex + 1 < windowOrder.size()) { @@ -240,17 +332,20 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } } - protected Element getModalityCurtain() { + protected com.google.gwt.user.client.Element getModalityCurtain() { if (modalityCurtain == null) { modalityCurtain = DOM.createDiv(); modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain"); } - return modalityCurtain; + return DOM.asOld(modalityCurtain); } protected void constructDOM() { setStyleName(CLASSNAME); + topTabStop = DOM.createDiv(); + DOM.setElementAttribute(topTabStop, "tabindex", "0"); + header = DOM.createDiv(); DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader"); headerText = DOM.createDiv(); @@ -265,18 +360,25 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, maximizeRestoreBox = DOM.createDiv(); DOM.setElementProperty(maximizeRestoreBox, "className", CLASSNAME + "-maximizebox"); + DOM.setElementAttribute(maximizeRestoreBox, "tabindex", "0"); DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox"); + DOM.setElementAttribute(closeBox, "tabindex", "0"); DOM.appendChild(footer, resizeBox); + bottomTabStop = DOM.createDiv(); + DOM.setElementAttribute(bottomTabStop, "tabindex", "0"); + wrapper = DOM.createDiv(); DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap"); + DOM.appendChild(wrapper, topTabStop); 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(wrapper, bottomTabStop); DOM.appendChild(super.getContainerElement(), wrapper); sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS @@ -284,6 +386,96 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, setWidget(contentPanel); + // Make the closebox accessible for assistive devices + Roles.getButtonRole().set(closeBox); + Roles.getButtonRole().setAriaLabelProperty(closeBox, "close button"); + + // Make the maximizebox accessible for assistive devices + Roles.getButtonRole().set(maximizeRestoreBox); + Roles.getButtonRole().setAriaLabelProperty(maximizeRestoreBox, + "maximize button"); + + // Provide the title to assistive devices + AriaHelper.ensureHasId(headerText); + Roles.getDialogRole().setAriaLabelledbyProperty(getElement(), + Id.of(headerText)); + + // Handlers to Prevent tab to leave the window + topEventBlocker = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (nativeEvent.getEventTarget().cast() == topTabStop + && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB + && nativeEvent.getShiftKey()) { + nativeEvent.preventDefault(); + } + } + }; + + bottomEventBlocker = new NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (nativeEvent.getEventTarget().cast() == bottomTabStop + && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB + && !nativeEvent.getShiftKey()) { + nativeEvent.preventDefault(); + } + } + }; + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + * <p> + * This message is not visible on the screen. + * + * @param topMessage + * String provided when the user navigates with Shift-Tab keys to + * the top of the window + */ + public void setTabStopTopAssistiveText(String topMessage) { + Roles.getNoteRole().setAriaLabelProperty(topTabStop, topMessage); + } + + /** + * Sets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + * <p> + * This message is not visible on the screen. + * + * @param bottomMessage + * String provided when the user navigates with the Tab key to + * the bottom of the window + */ + public void setTabStopBottomAssistiveText(String bottomMessage) { + Roles.getNoteRole().setAriaLabelProperty(bottomTabStop, bottomMessage); + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the top of the window when leaving a window with the tab key + * is prevented. + * + * @return the top message + */ + public String getTabStopTopAssistiveText() { + return Roles.getNoteRole().getAriaLabelProperty(topTabStop); + } + + /** + * Gets the message that is provided to users of assistive devices when the + * user reaches the bottom of the window when leaving a window with the tab + * key is prevented. + * + * @return the bottom message + */ + public String getTabStopBottomAssistiveText() { + return Roles.getNoteRole().getAriaLabelProperty(bottomTabStop); } /** @@ -517,6 +709,7 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (isAttached()) { showModalityCurtain(); } + addTabBlockHandlers(); deferOrdering(); } else { if (modalityCurtain != null) { @@ -525,6 +718,9 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } modalityCurtain = null; } + if (!doTabStop) { + removeTabBlockHandlers(); + } } } @@ -682,18 +878,71 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, if (icon != null) { icon = client.translateVaadinUri(icon); html = "<img src=\"" + Util.escapeAttribute(icon) - + "\" class=\"v-icon\" />" + html; + + "\" class=\"v-icon\" alt=\"\" />" + html; } + + // Provide information to assistive device users that a sub window was + // opened + String prefix = "<span class='" + + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE + "'>" + + assistivePrefix + "</span>"; + String postfix = "<span class='" + + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE + "'>" + + assistivePostfix + "</span>"; + + html = prefix + html + postfix; DOM.setInnerHTML(headerText, html); } + /** + * Setter for the text for assistive devices the window caption is prefixed + * with. + * + * @param assistivePrefix + * the assistivePrefix to set + */ + public void setAssistivePrefix(String assistivePrefix) { + this.assistivePrefix = assistivePrefix; + } + + /** + * Getter for the text for assistive devices the window caption is prefixed + * with. + * + * @return the assistivePrefix + */ + public String getAssistivePrefix() { + return assistivePrefix; + } + + /** + * Setter for the text for assistive devices the window caption is postfixed + * with. + * + * @param assistivePostfix + * the assistivePostfix to set + */ + public void setAssistivePostfix(String assistivePostfix) { + this.assistivePostfix = assistivePostfix; + } + + /** + * Getter for the text for assistive devices the window caption is postfixed + * with. + * + * @return the assistivePostfix + */ + public String getAssistivePostfix() { + return assistivePostfix; + } + @Override - protected Element getContainerElement() { + protected com.google.gwt.user.client.Element getContainerElement() { // in GWT 1.5 this method is used in PopupPanel constructor if (contents == null) { return super.getContainerElement(); } - return contents; + return DOM.asOld(contents); } private Event headerDragPending; @@ -742,14 +991,14 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } bubble = false; } + if (type == Event.ONCLICK) { + activateOnClick(); + } } else if (dragging || !contents.isOrHasChild(target)) { onDragEvent(event); bubble = false; } else if (type == Event.ONCLICK) { - // clicked inside window, ensure to be on top - if (!isActive()) { - bringToFront(); - } + activateOnClick(); } /* @@ -771,6 +1020,13 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } } + private void activateOnClick() { + // clicked inside window or inside header, ensure to be on top + if (!isActive()) { + bringToFront(); + } + } + private void onCloseClick() { client.updateVariable(id, "close", true, true); } @@ -1068,6 +1324,13 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } @Override + public void onKeyUp(KeyUpEvent event) { + if (isClosable() && event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + onCloseClick(); + } + } + + @Override public void onBlur(BlurEvent event) { hasFocus = false; @@ -1108,9 +1371,104 @@ public class VWindow extends VOverlay implements ShortcutActionHandlerOwner, } /** + * Allows to specify which connectors contain the description for the + * window. Text contained in the widgets of the connectors will be read by + * assistive devices when it is opened. + * <p> + * When the provided array is empty, an existing description is removed. + * + * @param connectors + * with the connectors of the widgets to use as description + */ + public void setAssistiveDescription(Connector[] connectors) { + if (connectors != null) { + assistiveConnectors = connectors; + + if (connectors.length == 0) { + Roles.getDialogRole().removeAriaDescribedbyProperty( + getElement()); + } else { + Id[] ids = new Id[connectors.length]; + for (int index = 0; index < connectors.length; index++) { + if (connectors[index] == null) { + throw new IllegalArgumentException( + "All values in parameter description need to be non-null"); + } + + Element element = ((ComponentConnector) connectors[index]) + .getWidget().getElement(); + AriaHelper.ensureHasId(element); + ids[index] = Id.of(element); + } + + Roles.getDialogRole().setAriaDescribedbyProperty(getElement(), + ids); + } + } else { + throw new IllegalArgumentException( + "Parameter description must be non-null"); + } + } + + /** + * Gets the connectors that are used as assistive description. Text + * contained in these connectors will be read by assistive devices when the + * window is opened. + * + * @return list of previously set connectors + */ + public List<Connector> getAssistiveDescription() { + return Collections.unmodifiableList(Arrays.asList(assistiveConnectors)); + } + + /** + * Sets the WAI-ARIA role the window. + * + * This role defines how an assistive device handles a window. Available + * roles are alertdialog and dialog (@see <a + * href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles + * Model</a>). + * + * The default role is dialog. + * + * @param role + * WAI-ARIA role to set for the window + */ + public void setWaiAriaRole(WindowRole role) { + if ("alertdialog".equals(role)) { + Roles.getAlertdialogRole().set(getElement()); + } else { + Roles.getDialogRole().set(getElement()); + } + } + + /** + * Registers the handlers that prevent to leave the window using the + * Tab-key. + * <p> + * The value of the parameter doTabStop is stored and used for non-modal + * windows. For modal windows, the handlers are always registered, while + * preserving the stored value. + * + * @param doTabStop + * true to prevent leaving the window, false to allow leaving the + * window for non modal windows + */ + public void setTabStopEnabled(boolean doTabStop) { + this.doTabStop = doTabStop; + + if (doTabStop || vaadinModality) { + addTabBlockHandlers(); + } else { + removeTabBlockHandlers(); + } + } + + /** * Adds a Handler for when user moves the window. * * @since 7.1.9 + * * @return {@link HandlerRegistration} used to remove the handler */ public HandlerRegistration addMoveHandler(WindowMoveHandler handler) { diff --git a/client/src/com/vaadin/client/ui/VWindowOverlay.java b/client/src/com/vaadin/client/ui/VWindowOverlay.java new file mode 100644 index 0000000000..6558ab14fa --- /dev/null +++ b/client/src/com/vaadin/client/ui/VWindowOverlay.java @@ -0,0 +1,78 @@ +/* + * 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 com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.ui.RootPanel; +import com.vaadin.client.ApplicationConnection; + +public class VWindowOverlay extends VOverlay { + public VWindowOverlay() { + } + + public VWindowOverlay(boolean autoHide, boolean modal, boolean showShadow) { + super(autoHide, modal, showShadow); + } + + /** + * Gets the 'overlay container' element. Tries to find the current + * {@link ApplicationConnection} using {@link #getApplicationConnection()}. + * + * @return the overlay container element for the current + * {@link ApplicationConnection} or another element if the current + * {@link ApplicationConnection} cannot be determined. + */ + @Override + public com.google.gwt.user.client.Element getOverlayContainer() { + ApplicationConnection ac = getApplicationConnection(); + if (ac == null) { + return super.getOverlayContainer(); + } else { + Element overlayContainer = getOverlayContainer(ac); + return DOM.asOld(overlayContainer); + } + } + + /** + * Gets the 'overlay container' element pertaining to the given + * {@link ApplicationConnection}. Each overlay should be created in a + * overlay container element, so that the correct theme and styles can be + * applied. + * + * @param ac + * A reference to {@link ApplicationConnection} + * @return The overlay container + */ + public static com.google.gwt.user.client.Element getOverlayContainer( + ApplicationConnection ac) { + String id = ac.getConfiguration().getRootPanelId(); + id = id += "-window-overlays"; + Element container = DOM.getElementById(id); + if (container == null) { + container = DOM.createDiv(); + container.setId(id); + String styles = ac.getUIConnector().getWidget().getParent() + .getStyleName(); + container.addClassName(styles); + container.addClassName(CLASSNAME_CONTAINER); + RootPanel.get().getElement().appendChild(container); + } + + return DOM.asOld(container); + } +} diff --git a/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java b/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java index da79639dcd..6a6a1429f8 100644 --- a/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/absolutelayout/AbsoluteLayoutConnector.java @@ -17,7 +17,8 @@ package com.vaadin.client.ui.absolutelayout; import java.util.List; -import com.google.gwt.user.client.Element; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.DOM; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.DirectionalManagedLayout; @@ -46,14 +47,15 @@ public class AbsoluteLayoutConnector extends this) { @Override - protected ComponentConnector getChildComponent(Element element) { + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { return getConnectorForElement(element); } @Override protected LayoutClickRpc getLayoutClickRPC() { return getRpcProxy(AbsoluteLayoutServerRpc.class); - }; + } }; private StateChangeHandler childStateChangeHandler = new StateChangeHandler() { @@ -91,12 +93,32 @@ public class AbsoluteLayoutConnector extends * this layout * @return The Paintable which the element is a part of. Null if the element * belongs to the layout and not to a child. + * @deprecated As of 7.2, call or override + * {@link #getConnectorForElement(Element)} instead */ - protected ComponentConnector getConnectorForElement(Element element) { + @Deprecated + protected ComponentConnector getConnectorForElement( + com.google.gwt.user.client.Element element) { return Util.getConnectorForElement(getConnection(), getWidget(), element); } + /** + * Returns the deepest nested child component which contains "element". The + * child component is also returned if "element" is part of its caption. + * + * @param element + * An element that is a nested sub element of the root element in + * this layout + * @return The Paintable which the element is a part of. Null if the element + * belongs to the layout and not to a child. + * + * @since 7.2 + */ + protected ComponentConnector getConnectorForElement(Element element) { + return getConnectorForElement(DOM.asOld(element)); + } + /* * (non-Javadoc) * @@ -163,7 +185,7 @@ public class AbsoluteLayoutConnector extends private void setChildWidgetPosition(ComponentConnector child) { getWidget().setWidgetPosition(child.getWidget(), getState().connectorToCssPosition.get(child.getConnectorId())); - }; + } /* * (non-Javadoc) diff --git a/client/src/com/vaadin/client/ui/accordion/AccordionConnector.java b/client/src/com/vaadin/client/ui/accordion/AccordionConnector.java index 99fbd07f9b..ce843dc22f 100644 --- a/client/src/com/vaadin/client/ui/accordion/AccordionConnector.java +++ b/client/src/com/vaadin/client/ui/accordion/AccordionConnector.java @@ -15,19 +15,17 @@ */ package com.vaadin.client.ui.accordion; -import java.util.Iterator; - -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.UIDL; import com.vaadin.client.Util; +import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VAccordion; import com.vaadin.client.ui.VAccordion.StackItem; import com.vaadin.client.ui.layout.MayScrollChildren; import com.vaadin.client.ui.tabsheet.TabsheetBaseConnector; import com.vaadin.shared.ui.Connect; +import com.vaadin.shared.ui.accordion.AccordionState; import com.vaadin.ui.Accordion; @Connect(Accordion.class) @@ -35,35 +33,32 @@ public class AccordionConnector extends TabsheetBaseConnector implements SimpleManagedLayout, MayScrollChildren { @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().selectedUIDLItemIndex = -1; - super.updateFromUIDL(uidl, client); + protected void init() { + super.init(); + getWidget().setConnector(this); + } + + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + /* * Render content after all tabs have been created and we know how large * the content area is */ - if (getWidget().selectedUIDLItemIndex >= 0) { + if (getWidget().selectedItemIndex >= 0) { StackItem selectedItem = getWidget().getStackItem( - getWidget().selectedUIDLItemIndex); - UIDL selectedTabUIDL = getWidget().lazyUpdateMap - .remove(selectedItem); - getWidget().open(getWidget().selectedUIDLItemIndex); + getWidget().selectedItemIndex); - selectedItem.setContent(selectedTabUIDL); - } else if (isRealUpdate(uidl) && getWidget().getOpenStackItem() != null) { - getWidget().close(getWidget().getOpenStackItem()); - } + getWidget().open(getWidget().selectedItemIndex); - // finally render possible hidden tabs - if (getWidget().lazyUpdateMap.size() > 0) { - for (Iterator iterator = getWidget().lazyUpdateMap.keySet() - .iterator(); iterator.hasNext();) { - StackItem item = (StackItem) iterator.next(); - item.setContent(getWidget().lazyUpdateMap.get(item)); + ComponentConnector contentConnector = getChildComponents().get(0); + if (contentConnector != null) { + selectedItem.setContent(contentConnector.getWidget()); } - getWidget().lazyUpdateMap.clear(); + } else if (getWidget().getOpenStackItem() != null) { + getWidget().close(getWidget().getOpenStackItem()); } - } @Override @@ -123,14 +118,24 @@ public class AccordionConnector extends TabsheetBaseConnector implements openTab.setHeight(spaceForOpenItem); } else { openTab.setHeightFromWidget(); - } - } + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ConnectorHierarchyChangeEvent. + * ConnectorHierarchyChangeHandler + * #onConnectorHierarchyChange(com.vaadin.client + * .ConnectorHierarchyChangeEvent) + */ @Override public void onConnectorHierarchyChange( ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { - // TODO Move code from updateFromUIDL to this method + } + + @Override + public AccordionState getState() { + return (AccordionState) super.getState(); } } diff --git a/client/src/com/vaadin/client/ui/aria/AriaHelper.java b/client/src/com/vaadin/client/ui/aria/AriaHelper.java index 0ff58cf510..b1f51b85e9 100644 --- a/client/src/com/vaadin/client/ui/aria/AriaHelper.java +++ b/client/src/com/vaadin/client/ui/aria/AriaHelper.java @@ -19,8 +19,8 @@ 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.dom.client.Element; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; /** @@ -47,7 +47,8 @@ public class AriaHelper { ((HandlesAriaCaption) widget).bindAriaCaption(null); } else { ensureHasId(captionElement); - ((HandlesAriaCaption) widget).bindAriaCaption(captionElement); + ((HandlesAriaCaption) widget).bindAriaCaption(DOM + .asOld(captionElement)); } } else if (captionElement != null) { // Handle the default case diff --git a/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java b/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java index 50f83fdede..e754f2d095 100644 --- a/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java +++ b/client/src/com/vaadin/client/ui/aria/HandlesAriaCaption.java @@ -16,8 +16,6 @@ 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 @@ -35,5 +33,5 @@ public interface HandlesAriaCaption { * @param captionElement * Element of the caption */ - void bindAriaCaption(Element captionElement); + void bindAriaCaption(com.google.gwt.user.client.Element captionElement); } diff --git a/client/src/com/vaadin/client/ui/button/ButtonConnector.java b/client/src/com/vaadin/client/ui/button/ButtonConnector.java index a6630f28b9..32a457c1f1 100644 --- a/client/src/com/vaadin/client/ui/button/ButtonConnector.java +++ b/client/src/com/vaadin/client/ui/button/ButtonConnector.java @@ -26,8 +26,8 @@ import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.DOM; import com.vaadin.client.EventHelper; import com.vaadin.client.MouseEventDetailsBuilder; +import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VButton; @@ -56,48 +56,38 @@ public class ButtonConnector extends AbstractComponentConnector implements super.init(); getWidget().addClickHandler(this); getWidget().client = getConnection(); - addStateChangeHandler("errorMessage", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - if (null != getState().errorMessage) { - if (getWidget().errorIndicatorElement == null) { - getWidget().errorIndicatorElement = DOM.createSpan(); - getWidget().errorIndicatorElement - .setClassName("v-errorindicator"); - } - getWidget().wrapper.insertBefore( - getWidget().errorIndicatorElement, - getWidget().captionElement); - - } else if (getWidget().errorIndicatorElement != null) { - getWidget().wrapper - .removeChild(getWidget().errorIndicatorElement); - getWidget().errorIndicatorElement = null; - } - } - }); - - addStateChangeHandler("resources", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - if (getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - getWidget().wrapper.insertBefore( - getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getIcon()); - getWidget().icon.setAlternateText(getState().iconAltText); - } else { - if (getWidget().icon != null) { - getWidget().wrapper.removeChild(getWidget().icon - .getElement()); - getWidget().icon = null; - } - } + } + + @OnStateChange("errorMessage") + void setErrorMessage() { + if (null != getState().errorMessage) { + if (getWidget().errorIndicatorElement == null) { + getWidget().errorIndicatorElement = DOM.createSpan(); + getWidget().errorIndicatorElement + .setClassName("v-errorindicator"); } - }); + getWidget().wrapper.insertBefore(getWidget().errorIndicatorElement, + getWidget().captionElement); + + } else if (getWidget().errorIndicatorElement != null) { + getWidget().wrapper.removeChild(getWidget().errorIndicatorElement); + getWidget().errorIndicatorElement = null; + } + } + + @OnStateChange("resources") + void onResourceChange() { + if (getWidget().icon != null) { + getWidget().wrapper.removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + icon.setAlternateText(getState().iconAltText); + getWidget().wrapper.insertBefore(icon.getElement(), + getWidget().captionElement); + } } @Override @@ -107,22 +97,27 @@ public class ButtonConnector extends AbstractComponentConnector implements focusHandlerRegistration); blurHandlerRegistration = EventHelper.updateBlurHandler(this, blurHandlerRegistration); + } - if (stateChangeEvent.hasPropertyChanged("caption") - || stateChangeEvent.hasPropertyChanged("htmlContentAllowed")) { - // Set text - if (getState().htmlContentAllowed) { - getWidget().setHtml(getState().caption); - } else { - getWidget().setText(getState().caption); - } + @OnStateChange({ "caption", "htmlContentAllowed" }) + void setCaption() { + String caption = getState().caption; + if (getState().htmlContentAllowed) { + getWidget().setHtml(caption); + } else { + getWidget().setText(caption); } + } - if (getWidget().icon != null - && stateChangeEvent.hasPropertyChanged("iconAltText")) { + @OnStateChange("iconAltText") + void setIconAltText() { + if (getWidget().icon != null) { getWidget().icon.setAlternateText(getState().iconAltText); } + } + @OnStateChange("clickShortcutKeyCode") + void setClickShortcut() { getWidget().clickShortcut = getState().clickShortcutKeyCode; } diff --git a/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java index 49cd2386ac..89f923d483 100644 --- a/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java +++ b/client/src/com/vaadin/client/ui/calendar/CalendarConnector.java @@ -24,11 +24,11 @@ import java.util.Iterator; import java.util.List; import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.Element; 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; @@ -420,7 +420,7 @@ public class CalendarConnector extends AbstractComponentConnector implements @Override public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) { TooltipInfo tooltipInfo = null; - Widget w = Util.findWidget((Element) element, null); + Widget w = Util.findWidget(element, null); if (w instanceof HasTooltipKey) { tooltipInfo = GWT.create(TooltipInfo.class); String title = tooltips.get(((HasTooltipKey) w).getTooltipKey()); diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java index 516447153e..c3fd2b54cf 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCell.java @@ -342,8 +342,7 @@ public class DateCell extends FocusableComplexPanel implements } public int getSlotBorder() { - return Util - .measureVerticalBorder((com.google.gwt.user.client.Element) slotElements[0]); + return Util.measureVerticalBorder(slotElements[0]); } private void drawDayEvents(List<DateCellGroup> groups) { @@ -504,7 +503,7 @@ public class DateCell extends FocusableComplexPanel implements updatePositionFor(dayEvent, targetDay, calendarEvent); } - add(dayEvent, (com.google.gwt.user.client.Element) main); + add(dayEvent, main); } // date methods are not deprecated in GWT @@ -561,8 +560,7 @@ public class DateCell extends FocusableComplexPanel implements } index++; } - this.insert(dayEvent, (com.google.gwt.user.client.Element) main, index, - true); + this.insert(dayEvent, main, index, true); } public void removeEvent(DateCellDayEvent dayEvent) { @@ -784,11 +782,28 @@ public class DateCell extends FocusableComplexPanel implements return today != null; } + /** + * @deprecated As of 7.2, call or override + * {@link #addEmphasisStyle(Element)} instead + */ + @Deprecated public void addEmphasisStyle(com.google.gwt.user.client.Element elementOver) { String originalStylename = getStyleName(elementOver); setStyleName(elementOver, originalStylename + DRAGEMPHASISSTYLE); } + /** + * @since 7.2 + */ + public void addEmphasisStyle(Element elementOver) { + addEmphasisStyle(DOM.asOld(elementOver)); + } + + /** + * @deprecated As of 7.2, call or override + * {@link #removeEmphasisStyle(Element)} instead + */ + @Deprecated public void removeEmphasisStyle( com.google.gwt.user.client.Element elementOver) { String originalStylename = getStyleName(elementOver); @@ -798,6 +813,13 @@ public class DateCell extends FocusableComplexPanel implements - DRAGEMPHASISSTYLE.length())); } + /** + * @since 7.2 + */ + public void removeEmphasisStyle(Element elementOver) { + removeEmphasisStyle(DOM.asOld(elementOver)); + } + @Override public void onContextMenu(ContextMenuEvent event) { if (weekgrid.getCalendar().getMouseEventListener() != null) { diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java index 39de122694..ae86833952 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/DateCellDayEvent.java @@ -42,7 +42,6 @@ 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; /** @@ -71,8 +70,8 @@ public class DateCellDayEvent extends FocusableHTML implements 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 topResizeBar; + private Element bottomResizeBar; private Element clickTarget; private final Integer eventIndex; private int slotHeight; @@ -105,7 +104,6 @@ public class DateCellDayEvent extends FocusableHTML implements eventContent.addClassName("v-calendar-event-content"); getElement().appendChild(eventContent); - VCalendar calendar = weekGrid.getCalendar(); if (weekGrid.getCalendar().isEventResizeAllowed()) { topResizeBar = DOM.createDiv(); bottomResizeBar = DOM.createDiv(); @@ -189,9 +187,11 @@ public class DateCellDayEvent extends FocusableHTML implements String escapedCaption = Util.escapeHTML(calendarEvent.getCaption()); String timeAsText = calendarEvent.getTimeAsText(); if (bigMode) { - innerHtml = "<span>" + timeAsText + "</span><br />" + escapedCaption; + innerHtml = "<span>" + timeAsText + "</span><br />" + + escapedCaption; } else { - innerHtml = "<span>" + timeAsText + "<span>:</span></span> " + escapedCaption; + innerHtml = "<span>" + timeAsText + "<span>:</span></span> " + + escapedCaption; } caption.setInnerHTML(innerHtml); eventContent.setInnerHTML(""); diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java index 6233e8111e..58b5fafa7f 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/DayToolbar.java @@ -72,8 +72,6 @@ public class DayToolbar extends HorizontalPanel implements ClickHandler { 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); diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java index df9bc42d2a..3b1c774793 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/MonthGrid.java @@ -35,7 +35,6 @@ public class MonthGrid extends FocusableGrid implements KeyDownHandler { private SimpleDayCell selectionEnd; private final VCalendar calendar; private boolean rangeSelectDisabled; - private boolean disabled; private boolean enabled = true; private final HandlerRegistration keyDownHandler; diff --git a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java index cf8006ef66..00fc1ef3ea 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/SimpleDayCell.java @@ -83,7 +83,6 @@ public class SimpleDayCell extends FocusableFlowPanel implements private Widget clickedWidget; private HandlerRegistration bottomSpacerMouseDownHandler; private boolean scrollable = false; - private boolean eventCanceled; private MonthGrid monthGrid; private HandlerRegistration keyDownHandler; 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 index fd0be4881e..7c0c541ee3 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarMonthDropHandler.java @@ -15,8 +15,8 @@ */ package com.vaadin.client.ui.calendar.schedule.dd; +import com.google.gwt.dom.client.Element; 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.CalendarConnector; import com.vaadin.client.ui.calendar.schedule.SimpleDayCell; @@ -101,10 +101,9 @@ public class CalendarMonthDropHandler extends CalendarDropHandler { * 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(); + private boolean isLocationValid(Element elementOver) { + Element monthGridElement = calendarConnector.getWidget().getMonthGrid() + .getElement(); // drops are not allowed in: // - weekday header 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 index cede1827a2..d19dcfedc4 100644 --- a/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java +++ b/client/src/com/vaadin/client/ui/calendar/schedule/dd/CalendarWeekDropHandler.java @@ -15,8 +15,8 @@ */ package com.vaadin.client.ui.calendar.schedule.dd; +import com.google.gwt.dom.client.Element; 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.CalendarConnector; import com.vaadin.client.ui.calendar.schedule.DateCell; @@ -34,7 +34,7 @@ import com.vaadin.client.ui.dd.VDragEvent; */ public class CalendarWeekDropHandler extends CalendarDropHandler { - private com.google.gwt.user.client.Element currentTargetElement; + private Element currentTargetElement; private DateCell currentTargetDay; public CalendarWeekDropHandler(CalendarConnector connector) { @@ -100,17 +100,16 @@ public class CalendarWeekDropHandler extends CalendarDropHandler { * 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; + private boolean isLocationValid(Element elementOver) { + Element weekGridElement = calendarConnector.getWidget().getWeekGrid() + .getElement(); + Element timeBarElement = calendarConnector.getWidget().getWeekGrid() + .getTimeBar().getElement(); + + Element todayBarElement = null; if (calendarConnector.getWidget().getWeekGrid().hasToday()) { - todayBarElement = (Element) calendarConnector.getWidget() - .getWeekGrid().getDateCellOfToday().getTodaybarElement(); + todayBarElement = calendarConnector.getWidget().getWeekGrid() + .getDateCellOfToday().getTodaybarElement(); } // drops are not allowed in: diff --git a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java index 85e4e5ee8b..b40e96ff95 100644 --- a/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java +++ b/client/src/com/vaadin/client/ui/checkbox/CheckBoxConnector.java @@ -30,6 +30,7 @@ import com.vaadin.client.VTooltip; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; import com.vaadin.client.ui.VCheckBox; import com.vaadin.shared.MouseEventDetails; import com.vaadin.shared.communication.FieldRpc.FocusAndBlurServerRpc; @@ -96,21 +97,17 @@ public class CheckBoxConnector extends AbstractFieldConnector implements getWidget().setEnabled(false); } - if (getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - DOM.insertChild(getWidget().getElement(), - getWidget().icon.getElement(), 1); - getWidget().icon.sinkEvents(VTooltip.TOOLTIP_EVENTS); - getWidget().icon.sinkEvents(Event.ONCLICK); - } - getWidget().icon.setUri(getIcon()); - } else if (getWidget().icon != null) { - // detach icon - DOM.removeChild(getWidget().getElement(), - getWidget().icon.getElement()); + if (getWidget().icon != null) { + getWidget().getElement().removeChild(getWidget().icon.getElement()); getWidget().icon = null; } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + DOM.insertChild(getWidget().getElement(), icon.getElement(), 1); + icon.sinkEvents(VTooltip.TOOLTIP_EVENTS); + icon.sinkEvents(Event.ONCLICK); + } // Set text getWidget().setText(getState().caption); diff --git a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java index dbfbf43eb6..3a135e2381 100644 --- a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java +++ b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGradient.java @@ -22,7 +22,6 @@ 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.user.client.Element; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.HTML; @@ -191,7 +190,7 @@ public class VColorPickerGradient extends FocusPanel implements } @Override - public Element getSubPartElement(String subPart) { + public com.google.gwt.user.client.Element getSubPartElement(String subPart) { if (subPart.equals(CLICKLAYER_ID)) { return clicklayer.getElement(); } @@ -200,7 +199,7 @@ public class VColorPickerGradient extends FocusPanel implements } @Override - public String getSubPartName(Element subElement) { + public String getSubPartName(com.google.gwt.user.client.Element subElement) { if (clicklayer.getElement().isOrHasChild(subElement)) { return CLICKLAYER_ID; } diff --git a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java index 9f4b0e0d76..8c9c34d668 100644 --- a/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java +++ b/client/src/com/vaadin/client/ui/colorpicker/VColorPickerGrid.java @@ -15,11 +15,11 @@ */ package com.vaadin.client.ui.colorpicker; +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.event.dom.client.HasClickHandlers; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.HTMLTable.Cell; diff --git a/client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java b/client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java index 45e52c890e..034fd79364 100644 --- a/client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/csslayout/CssLayoutConnector.java @@ -16,7 +16,6 @@ package com.vaadin.client.ui.csslayout; import com.google.gwt.dom.client.Style; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; @@ -46,7 +45,8 @@ public class CssLayoutConnector extends AbstractLayoutConnector { this) { @Override - protected ComponentConnector getChildComponent(Element element) { + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { return Util.getConnectorForElement(getConnection(), getWidget(), element); } @@ -54,7 +54,7 @@ public class CssLayoutConnector extends AbstractLayoutConnector { @Override protected LayoutClickRpc getLayoutClickRPC() { return getRpcProxy(CssLayoutServerRpc.class); - }; + } }; private final FastStringMap<VCaption> childIdToCaption = FastStringMap diff --git a/client/src/com/vaadin/client/ui/dd/DDUtil.java b/client/src/com/vaadin/client/ui/dd/DDUtil.java index c8a0621d74..96f681b54a 100644 --- a/client/src/com/vaadin/client/ui/dd/DDUtil.java +++ b/client/src/com/vaadin/client/ui/dd/DDUtil.java @@ -15,8 +15,8 @@ */ package com.vaadin.client.ui.dd; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Window; import com.vaadin.client.Util; import com.vaadin.shared.ui.dd.HorizontalDropLocation; @@ -58,7 +58,8 @@ public class DDUtil { } public static HorizontalDropLocation getHorizontalDropLocation( - Element element, NativeEvent event, double leftRightRatio) { + Element element, NativeEvent event, + double leftRightRatio) { int clientX = Util.getTouchOrMouseClientX(event); // Event coordinates are relative to the viewport, element absolute diff --git a/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java b/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java index b911c28a07..f44fceb398 100644 --- a/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java +++ b/client/src/com/vaadin/client/ui/dd/VDragAndDropManager.java @@ -144,13 +144,11 @@ public class VDragAndDropManager { } if (currentDropHandler != null) { - currentDrag - .setElementOver((com.google.gwt.user.client.Element) targetElement); + currentDrag.setElementOver(targetElement); currentDropHandler.dragEnter(currentDrag); } } else if (findDragTarget != null) { - currentDrag - .setElementOver((com.google.gwt.user.client.Element) targetElement); + currentDrag.setElementOver(targetElement); currentDropHandler.dragOver(currentDrag); } // prevent text selection on IE @@ -162,8 +160,7 @@ public class VDragAndDropManager { // ApplicationConnection.getConsole().log( // "Target just modified on " // + event.getType()); - currentDrag - .setElementOver((com.google.gwt.user.client.Element) targetElement); + currentDrag.setElementOver(targetElement); break; } @@ -191,8 +188,7 @@ public class VDragAndDropManager { // ApplicationConnection.getConsole().log( // "DropHandler now" // + currentDropHandler.getPaintable()); - currentDrag - .setElementOver((com.google.gwt.user.client.Element) targetElement); + currentDrag.setElementOver(targetElement); target.dragEnter(currentDrag); } else if (target == null && currentDropHandler != null) { // ApplicationConnection.getConsole().log("Invalid state!?"); @@ -223,8 +219,7 @@ public class VDragAndDropManager { case Event.ONMOUSEMOVE: case Event.ONTOUCHMOVE: if (currentDropHandler != null) { - currentDrag - .setElementOver((com.google.gwt.user.client.Element) targetElement); + currentDrag.setElementOver(targetElement); currentDropHandler.dragOver(currentDrag); } nativeEvent.preventDefault(); @@ -473,8 +468,7 @@ public class VDragAndDropManager { */ private VDropHandler findDragTarget(Element element) { try { - Widget w = Util.findWidget( - (com.google.gwt.user.client.Element) element, null); + Widget w = Util.findWidget(element, null); if (w == null) { return null; } diff --git a/client/src/com/vaadin/client/ui/dd/VDragEvent.java b/client/src/com/vaadin/client/ui/dd/VDragEvent.java index a4667e57f3..9b592cfcbd 100644 --- a/client/src/com/vaadin/client/ui/dd/VDragEvent.java +++ b/client/src/com/vaadin/client/ui/dd/VDragEvent.java @@ -19,12 +19,13 @@ import java.util.HashMap; import java.util.Map; 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.dom.client.Style.Unit; import com.google.gwt.dom.client.TableElement; import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.event.dom.client.MouseOverEvent; -import com.google.gwt.user.client.Element; +import com.google.gwt.user.client.DOM; import com.vaadin.client.BrowserInfo; import com.vaadin.client.Util; @@ -94,20 +95,32 @@ public class VDragEvent { * * @return the element in {@link VDropHandler} on which mouse cursor is on */ - public Element getElementOver() { + public com.google.gwt.user.client.Element getElementOver() { if (elementOver != null) { - return elementOver; + return DOM.asOld(elementOver); } else if (currentGwtEvent != null) { return currentGwtEvent.getEventTarget().cast(); } return null; } - public void setElementOver(Element targetElement) { + /** + * @deprecated As of 7.2, call or override {@link #setElementOver(Element)} + * instead + */ + @Deprecated + public void setElementOver(com.google.gwt.user.client.Element targetElement) { elementOver = targetElement; } /** + * @since 7.2 + */ + public void setElementOver(Element targetElement) { + setElementOver(DOM.asOld(targetElement)); + } + + /** * Sets the drag image used for current drag and drop operation. Drag image * is displayed next to mouse cursor during drag and drop. * <p> @@ -121,12 +134,36 @@ public class VDragEvent { * to HTML5 DataTransfer * * @param node + * @deprecated As of 7.2, call or override {@link #setDragImage(Element)} + * instead */ - public void setDragImage(Element node) { + @Deprecated + public void setDragImage(com.google.gwt.user.client.Element node) { setDragImage(node, DEFAULT_OFFSET, DEFAULT_OFFSET); } /** + * Sets the drag image used for current drag and drop operation. Drag image + * is displayed next to mouse cursor during drag and drop. + * <p> + * The element to be used as drag image will automatically get CSS style + * name "v-drag-element". + * + * TODO decide if this method should be here or in {@link VTransferable} (in + * HTML5 it is in DataTransfer) or {@link VDragAndDropManager} + * + * TODO should be possible to override behavior. Like to proxy the element + * to HTML5 DataTransfer + * + * @param node + * + * @since 7.2 + */ + public void setDragImage(Element node) { + setDragImage(DOM.asOld(node)); + } + + /** * TODO consider using similar smaller (than map) api as in Transferable * * TODO clean up when drop handler changes @@ -150,19 +187,44 @@ public class VDragEvent { * the horizontal offset of drag image from mouse cursor * @param offsetY * the vertical offset of drag image from mouse cursor + * @deprecated As of 7.2, call or override + * {@link #setDragImage(Element,int,int)} instead */ - public void setDragImage(Element element, int offsetX, int offsetY) { + @Deprecated + public void setDragImage(com.google.gwt.user.client.Element element, + int offsetX, int offsetY) { element.getStyle().setMarginLeft(offsetX, Unit.PX); element.getStyle().setMarginTop(offsetY, Unit.PX); VDragAndDropManager.get().setDragElement(element); + + } + + /** + * Sets the drag image used for current drag and drop operation. Drag image + * is displayed next to mouse cursor during drag and drop. + * <p> + * The element to be used as drag image will automatically get CSS style + * name "v-drag-element". + * + * @param element + * the dom element to be positioned next to mouse cursor + * @param offsetX + * the horizontal offset of drag image from mouse cursor + * @param offsetY + * the vertical offset of drag image from mouse cursor + * + * @since 7.2 + */ + public void setDragImage(Element element, int offsetX, int offsetY) { + setDragImage(DOM.asOld(element), offsetX, offsetY); } /** * @return the current Element used as a drag image (aka drag proxy) or null * if drag image is not currently set for this drag operation. */ - public Element getDragImage() { - return (Element) VDragAndDropManager.get().getDragElement(); + public com.google.gwt.user.client.Element getDragImage() { + return DOM.asOld(VDragAndDropManager.get().getDragElement()); } /** @@ -172,8 +234,12 @@ public class VDragEvent { * @param alignImageToEvent * if true, proxy image is aligned to start event, else next to * mouse cursor + * @deprecated As of 7.2, call or override + * {@link #createDragImage(Element,boolean)} instead */ - public void createDragImage(Element element, boolean alignImageToEvent) { + @Deprecated + public void createDragImage(com.google.gwt.user.client.Element element, + boolean alignImageToEvent) { Element cloneNode = (Element) element.cloneNode(true); if (BrowserInfo.get().isIE()) { if (cloneNode.getTagName().toLowerCase().equals("tr")) { @@ -198,4 +264,17 @@ public class VDragEvent { } + /** + * Automatically tries to create a proxy image from given element. + * + * @param element + * @param alignImageToEvent + * if true, proxy image is aligned to start event, else next to + * mouse cursor + * @since 7.2 + */ + public void createDragImage(Element element, boolean alignImageToEvent) { + createDragImage(DOM.asOld(element), alignImageToEvent); + } + } diff --git a/client/src/com/vaadin/client/ui/dd/VHtml5File.java b/client/src/com/vaadin/client/ui/dd/VHtml5File.java index 4b36e7fd1b..ef74439063 100644 --- a/client/src/com/vaadin/client/ui/dd/VHtml5File.java +++ b/client/src/com/vaadin/client/ui/dd/VHtml5File.java @@ -23,7 +23,7 @@ import com.google.gwt.core.client.JavaScriptObject; public class VHtml5File extends JavaScriptObject { protected VHtml5File() { - }; + } public native final String getName() /*-{ @@ -35,7 +35,13 @@ public class VHtml5File extends JavaScriptObject { return this.type; }-*/; - public native final int getSize() + /* + * Browser implementations support files >2GB dropped and report the value + * as long. Due to JSNI limitations this value needs to be sent as double + * and then cast back to a long value. + * www.gwtproject.org/doc/latest/DevGuideCodingBasicsJSNI.html#important + */ + public native final double getSize() /*-{ return this.size ? this.size : 0; }-*/; diff --git a/client/src/com/vaadin/client/ui/dd/VIsOverId.java b/client/src/com/vaadin/client/ui/dd/VIsOverId.java index f8083f8b60..7e2f596a20 100644 --- a/client/src/com/vaadin/client/ui/dd/VIsOverId.java +++ b/client/src/com/vaadin/client/ui/dd/VIsOverId.java @@ -19,7 +19,6 @@ package com.vaadin.client.ui.dd; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.ConnectorMap; import com.vaadin.client.UIDL; import com.vaadin.shared.ui.dd.AcceptCriterion; import com.vaadin.ui.AbstractSelect; @@ -36,8 +35,6 @@ final public class VIsOverId extends VAcceptCriterion { .getCurrentDropHandler(); ComponentConnector dropHandlerConnector = currentDropHandler .getConnector(); - ConnectorMap paintableMap = ConnectorMap.get(currentDropHandler - .getApplicationConnection()); String pid2 = dropHandlerConnector.getConnectorId(); if (pid2.equals(pid)) { diff --git a/client/src/com/vaadin/client/ui/dd/VItemIdIs.java b/client/src/com/vaadin/client/ui/dd/VItemIdIs.java index 7d60eda4f9..b022f434f4 100644 --- a/client/src/com/vaadin/client/ui/dd/VItemIdIs.java +++ b/client/src/com/vaadin/client/ui/dd/VItemIdIs.java @@ -32,8 +32,6 @@ final public class VItemIdIs extends VAcceptCriterion { String pid = configuration.getStringAttribute("s"); ComponentConnector dragSource = drag.getTransferable() .getDragSource(); - VDropHandler currentDropHandler = VDragAndDropManager.get() - .getCurrentDropHandler(); String pid2 = dragSource.getConnectorId(); if (pid2.equals(pid)) { Object searchedId = drag.getTransferable().getData("itemId"); diff --git a/client/src/com/vaadin/client/ui/embedded/EmbeddedConnector.java b/client/src/com/vaadin/client/ui/embedded/EmbeddedConnector.java index c6e4883fa9..c6e9e774ee 100644 --- a/client/src/com/vaadin/client/ui/embedded/EmbeddedConnector.java +++ b/client/src/com/vaadin/client/ui/embedded/EmbeddedConnector.java @@ -19,13 +19,13 @@ package com.vaadin.client.ui.embedded; import java.util.Map; 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.dom.client.Node; import com.google.gwt.dom.client.NodeList; import com.google.gwt.dom.client.ObjectElement; import com.google.gwt.dom.client.Style; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Event; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.Paintable; diff --git a/client/src/com/vaadin/client/ui/form/FormConnector.java b/client/src/com/vaadin/client/ui/form/FormConnector.java index acd0e917fc..f5256adbb2 100644 --- a/client/src/com/vaadin/client/ui/form/FormConnector.java +++ b/client/src/com/vaadin/client/ui/form/FormConnector.java @@ -27,7 +27,6 @@ import com.vaadin.client.Paintable; import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; import com.vaadin.client.ui.AbstractComponentContainerConnector; -import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.ShortcutActionHandler; import com.vaadin.client.ui.VForm; import com.vaadin.client.ui.layout.ElementResizeEvent; @@ -74,7 +73,7 @@ public class FormConnector extends AbstractComponentContainerConnector protected void init() { getLayoutManager().addElementResizeListener( getWidget().errorMessage.getElement(), footerResizeListener); - }; + } @Override public void onUnregister() { @@ -108,17 +107,14 @@ public class FormConnector extends AbstractComponentContainerConnector } else { getWidget().caption.setInnerText(""); } - if (getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(client); - getWidget().legend.insertFirst(getWidget().icon.getElement()); - } - getWidget().icon.setUri(getIcon()); + if (getWidget().icon != null) { + getWidget().legend.removeChild(getWidget().icon.getElement()); + } + if (getIconUri() != null) { + getWidget().icon = client.getIcon(getIconUri()); + getWidget().legend.insertFirst(getWidget().icon.getElement()); + legendEmpty = false; - } else { - if (getWidget().icon != null) { - getWidget().legend.removeChild(getWidget().icon.getElement()); - } } if (legendEmpty) { getWidget().addStyleDependentName("nocaption"); diff --git a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java index c65f689f7a..8328e1f0a7 100644 --- a/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/formlayout/FormLayoutConnector.java @@ -114,18 +114,14 @@ public class FormLayoutConnector extends AbstractLayoutConnector { TooltipInfo info = null; if (element != getWidget().getElement()) { - Object node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - VFormLayout.Caption.class); + Object node = Util.findWidget(element, VFormLayout.Caption.class); if (node != null) { VFormLayout.Caption caption = (VFormLayout.Caption) node; info = caption.getOwner().getTooltipInfo(element); } else { - node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - VFormLayout.ErrorFlag.class); + node = Util.findWidget(element, VFormLayout.ErrorFlag.class); if (node != null) { VFormLayout.ErrorFlag flag = (VFormLayout.ErrorFlag) node; diff --git a/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java b/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java index 29d41e00b7..cc052fa6d5 100644 --- a/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/gridlayout/GridLayoutConnector.java @@ -17,7 +17,6 @@ package com.vaadin.client.ui.gridlayout; import java.util.Map.Entry; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; @@ -49,14 +48,15 @@ public class GridLayoutConnector extends AbstractComponentContainerConnector this) { @Override - protected ComponentConnector getChildComponent(Element element) { + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { return getWidget().getComponent(element); } @Override protected LayoutClickRpc getLayoutClickRPC() { return getRpcProxy(GridLayoutServerRpc.class); - }; + } }; diff --git a/client/src/com/vaadin/client/ui/label/LabelConnector.java b/client/src/com/vaadin/client/ui/label/LabelConnector.java index 9639987e8d..6a04c91562 100644 --- a/client/src/com/vaadin/client/ui/label/LabelConnector.java +++ b/client/src/com/vaadin/client/ui/label/LabelConnector.java @@ -36,12 +36,6 @@ public class LabelConnector extends AbstractComponentConnector { } @Override - protected void init() { - super.init(); - getWidget().setConnection(getConnection()); - } - - @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); boolean sinkOnloads = false; diff --git a/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java b/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java index 125f135aee..5c8a627dea 100644 --- a/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java +++ b/client/src/com/vaadin/client/ui/layout/VLayoutSlot.java @@ -16,18 +16,18 @@ package com.vaadin.client.ui.layout; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; 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.Widget; import com.vaadin.client.VCaption; import com.vaadin.shared.ui.AlignmentInfo; public abstract class VLayoutSlot { - private final Element wrapper = Document.get().createDivElement().cast(); + private final Element wrapper = Document.get().createDivElement(); private AlignmentInfo alignment; private VCaption caption; @@ -288,8 +288,8 @@ public abstract class VLayoutSlot { return isVertical ? isRelativeHeight() : isRelativeWidth(); } - public Element getWrapperElement() { - return wrapper; + public com.google.gwt.user.client.Element getWrapperElement() { + return DOM.asOld(wrapper); } public void setExpandRatio(double expandRatio) { diff --git a/client/src/com/vaadin/client/ui/link/LinkConnector.java b/client/src/com/vaadin/client/ui/link/LinkConnector.java index 228897278e..c8bbc426e9 100644 --- a/client/src/com/vaadin/client/ui/link/LinkConnector.java +++ b/client/src/com/vaadin/client/ui/link/LinkConnector.java @@ -17,38 +17,21 @@ package com.vaadin.client.ui.link; import com.google.gwt.user.client.DOM; -import com.vaadin.client.ApplicationConnection; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; import com.vaadin.client.communication.StateChangeEvent; -import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.VLink; -import com.vaadin.shared.ui.BorderStyle; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.link.LinkConstants; import com.vaadin.shared.ui.link.LinkState; import com.vaadin.ui.Link; @Connect(Link.class) -public class LinkConnector extends AbstractComponentConnector implements - Paintable { +public class LinkConnector extends AbstractComponentConnector { @Override protected void init() { super.init(); - addStateChangeHandler("resources", new StateChangeHandler() { - @Override - public void onStateChanged(StateChangeEvent stateChangeEvent) { - getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); - if (getWidget().src == null) { - getWidget().anchor.removeAttribute("href"); - } else { - getWidget().anchor.setAttribute("href", getWidget().src); - } - } - }); } @Override @@ -62,35 +45,30 @@ public class LinkConnector extends AbstractComponentConnector implements } @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - - if (!isRealUpdate(uidl)) { - return; - } - - getWidget().client = client; + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); getWidget().enabled = isEnabled(); - if (uidl.hasAttribute("name")) { - getWidget().target = uidl.getStringAttribute("name"); - getWidget().anchor.setAttribute("target", getWidget().target); - } - - if (uidl.hasAttribute("border")) { - if ("none".equals(uidl.getStringAttribute("border"))) { - getWidget().borderStyle = BorderStyle.NONE; + if (stateChangeEvent.hasPropertyChanged("resources")) { + getWidget().src = getResourceUrl(LinkConstants.HREF_RESOURCE); + if (getWidget().src == null) { + getWidget().anchor.removeAttribute("href"); } else { - getWidget().borderStyle = BorderStyle.MINIMAL; + getWidget().anchor.setAttribute("href", getWidget().src); } + } + + getWidget().target = getState().target; + if (getWidget().target == null) { + getWidget().anchor.removeAttribute("target"); } else { - getWidget().borderStyle = BorderStyle.DEFAULT; + getWidget().anchor.setAttribute("target", getWidget().target); } - getWidget().targetHeight = uidl.hasAttribute("targetHeight") ? uidl - .getIntAttribute("targetHeight") : -1; - getWidget().targetWidth = uidl.hasAttribute("targetWidth") ? uidl - .getIntAttribute("targetWidth") : -1; + getWidget().borderStyle = getState().targetBorder; + getWidget().targetWidth = getState().targetWidth; + getWidget().targetHeight = getState().targetHeight; // Set link caption getWidget().captionElement.setInnerText(getState().caption); @@ -109,15 +87,16 @@ public class LinkConnector extends AbstractComponentConnector implements "none"); } - if (getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(client); - getWidget().anchor.insertBefore(getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getIcon()); + if (getWidget().icon != null) { + getWidget().anchor.removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + getWidget().anchor.insertBefore(icon.getElement(), + getWidget().captionElement); } - } @Override diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBar.java b/client/src/com/vaadin/client/ui/menubar/MenuBar.java index 4441faf7ab..6f0546601a 100644 --- a/client/src/com/vaadin/client/ui/menubar/MenuBar.java +++ b/client/src/com/vaadin/client/ui/menubar/MenuBar.java @@ -37,9 +37,9 @@ import java.util.ArrayList; import java.util.List; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; 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.Event; import com.google.gwt.user.client.ui.PopupListener; import com.google.gwt.user.client.ui.PopupPanel; diff --git a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java b/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java index 3e22ebb05b..4ead614275 100644 --- a/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java +++ b/client/src/com/vaadin/client/ui/menubar/MenuBarConnector.java @@ -27,7 +27,7 @@ import com.vaadin.client.TooltipInfo; import com.vaadin.client.UIDL; import com.vaadin.client.Util; import com.vaadin.client.ui.AbstractComponentConnector; -import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VMenuBar; import com.vaadin.shared.ui.ComponentStateUtil; @@ -81,7 +81,8 @@ public class MenuBarConnector extends AbstractComponentConnector implements + Util.escapeAttribute(client .translateVaadinUri(moreItemUIDL .getStringAttribute("icon"))) - + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />"); + + "\" class=\"" + ImageIcon.CLASSNAME + + "\" alt=\"\" />"); } String moreItemText = moreItemUIDL.getStringAttribute("text"); @@ -196,7 +197,7 @@ public class MenuBarConnector extends AbstractComponentConnector implements if (element != getWidget().getElement()) { VMenuBar.CustomMenuItem item = getWidget().getMenuItemWithElement( - (com.google.gwt.user.client.Element) element); + element); if (item != null) { info = item.getTooltip(); } diff --git a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java index 11a76b483b..e4e88899eb 100644 --- a/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java +++ b/client/src/com/vaadin/client/ui/nativebutton/NativeButtonConnector.java @@ -87,20 +87,16 @@ public class NativeButtonConnector extends AbstractComponentConnector implements getWidget().errorIndicatorElement = null; } - if (getIcon() != null) { - if (getWidget().icon == null) { - getWidget().icon = new Icon(getConnection()); - getWidget().getElement().insertBefore( - getWidget().icon.getElement(), - getWidget().captionElement); - } - getWidget().icon.setUri(getIcon()); - } else { - if (getWidget().icon != null) { - getWidget().getElement().removeChild( - getWidget().icon.getElement()); - getWidget().icon = null; - } + if (getWidget().icon != null) { + getWidget().getElement().removeChild(getWidget().icon.getElement()); + getWidget().icon = null; + } + Icon icon = getIcon(); + if (icon != null) { + getWidget().icon = icon; + getWidget().getElement().insertBefore(icon.getElement(), + getWidget().captionElement); + icon.setAlternateText(getState().iconAltText); } } diff --git a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java b/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java index d9299f2621..fbf94f0da3 100644 --- a/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java +++ b/client/src/com/vaadin/client/ui/orderedlayout/AbstractOrderedLayoutConnector.java @@ -17,8 +17,8 @@ package com.vaadin.client.ui.orderedlayout; import java.util.List; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; @@ -32,6 +32,7 @@ import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.communication.StateChangeEvent.StateChangeHandler; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.AbstractLayoutConnector; +import com.vaadin.client.ui.Icon; import com.vaadin.client.ui.LayoutClickEventHandler; import com.vaadin.client.ui.aria.AriaHelper; import com.vaadin.client.ui.layout.ElementResizeEvent; @@ -59,7 +60,8 @@ public abstract class AbstractOrderedLayoutConnector extends this) { @Override - protected ComponentConnector getChildComponent(Element element) { + protected ComponentConnector getChildComponent( + com.google.gwt.user.client.Element element) { return Util.getConnectorForElement(getConnection(), getWidget(), element); } @@ -97,12 +99,12 @@ public abstract class AbstractOrderedLayoutConnector extends public void onElementResize(ElementResizeEvent e) { // Get all needed element references - Element captionElement = (Element) e.getElement().cast(); + Element captionElement = e.getElement(); // Caption position determines if the widget element is the first or // last child inside the caption wrap CaptionPosition pos = getWidget().getCaptionPositionFromElement( - (Element) captionElement.getParentElement().cast()); + captionElement.getParentElement()); // The default is the last child Element widgetElement = captionElement.getParentElement() @@ -244,6 +246,8 @@ public abstract class AbstractOrderedLayoutConnector extends URLReference iconUrl = child.getState().resources .get(ComponentConstants.ICON_RESOURCE); String iconUrlString = iconUrl != null ? iconUrl.getURL() : null; + Icon icon = child.getConnection().getIcon(iconUrlString); + List<String> styles = child.getState().styles; String error = child.getState().errorMessage; boolean showError = error != null; @@ -262,8 +266,8 @@ public abstract class AbstractOrderedLayoutConnector extends slot.setCaptionResizeListener(null); } - slot.setCaption(caption, iconUrlString, styles, error, showError, - required, enabled); + slot.setCaption(caption, icon, styles, error, showError, required, + enabled); AriaHelper.handleInputRequired(child.getWidget(), required); AriaHelper.handleInputInvalid(child.getWidget(), showError); @@ -374,8 +378,7 @@ public abstract class AbstractOrderedLayoutConnector extends @Override public TooltipInfo getTooltipInfo(com.google.gwt.dom.client.Element element) { if (element != getWidget().getElement()) { - Slot slot = Util.findWidget( - (com.google.gwt.user.client.Element) element, Slot.class); + Slot slot = Util.findWidget(element, Slot.class); if (slot != null && slot.getCaptionElement() != null && slot.getParent() == getWidget() && slot.getCaptionElement().isOrHasChild(element)) { diff --git a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java index d9037cda3c..03038eefd3 100644 --- a/client/src/com/vaadin/client/ui/orderedlayout/Slot.java +++ b/client/src/com/vaadin/client/ui/orderedlayout/Slot.java @@ -19,18 +19,21 @@ package com.vaadin.client.ui.orderedlayout; import java.util.List; import com.google.gwt.aria.client.Roles; +import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; 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.ui.SimplePanel; -import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; import com.vaadin.client.BrowserInfo; import com.vaadin.client.LayoutManager; import com.vaadin.client.StyleConstants; import com.vaadin.client.Util; +import com.vaadin.client.ui.FontIcon; +import com.vaadin.client.ui.Icon; +import com.vaadin.client.ui.ImageIcon; import com.vaadin.client.ui.layout.ElementResizeEvent; import com.vaadin.client.ui.layout.ElementResizeListener; import com.vaadin.shared.ui.AlignmentInfo; @@ -39,44 +42,6 @@ import com.vaadin.shared.ui.AlignmentInfo; * Represents a slot which contains the actual widget in the layout. */ public final class Slot extends SimplePanel { - /** - * The icon for each widget. Located in the caption of the slot. - */ - private static class Icon extends UIObject { - - public static final String CLASSNAME = "v-icon"; - - private String myUrl; - - /** - * Constructor - */ - public Icon() { - setElement(DOM.createImg()); - DOM.setElementProperty(getElement(), "alt", ""); - setStyleName(CLASSNAME); - } - - /** - * Set the URL where the icon is located - * - * @param url - * A fully qualified URL - */ - public void setUri(String url) { - if (!url.equals(myUrl)) { - /* - * Start sinking onload events, widgets responsibility to react. - * We must do this BEFORE we set src as IE fires the event - * immediately if the image is found in cache (#2592). - */ - sinkEvents(Event.ONLOAD); - - DOM.setElementProperty(getElement(), "src", url); - myUrl = url; - } - } - } private static final String ALIGN_CLASS_PREFIX = "v-align-"; @@ -123,15 +88,12 @@ public final class Slot extends SimplePanel { private double expandRatio = -1; /** - * Constructor + * Constructs a slot. * + * @param layout + * The layout to which this slot belongs * @param widget * The widget to put in the slot - * @param layout - * TODO - * - * @param layoutManager - * The layout manager used by the layout */ public Slot(VAbstractOrderedLayout layout, Widget widget) { this.layout = layout; @@ -370,8 +332,8 @@ public final class Slot extends SimplePanel { * * @return */ - public Element getSpacingElement() { - return spacer; + public com.google.gwt.user.client.Element getSpacingElement() { + return DOM.asOld(spacer); } /** @@ -445,7 +407,7 @@ public final class Slot extends SimplePanel { * @param captionText * The text of the caption * @param iconUrl - * The icon URL + * The icon URL, must already be run trough translateVaadinUri() * @param styles * The style names * @param error @@ -456,10 +418,47 @@ public final class Slot extends SimplePanel { * Is the (field) required * @param enabled * Is the component enabled + * + * @deprecated Use + * {@link #setCaption(String, Icon, List, String, boolean, boolean, boolean)} + * instead */ + @Deprecated public void setCaption(String captionText, String iconUrl, List<String> styles, String error, boolean showError, boolean required, boolean enabled) { + Icon icon; + if (FontIcon.isFontIconUri(iconUrl)) { + icon = GWT.create(FontIcon.class); + } else { + icon = GWT.create(ImageIcon.class); + } + icon.setUri(iconUrl); + + setCaption(captionText, icon, styles, error, showError, required, + enabled); + } + + /** + * Set the caption of the slot + * + * @param captionText + * The text of the caption + * @param icon + * The icon + * @param styles + * The style names + * @param error + * The error message + * @param showError + * Should the error message be shown + * @param required + * Is the (field) required + * @param enabled + * Is the component enabled + */ + public void setCaption(String captionText, Icon icon, List<String> styles, + String error, boolean showError, boolean required, boolean enabled) { // TODO place for optimization: check if any of these have changed // since last time, and only run those changes @@ -469,7 +468,7 @@ public final class Slot extends SimplePanel { final Element focusedElement = Util.getFocusedElement(); // By default focus will not be lost boolean focusLost = false; - if (captionText != null || iconUrl != null || error != null || required) { + if (captionText != null || icon != null || error != null || required) { if (caption == null) { caption = DOM.createDiv(); captionWrap = DOM.createDiv(); @@ -516,16 +515,13 @@ public final class Slot extends SimplePanel { } // Icon - if (iconUrl != null) { - if (icon == null) { - icon = new Icon(); - caption.insertFirst(icon.getElement()); - } - icon.setUri(iconUrl); - } else if (icon != null) { - icon.getElement().removeFromParent(); - icon = null; + if (this.icon != null) { + this.icon.getElement().removeFromParent(); + } + if (icon != null) { + caption.insertFirst(icon.getElement()); } + this.icon = icon; // Required if (required) { @@ -576,7 +572,7 @@ public final class Slot extends SimplePanel { } // Caption position - if (captionText != null || iconUrl != null) { + if (captionText != null || icon != null) { setCaptionPosition(CaptionPosition.TOP); } else { setCaptionPosition(CaptionPosition.RIGHT); @@ -633,8 +629,8 @@ public final class Slot extends SimplePanel { /** * Get the slots caption element */ - public Element getCaptionElement() { - return caption; + public com.google.gwt.user.client.Element getCaptionElement() { + return DOM.asOld(caption); } private boolean relativeWidth = false; @@ -729,11 +725,11 @@ public final class Slot extends SimplePanel { * @see com.google.gwt.user.client.ui.SimplePanel#getContainerElement() */ @Override - protected Element getContainerElement() { + protected com.google.gwt.user.client.Element getContainerElement() { if (captionWrap == null) { return getElement(); } else { - return captionWrap; + return DOM.asOld(captionWrap); } } diff --git a/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java b/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java index 54c9eb6c68..4a1dae9b95 100644 --- a/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java +++ b/client/src/com/vaadin/client/ui/orderedlayout/VAbstractOrderedLayout.java @@ -18,6 +18,7 @@ package com.vaadin.client.ui.orderedlayout; import java.util.HashMap; import java.util.Map; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; @@ -25,7 +26,6 @@ import com.google.gwt.dom.client.Style.Visibility; import com.google.gwt.regexp.shared.MatchResult; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Element; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; @@ -141,9 +141,14 @@ public class VAbstractOrderedLayout extends FlowPanel { /** * {@inheritDoc} + * + * @deprecated As of 7.2, use or override + * {@link #insert(Widget, Element, int, boolean)} instead. */ @Override - protected void insert(Widget child, Element container, int beforeIndex, + @Deprecated + protected void insert(Widget child, + com.google.gwt.user.client.Element container, int beforeIndex, boolean domInsert) { // Validate index; adjust if the widget is already a child of this // panel. @@ -156,7 +161,8 @@ public class VAbstractOrderedLayout extends FlowPanel { getChildren().insert(child, beforeIndex); // Physical attach. - container = expandWrapper != null ? expandWrapper : getElement(); + container = expandWrapper != null ? DOM.asOld(expandWrapper) + : getElement(); if (domInsert) { if (spacing) { if (beforeIndex != 0) { @@ -184,6 +190,17 @@ public class VAbstractOrderedLayout extends FlowPanel { } /** + * {@inheritDoc} + * + * @since 7.2 + */ + @Override + protected void insert(Widget child, Element container, int beforeIndex, + boolean domInsert) { + insert(child, DOM.asOld(container), beforeIndex, domInsert); + } + + /** * Remove a slot from the layout * * @param widget @@ -220,8 +237,10 @@ public class VAbstractOrderedLayout extends FlowPanel { * @param widgetElement * The element of the widget ( Same as getWidget().getElement() ) * @return + * @deprecated As of 7.2, call or override {@link #getSlot(Element)} instead */ - public Slot getSlot(Element widgetElement) { + @Deprecated + public Slot getSlot(com.google.gwt.user.client.Element widgetElement) { for (Map.Entry<Widget, Slot> entry : widgetToSlot.entrySet()) { if (entry.getKey().getElement() == widgetElement) { return entry.getValue(); @@ -231,6 +250,20 @@ public class VAbstractOrderedLayout extends FlowPanel { } /** + * Gets a slot based on the widget element. If no slot is found then null is + * returned. + * + * @param widgetElement + * The element of the widget ( Same as getWidget().getElement() ) + * @return + * + * @since 7.2 + */ + public Slot getSlot(Element widgetElement) { + return getSlot(DOM.asOld(widgetElement)); + } + + /** * Set the layout manager for the layout * * @param manager @@ -257,8 +290,12 @@ public class VAbstractOrderedLayout extends FlowPanel { * The wrapping element * * @return The caption position + * @deprecated As of 7.2, call or override + * {@link #getCaptionPositionFromElement(Element)} instead */ - public CaptionPosition getCaptionPositionFromElement(Element captionWrap) { + @Deprecated + public CaptionPosition getCaptionPositionFromElement( + com.google.gwt.user.client.Element captionWrap) { RegExp captionPositionRegexp = RegExp.compile("v-caption-on-(\\S+)"); // Get caption position from the classname @@ -274,16 +311,34 @@ public class VAbstractOrderedLayout extends FlowPanel { } /** + * Deducts the caption position by examining the wrapping element. + * <p> + * For internal use only. May be removed or replaced in the future. + * + * @param captionWrap + * The wrapping element + * + * @return The caption position + * @since 7.2 + */ + public CaptionPosition getCaptionPositionFromElement(Element captionWrap) { + return getCaptionPositionFromElement(DOM.asOld(captionWrap)); + } + + /** * Update the offset off the caption relative to the slot * <p> * For internal use only. May be removed or replaced in the future. * * @param caption * The caption element + * @deprecated As of 7.2, call or override + * {@link #updateCaptionOffset(Element)} instead */ - public void updateCaptionOffset(Element caption) { + @Deprecated + public void updateCaptionOffset(com.google.gwt.user.client.Element caption) { - Element captionWrap = caption.getParentElement().cast(); + Element captionWrap = caption.getParentElement(); Style captionWrapStyle = captionWrap.getStyle(); captionWrapStyle.clearPaddingTop(); @@ -341,6 +396,19 @@ public class VAbstractOrderedLayout extends FlowPanel { } /** + * Update the offset off the caption relative to the slot + * <p> + * For internal use only. May be removed or replaced in the future. + * + * @param caption + * The caption element + * @since 7.2 + */ + public void updateCaptionOffset(Element caption) { + updateCaptionOffset(DOM.asOld(caption)); + } + + /** * Set the margin of the layout * * @param marginInfo diff --git a/client/src/com/vaadin/client/ui/panel/PanelConnector.java b/client/src/com/vaadin/client/ui/panel/PanelConnector.java index 4011f86c76..f2e73bae80 100644 --- a/client/src/com/vaadin/client/ui/panel/PanelConnector.java +++ b/client/src/com/vaadin/client/ui/panel/PanelConnector.java @@ -137,8 +137,8 @@ public class PanelConnector extends AbstractSingleComponentContainerConnector getWidget().client = client; getWidget().id = uidl.getId(); - if (getIcon() != null) { - getWidget().setIconUri(getIcon(), client); + if (getIconUri() != null) { + getWidget().setIconUri(getIconUri(), client); } else { getWidget().setIconUri(null, client); } diff --git a/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java b/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java index f49c4ba3ee..2f53280c99 100644 --- a/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java +++ b/client/src/com/vaadin/client/ui/popupview/PopupViewConnector.java @@ -49,11 +49,6 @@ public class PopupViewConnector extends AbstractHasComponentsConnector } @Override - public boolean delegateCaptionHandling() { - return false; - } - - @Override public void onStateChanged(StateChangeEvent stateChangeEvent) { super.onStateChanged(stateChangeEvent); diff --git a/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java b/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java index d2576eb133..a19e931713 100644 --- a/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java +++ b/client/src/com/vaadin/client/ui/richtextarea/RichTextAreaConnector.java @@ -109,5 +109,5 @@ public class RichTextAreaConnector extends AbstractFieldConnector implements getState().immediate); } } - }; + } } diff --git a/client/src/com/vaadin/client/ui/slider/SliderConnector.java b/client/src/com/vaadin/client/ui/slider/SliderConnector.java index 71462d69f0..b4eb9f14f7 100644 --- a/client/src/com/vaadin/client/ui/slider/SliderConnector.java +++ b/client/src/com/vaadin/client/ui/slider/SliderConnector.java @@ -21,6 +21,8 @@ import com.vaadin.client.communication.RpcProxy; import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractFieldConnector; import com.vaadin.client.ui.VSlider; +import com.vaadin.client.ui.layout.ElementResizeEvent; +import com.vaadin.client.ui.layout.ElementResizeListener; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.slider.SliderServerRpc; import com.vaadin.shared.ui.slider.SliderState; @@ -33,11 +35,29 @@ public class SliderConnector extends AbstractFieldConnector implements protected SliderServerRpc rpc = RpcProxy .create(SliderServerRpc.class, this); + private final ElementResizeListener resizeListener = new ElementResizeListener() { + + @Override + public void onElementResize(ElementResizeEvent e) { + getWidget().iLayout(); + } + }; + @Override public void init() { super.init(); getWidget().setConnection(getConnection()); getWidget().addValueChangeHandler(this); + + getLayoutManager().addElementResizeListener(getWidget().getElement(), + resizeListener); + } + + @Override + public void onUnregister() { + super.onUnregister(); + getLayoutManager().removeElementResizeListener( + getWidget().getElement(), resizeListener); } @Override diff --git a/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java b/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java index 464dd5057c..45535de3de 100644 --- a/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java +++ b/client/src/com/vaadin/client/ui/splitpanel/AbstractSplitPanelConnector.java @@ -18,12 +18,13 @@ package com.vaadin.client.ui.splitpanel; import java.util.LinkedList; import java.util.List; +import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.event.dom.client.DomEvent.Type; import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.HandlerRegistration; -import com.google.gwt.user.client.Element; +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.ComponentConnector; @@ -103,12 +104,12 @@ public abstract class AbstractSplitPanelConnector extends } return super.shouldFireEvent(event); - }; + } @Override - protected Element getRelativeToElement() { - return getWidget().splitter; - }; + protected com.google.gwt.user.client.Element getRelativeToElement() { + return DOM.asOld(getWidget().splitter); + } @Override protected void fireClick(NativeEvent event, diff --git a/client/src/com/vaadin/client/ui/table/TableConnector.java b/client/src/com/vaadin/client/ui/table/TableConnector.java index 4d7d3c6277..d2bd06a753 100644 --- a/client/src/com/vaadin/client/ui/table/TableConnector.java +++ b/client/src/com/vaadin/client/ui/table/TableConnector.java @@ -264,7 +264,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements if (getWidget().focusedRow != null) { if (!getWidget().focusedRow.isAttached() - && !getWidget().rowRequestHandler.isRunning()) { + && !getWidget().rowRequestHandler.isRequestHandlerRunning()) { // focused row has been orphaned, can't focus if (getWidget().selectedRowKeys.contains(getWidget().focusedRow .getKey())) { @@ -402,9 +402,7 @@ public class TableConnector extends AbstractHasComponentsConnector implements TooltipInfo info = null; if (element != getWidget().getElement()) { - Object node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - VScrollTableRow.class); + Object node = Util.findWidget(element, VScrollTableRow.class); if (node != null) { VScrollTableRow row = (VScrollTableRow) node; diff --git a/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java b/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java index 283bc1b63b..30c9e47c6e 100644 --- a/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java +++ b/client/src/com/vaadin/client/ui/tabsheet/TabsheetBaseConnector.java @@ -19,31 +19,41 @@ import java.util.ArrayList; import java.util.Iterator; import com.google.gwt.user.client.ui.Widget; -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; -import com.vaadin.client.Paintable; -import com.vaadin.client.UIDL; +import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.AbstractComponentContainerConnector; import com.vaadin.client.ui.VTabsheetBase; -import com.vaadin.shared.ui.tabsheet.TabsheetBaseConstants; +import com.vaadin.shared.ui.tabsheet.TabState; +import com.vaadin.shared.ui.tabsheet.TabsheetState; public abstract class TabsheetBaseConnector extends - AbstractComponentContainerConnector implements Paintable { + AbstractComponentContainerConnector { + /* + * (non-Javadoc) + * + * @see com.vaadin.client.ui.AbstractConnector#init() + */ @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { - getWidget().client = client; + protected void init() { + super.init(); - if (!isRealUpdate(uidl)) { - return; - } + getWidget().setClient(getConnection()); + } - // Update member references - getWidget().id = uidl.getId(); - getWidget().disabled = !isEnabled(); + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin + * .client.communication.StateChangeEvent) + */ + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); - // Render content - final UIDL tabs = uidl.getChildUIDL(0); + // Update member references + getWidget().setEnabled(isEnabled()); // Widgets in the TabSheet before update ArrayList<Widget> oldWidgets = new ArrayList<Widget>(); @@ -53,26 +63,22 @@ public abstract class TabsheetBaseConnector extends } // Clear previous values - getWidget().tabKeys.clear(); - getWidget().disabledTabKeys.clear(); + getWidget().clearTabKeys(); int index = 0; - for (final Iterator<Object> it = tabs.getChildIterator(); it.hasNext();) { - final UIDL tab = (UIDL) it.next(); - final String key = tab.getStringAttribute("key"); - final boolean selected = tab.getBooleanAttribute("selected"); - final boolean hidden = tab.getBooleanAttribute("hidden"); - - if (tab.getBooleanAttribute(TabsheetBaseConstants.ATTRIBUTE_TAB_DISABLED)) { - getWidget().disabledTabKeys.add(key); - } + for (TabState tab : getState().tabs) { + final String key = tab.key; + final boolean selected = key.equals(getState().selected); - getWidget().tabKeys.add(key); + getWidget().addTabKey(key, !tab.enabled && tab.visible); if (selected) { - getWidget().activeTabIndex = index; + getWidget().setActiveTabIndex(index); + } + getWidget().renderTab(tab, index); + if (selected) { + getWidget().selectTab(index); } - getWidget().renderTab(tab, index, selected, hidden); index++; } @@ -104,4 +110,9 @@ public abstract class TabsheetBaseConnector extends return (VTabsheetBase) super.getWidget(); } + @Override + public TabsheetState getState() { + return (TabsheetState) super.getState(); + } + } diff --git a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java b/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java index 04a514738d..564e5847d9 100644 --- a/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java +++ b/client/src/com/vaadin/client/ui/tabsheet/TabsheetConnector.java @@ -17,36 +17,62 @@ package com.vaadin.client.ui.tabsheet; import com.google.gwt.dom.client.Element; import com.google.gwt.user.client.DOM; -import com.vaadin.client.ApplicationConnection; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; import com.vaadin.client.TooltipInfo; -import com.vaadin.client.UIDL; import com.vaadin.client.Util; +import com.vaadin.client.communication.StateChangeEvent; import com.vaadin.client.ui.SimpleManagedLayout; import com.vaadin.client.ui.VTabsheet; import com.vaadin.client.ui.layout.MayScrollChildren; import com.vaadin.shared.ui.Connect; -import com.vaadin.shared.ui.tabsheet.TabsheetState; +import com.vaadin.shared.ui.tabsheet.TabsheetClientRpc; import com.vaadin.ui.TabSheet; @Connect(TabSheet.class) public class TabsheetConnector extends TabsheetBaseConnector implements SimpleManagedLayout, MayScrollChildren { - // Can't use "style" as it's already in use + public TabsheetConnector() { + registerRpc(TabsheetClientRpc.class, new TabsheetClientRpc() { + @Override + public void revertToSharedStateSelection() { + for (int i = 0; i < getState().tabs.size(); ++i) { + final String key = getState().tabs.get(i).key; + final boolean selected = key.equals(getState().selected); + if (selected) { + getWidget().selectTab(i); + break; + } + } + renderContent(); + } + }); + } + @Override - public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { + protected void init() { + super.init(); + getWidget().setConnector(this); + } - if (isRealUpdate(uidl)) { - // Handle stylename changes before generics (might affect size - // calculations) - getWidget().handleStyleNames(uidl, getState()); - } + /* + * (non-Javadoc) + * + * @see + * com.vaadin.client.ui.AbstractComponentConnector#onStateChanged(com.vaadin + * .client.communication.StateChangeEvent) + */ + @Override + public void onStateChanged(StateChangeEvent stateChangeEvent) { + super.onStateChanged(stateChangeEvent); + + getWidget().handleStyleNames(getState()); - super.updateFromUIDL(uidl, client); - if (!isRealUpdate(uidl)) { - return; + if (getState().tabsVisible) { + getWidget().showTabs(); + } else { + getWidget().hideTabs(); } // tabs; push or not @@ -76,11 +102,6 @@ public class TabsheetConnector extends TabsheetBaseConnector implements } @Override - public TabsheetState getState() { - return (TabsheetState) super.getState(); - } - - @Override public void updateCaption(ComponentConnector component) { /* Tabsheet does not render its children's captions */ } @@ -119,9 +140,7 @@ public class TabsheetConnector extends TabsheetBaseConnector implements // Find a tooltip for the tab, if the element is a tab if (element != getWidget().getElement()) { - Object node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - VTabsheet.TabCaption.class); + Object node = Util.findWidget(element, VTabsheet.TabCaption.class); if (node != null) { VTabsheet.TabCaption caption = (VTabsheet.TabCaption) node; @@ -149,7 +168,24 @@ public class TabsheetConnector extends TabsheetBaseConnector implements @Override public void onConnectorHierarchyChange( - ConnectorHierarchyChangeEvent connectorHierarchyChangeEvent) { - // TODO Move code from updateFromUIDL to this method + ConnectorHierarchyChangeEvent connector) { + renderContent(); } + + /** + * (Re-)render the content of the active tab. + */ + protected void renderContent() { + ComponentConnector contentConnector = null; + if (!getChildComponents().isEmpty()) { + contentConnector = getChildComponents().get(0); + } + + if (null != contentConnector) { + getWidget().renderContent(contentConnector.getWidget()); + } else { + getWidget().renderContent(null); + } + } + } diff --git a/client/src/com/vaadin/client/ui/tree/TreeConnector.java b/client/src/com/vaadin/client/ui/tree/TreeConnector.java index 7560a0f56b..8b09c10ad3 100644 --- a/client/src/com/vaadin/client/ui/tree/TreeConnector.java +++ b/client/src/com/vaadin/client/ui/tree/TreeConnector.java @@ -46,6 +46,11 @@ public class TreeConnector extends AbstractComponentConnector implements protected final Map<TreeNode, TooltipInfo> tooltipMap = new HashMap<TreeNode, TooltipInfo>(); @Override + protected void init() { + getWidget().connector = this; + } + + @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { if (!isRealUpdate(uidl)) { return; @@ -327,9 +332,7 @@ public class TreeConnector extends AbstractComponentConnector implements // Try to find a tooltip for a node if (element != getWidget().getElement()) { - Object node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - TreeNode.class); + Object node = Util.findWidget(element, TreeNode.class); if (node != null) { TreeNode tnode = (TreeNode) node; diff --git a/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java b/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java index a8c6a8823a..4f62f0df89 100644 --- a/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java +++ b/client/src/com/vaadin/client/ui/treetable/TreeTableConnector.java @@ -123,9 +123,7 @@ public class TreeTableConnector extends TableConnector { TooltipInfo info = null; if (element != getWidget().getElement()) { - Object node = Util.findWidget( - (com.google.gwt.user.client.Element) element, - VTreeTableRow.class); + Object node = Util.findWidget(element, VTreeTableRow.class); if (node != null) { VTreeTableRow row = (VTreeTableRow) node; diff --git a/client/src/com/vaadin/client/ui/ui/UIConnector.java b/client/src/com/vaadin/client/ui/ui/UIConnector.java index 149d99de17..576f50d018 100644 --- a/client/src/com/vaadin/client/ui/ui/UIConnector.java +++ b/client/src/com/vaadin/client/ui/ui/UIConnector.java @@ -22,6 +22,7 @@ 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.Element; import com.google.gwt.dom.client.HeadElement; import com.google.gwt.dom.client.LinkElement; import com.google.gwt.dom.client.NativeEvent; @@ -35,7 +36,6 @@ import com.google.gwt.event.logical.shared.ResizeHandler; import com.google.gwt.http.client.URL; 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.Event; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; @@ -49,7 +49,6 @@ import com.vaadin.client.ApplicationConnection.ApplicationStoppedEvent; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ConnectorHierarchyChangeEvent; -import com.vaadin.client.ConnectorMap; import com.vaadin.client.Focusable; import com.vaadin.client.Paintable; import com.vaadin.client.ServerConnector; @@ -102,10 +101,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector protected void init() { super.init(); registerRpc(PageClientRpc.class, new PageClientRpc() { - @Override - public void setTitle(String title) { - com.google.gwt.user.client.Window.setTitle(title); - } @Override public void reload() { @@ -196,7 +191,6 @@ public class UIConnector extends AbstractSingleComponentContainerConnector @Override public void updateFromUIDL(final UIDL uidl, ApplicationConnection client) { - ConnectorMap paintableMap = ConnectorMap.get(getConnection()); getWidget().id = getConnectorId(); boolean firstPaint = getWidget().connection == null; getWidget().connection = client; @@ -510,6 +504,18 @@ public class UIConnector extends AbstractSingleComponentContainerConnector return (VUI) super.getWidget(); } + @Override + protected ComponentConnector getContent() { + ComponentConnector connector = super.getContent(); + // VWindow (WindowConnector is its connector)is also a child component + // but it's never a content widget + if (connector instanceof WindowConnector) { + return null; + } else { + return connector; + } + } + protected void onChildSizeChange() { ComponentConnector child = getContent(); if (child == null) { @@ -685,6 +691,13 @@ public class UIConnector extends AbstractSingleComponentContainerConnector configurePolling(); } + if (stateChangeEvent.hasPropertyChanged("pageState.title")) { + String title = getState().pageState.title; + if (title != null) { + com.google.gwt.user.client.Window.setTitle(title); + } + } + if (stateChangeEvent.hasPropertyChanged("pushConfiguration")) { getConnection().setPushEnabled( getState().pushConfiguration.mode.isEnabled()); diff --git a/client/src/com/vaadin/client/ui/upload/UploadConnector.java b/client/src/com/vaadin/client/ui/upload/UploadConnector.java index 989a913adc..03f1a2802c 100644 --- a/client/src/com/vaadin/client/ui/upload/UploadConnector.java +++ b/client/src/com/vaadin/client/ui/upload/UploadConnector.java @@ -16,13 +16,17 @@ package com.vaadin.client.ui.upload; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.Paintable; import com.vaadin.client.UIDL; import com.vaadin.client.ui.AbstractComponentConnector; import com.vaadin.client.ui.VUpload; +import com.vaadin.shared.EventId; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.upload.UploadClientRpc; +import com.vaadin.shared.ui.upload.UploadServerRpc; import com.vaadin.ui.Upload; @Connect(Upload.class) @@ -39,6 +43,21 @@ public class UploadConnector extends AbstractComponentConnector implements } @Override + protected void init() { + super.init(); + + getWidget().fu.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + if (hasEventListener(EventId.CHANGE)) { + getRpcProxy(UploadServerRpc.class).change( + getWidget().fu.getFilename()); + } + } + }); + } + + @Override public void updateFromUIDL(UIDL uidl, ApplicationConnection client) { if (!isRealUpdate(uidl)) { return; diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index 54ea384f5a..07015e7fbe 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -94,7 +94,7 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector @Override public boolean delegateCaptionHandling() { return false; - }; + } @Override protected void init() { @@ -229,7 +229,7 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector Style childStyle = layoutElement.getStyle(); // IE8 needs some hackery to measure its content correctly - Util.forceIE8Redraw((com.google.gwt.user.client.Element) layoutElement); + Util.forceIE8Redraw(layoutElement); if (content.isRelativeHeight() && !BrowserInfo.get().isIE9()) { childStyle.setPosition(Position.ABSOLUTE); @@ -295,11 +295,21 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector // 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(); + if (getIconUri() != null) { + iconURL = getIconUri(); } + + window.setAssistivePrefix(state.assistivePrefix); + window.setAssistivePostfix(state.assistivePostfix); window.setCaption(state.caption, iconURL); + window.setWaiAriaRole(getState().role); + window.setAssistiveDescription(state.contentDescription); + + window.setTabStopEnabled(getState().assistiveTabStop); + window.setTabStopTopAssistiveText(getState().assistiveTabStopTopText); + window.setTabStopBottomAssistiveText(getState().assistiveTabStopBottomText); + clickEventHandler.handleEventHandlerRegistration(); window.immediate = state.immediate; |