aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenri Sara <hesara@vaadin.com>2013-11-27 12:15:13 +0200
committerHenri Sara <hesara@vaadin.com>2013-11-27 12:15:13 +0200
commit45cebafddc76a0a27eec1769ec775e1d2435f47f (patch)
tree217eb39ba1c5c4c4a9fd04c382e722683bac3399
parent5b2ddc15bd2c30d9f9033a0c4cf50e32827ef0c6 (diff)
parent617cea0055a8f9fab0fa28e7f57f5bacf356425d (diff)
downloadvaadin-framework-45cebafddc76a0a27eec1769ec775e1d2435f47f.tar.gz
vaadin-framework-45cebafddc76a0a27eec1769ec775e1d2435f47f.zip
Merge remote-tracking branch 'origin/7.1' into testbench4
Change-Id: I88cd2dbf8c5c53e6e208c79a736162535ac1e068
-rw-r--r--client/src/com/vaadin/client/ApplicationConfiguration.java2
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java60
-rw-r--r--client/src/com/vaadin/client/ComponentLocator.java700
-rw-r--r--client/src/com/vaadin/client/SimpleTree.java8
-rw-r--r--client/src/com/vaadin/client/Util.java1
-rw-r--r--client/src/com/vaadin/client/componentlocator/ComponentLocator.java138
-rw-r--r--client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java675
-rw-r--r--client/src/com/vaadin/client/componentlocator/LocatorStrategy.java77
-rw-r--r--client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java415
-rw-r--r--client/src/com/vaadin/client/debug/internal/AnalyzeLayoutsPanel.java267
-rw-r--r--client/src/com/vaadin/client/debug/internal/ConnectorInfoPanel.java107
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchyPanel.java178
-rw-r--r--client/src/com/vaadin/client/debug/internal/HierarchySection.java474
-rw-r--r--client/src/com/vaadin/client/debug/internal/Highlight.java58
-rw-r--r--client/src/com/vaadin/client/debug/internal/Icon.java2
-rw-r--r--client/src/com/vaadin/client/debug/internal/OptimizedWidgetsetPanel.java137
-rw-r--r--client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java37
-rw-r--r--client/src/com/vaadin/client/debug/internal/SelectorPath.java866
-rw-r--r--client/src/com/vaadin/client/debug/internal/TestBenchSection.java307
-rw-r--r--client/src/com/vaadin/client/metadata/TypeDataStore.java16
-rw-r--r--client/src/com/vaadin/client/ui/SubPartAware.java4
-rw-r--r--client/src/com/vaadin/client/ui/VScrollTable.java108
-rw-r--r--publish.xml32
-rw-r--r--uitest/src/com/vaadin/tests/application/calculator/Calc.java268
-rw-r--r--uitest/src/com/vaadin/tests/componentlocator/TestDetachedNotPresent.html (renamed from uitest/src/com/vaadin/tests/gwtadapter/componentlocator/TestDetachedNotPresent.html)0
-rw-r--r--uitest/src/com/vaadin/tests/components/ui/UIAccess.java361
26 files changed, 3761 insertions, 1537 deletions
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java
index 7a70080c7e..a8ae47385a 100644
--- a/client/src/com/vaadin/client/ApplicationConfiguration.java
+++ b/client/src/com/vaadin/client/ApplicationConfiguration.java
@@ -41,6 +41,7 @@ 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.metadata.BundleLoadCallback;
import com.vaadin.client.metadata.ConnectorBundleLoader;
@@ -595,6 +596,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));
}
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index f95cec4fbc..841e1df96d 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -74,6 +74,7 @@ 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;
@@ -547,38 +548,41 @@ public class ApplicationConnection {
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/user/client/Element;)(id, element);
+ });
+ client.getPathForElement = $entry(function(element) {
+ return componentLocator.@com.vaadin.client.componentlocator.ComponentLocator::getPathForElement(Lcom/google/gwt/user/client/Element;)(element);
+ });
+ client.initializing = false;
- $wnd.vaadin.clients[TTAppId] = client;
+ $wnd.vaadin.clients[TTAppId] = client;
}-*/;
private static native final int calculateBootstrapTime()
diff --git a/client/src/com/vaadin/client/ComponentLocator.java b/client/src/com/vaadin/client/ComponentLocator.java
index af934470c2..ef7ccc3b65 100644
--- a/client/src/com/vaadin/client/ComponentLocator.java
+++ b/client/src/com/vaadin/client/ComponentLocator.java
@@ -15,706 +15,20 @@
*/
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.
+ *
+ * @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/SimpleTree.java b/client/src/com/vaadin/client/SimpleTree.java
index 7370496cb8..edfa23fb13 100644
--- a/client/src/com/vaadin/client/SimpleTree.java
+++ b/client/src/com/vaadin/client/SimpleTree.java
@@ -116,6 +116,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);
diff --git a/client/src/com/vaadin/client/Util.java b/client/src/com/vaadin/client/Util.java
index 8972670232..c0e5f621af 100644
--- a/client/src/com/vaadin/client/Util.java
+++ b/client/src/com/vaadin/client/Util.java
@@ -855,6 +855,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) {
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..6f6e52c0e1
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/ComponentLocator.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.componentlocator;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.gwt.user.client.Element;
+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.
+ */
+ public String getPathForElement(Element targetElement) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ String path = strategy.getPathForElement(targetElement);
+ if (null != path) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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 Element getElementByPath(String path) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ Element element = strategy.getElementByPath(path);
+ if (null != element) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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 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 Element getElementByPathStartingAt(String path, Element root) {
+ for (LocatorStrategy strategy : locatorStrategies) {
+ Element element = strategy.getElementByPathStartingAt(path, root);
+ if (null != element) {
+ return 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;
+ }
+}
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..5123a57e5d
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/LegacyLocatorStrategy.java
@@ -0,0 +1,675 @@
+/*
+ * 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.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.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;
+
+ public LegacyLocatorStrategy(ApplicationConnection clientConnection) {
+ client = clientConnection;
+ }
+
+ @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(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;
+ }
+ }
+
+ @Override
+ public Element getElementByPath(String path) {
+ return getElementByPathStartingAt(path, null);
+ }
+
+ @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;
+ }
+
+ /**
+ * 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(com.google.gwt.user.client.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..56ed396609
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/LocatorStrategy.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.google.gwt.user.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 {
+ /**
+ * 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);
+}
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..95b2745bf8
--- /dev/null
+++ b/client/src/com/vaadin/client/componentlocator/VaadinFinderLocatorStrategy.java
@@ -0,0 +1,415 @@
+/*
+ * 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;
+
+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.ApplicationConnection;
+import com.vaadin.client.ComponentConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.client.ui.AbstractHasComponentsConnector;
+import com.vaadin.client.ui.SubPartAware;
+import com.vaadin.client.ui.VNotification;
+import com.vaadin.shared.AbstractComponentState;
+
+/**
+ * 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;
+
+ public VaadinFinderLocatorStrategy(ApplicationConnection clientConnection) {
+ client = clientConnection;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getPathForElement(Element targetElement) {
+ // Path generation functionality is not yet implemented as there is no
+ // current need for it. This might be implemented in the future if the
+ // need arises. Until then, all locator generation is handled by
+ // LegacyLocatorStrategy.
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPath(String path) {
+ if (path.startsWith("//VNotification")) {
+ return findNotificationByPath(path);
+ }
+ return getElementByPathStartingAtConnector(path,
+ client.getUIConnector());
+ }
+
+ /**
+ * 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 Element findNotificationByPath(String path) {
+ ArrayList<VNotification> notifications = new ArrayList<VNotification>();
+ for (Widget w : RootPanel.get()) {
+ if (w instanceof VNotification) {
+ notifications.add((VNotification) w);
+ }
+ }
+ String indexStr = extractPredicateString(path);
+ int index = indexStr == null ? 0 : Integer.parseInt(indexStr);
+ if (index >= 0 && index < notifications.size()) {
+ return notifications.get(index).getElement();
+ }
+ return null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Element getElementByPathStartingAt(String path, Element root) {
+ return getElementByPathStartingAtConnector(path,
+ Util.findPaintable(client, root));
+ }
+
+ /**
+ * Finds an element 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 element identified by path or null if not found.
+ */
+ private Element getElementByPathStartingAtConnector(String path,
+ ComponentConnector root) {
+ String[] pathComponents = path.split(SUBPART_SEPARATOR);
+ ComponentConnector connector = findConnectorByPath(pathComponents[0],
+ root);
+ if (connector != null) {
+ if (pathComponents.length > 1) {
+ // We have subparts
+ if (connector.getWidget() instanceof SubPartAware) {
+ return ((SubPartAware) connector.getWidget())
+ .getSubPartElement(pathComponents[1]);
+ } else {
+ return null;
+ }
+ }
+ return connector.getWidget().getElement();
+ }
+ return null;
+ }
+
+ /**
+ * Recursively finds a connector for the element identified by the provided
+ * path by traversing the connector hierarchy starting from the
+ * {@code parent} connector.
+ *
+ * @param path
+ * The path identifying an element.
+ * @param parent
+ * The connector to start traversing from.
+ * @return The connector identified by {@code path} or null if it no such
+ * connector could be found.
+ */
+ private ComponentConnector findConnectorByPath(String path,
+ ComponentConnector parent) {
+ 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> potentialMatches = collectPotentialMatches(
+ parent, fragments[0], findRecursively);
+ ComponentConnector connector = filterPotentialMatches(potentialMatches,
+ extractPredicateString(fragments[0]));
+ if (connector != null) {
+ if (fragments.length > 1) {
+ return findConnectorByPath(fragments[1], connector);
+ } else {
+ return connector;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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 null if none.
+ */
+ private String extractPredicateString(String pathFragment) {
+ int ixOpenBracket = indexOfIgnoringQuotes(pathFragment, '[');
+ if (ixOpenBracket >= 0) {
+ int ixCloseBracket = indexOfIgnoringQuotes(pathFragment, ']',
+ ixOpenBracket);
+ return pathFragment.substring(ixOpenBracket + 1, ixCloseBracket);
+ }
+ return null;
+ }
+
+ /**
+ * Returns the first ComponentConnector that matches the predicate string
+ * from a list of potential matches. If {@code predicateString} is null, the
+ * first element in the {@code potentialMatches} list is returned.
+ *
+ * @param potentialMatches
+ * A list of potential matches to check.
+ * @param predicateString
+ * The predicate that should match. Can be an index or a property
+ * name, value pair or null.
+ * @return A {@link ComponentConnector} from the {@code potentialMatches}
+ * list, which matches the {@code predicateString} or null if no
+ * matches are found.
+ */
+ private ComponentConnector filterPotentialMatches(
+ List<ComponentConnector> potentialMatches, String predicateString) {
+
+ if (potentialMatches.isEmpty()) {
+ return null;
+ }
+
+ if (predicateString != null) {
+
+ int split_idx = predicateString.indexOf('=');
+
+ if (split_idx != -1) {
+
+ String propertyName = predicateString.substring(0, split_idx)
+ .trim();
+ String value = unquote(predicateString.substring(split_idx + 1)
+ .trim());
+
+ for (ComponentConnector connector : potentialMatches) {
+ Property property = AbstractConnector.getStateType(
+ connector).getProperty(propertyName);
+ if (valueEqualsPropertyValue(value, property,
+ connector.getState())) {
+ return connector;
+ }
+ }
+
+ return null;
+
+ } else {
+ int index = Integer.valueOf(predicateString);
+ return index < potentialMatches.size() ? potentialMatches
+ .get(index) : null;
+ }
+ }
+
+ return potentialMatches.get(0);
+ }
+
+ /**
+ * Returns true if the value matches the value of the property in the state
+ * object.
+ *
+ * @param value
+ * The value to compare against.
+ * @param property
+ * The property, whose value to check.
+ * @param state
+ * The connector, whose state object contains the property.
+ * @return true if the values match.
+ */
+ private boolean valueEqualsPropertyValue(String value, Property property,
+ AbstractComponentState state) {
+ try {
+ return value.equals(property.getValue(state));
+ } catch (NoDataException e) {
+ // The property doesn't exist in the state object, so they aren't
+ // equal.
+ return false;
+ }
+ }
+
+ /**
+ * Removes the surrounding quotes from a string if it is quoted.
+ *
+ * @param str
+ * the possibly quoted string
+ * @return an unquoted version of str
+ */
+ private String unquote(String str) {
+ if ((str.startsWith("\"") && str.endsWith("\""))
+ || (str.startsWith("'") && str.endsWith("'"))) {
+ return str.substring(1, str.length() - 1);
+ }
+ return str;
+ }
+
+ /**
+ * 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 AbstractHasComponentsConnector) {
+ List<ComponentConnector> children = ((AbstractHasComponentsConnector) 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 potentialMatches;
+ }
+
+ /**
+ * 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) {
+ return widgetName.equals(Util.getSimpleName(connector.getWidget()));
+ }
+
+ /**
+ * 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 = indexOfIgnoringQuotes(path, '/');
+ if (ixOfSlash > 0) {
+ return new String[] { path.substring(0, ixOfSlash),
+ path.substring(ixOfSlash) };
+ }
+ return new String[] { path };
+ }
+
+ private int indexOfIgnoringQuotes(String str, char find) {
+ return indexOfIgnoringQuotes(str, find, 0);
+ }
+
+ private int indexOfIgnoringQuotes(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;
+ }
+
+}
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..616bf70c38 100644
--- a/client/src/com/vaadin/client/debug/internal/HierarchySection.java
+++ b/client/src/com/vaadin/client/debug/internal/HierarchySection.java
@@ -15,23 +15,9 @@
*/
package com.vaadin.client.debug.internal;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import com.google.gwt.core.client.JsArray;
-import com.google.gwt.dom.client.Style.TextDecoration;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
-import com.google.gwt.event.dom.client.DoubleClickEvent;
-import com.google.gwt.event.dom.client.DoubleClickHandler;
-import com.google.gwt.event.dom.client.HasDoubleClickHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
-import com.google.gwt.event.dom.client.MouseOutEvent;
-import com.google.gwt.event.dom.client.MouseOutHandler;
-import com.google.gwt.event.dom.client.MouseOverEvent;
-import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
@@ -40,28 +26,15 @@ 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,40 @@ 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 +168,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 +220,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 +244,7 @@ public class HierarchySection implements Section {
.getNativeEvent().getClientX(), event.getNativeEvent()
.getClientY());
if (VDebugWindow.get().getElement().isOrHasChild(eventTarget)) {
- content.clear();
+ infoPanel.clear();
return;
}
@@ -654,7 +264,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..5ee3a25e2c 100644
--- a/client/src/com/vaadin/client/debug/internal/Highlight.java
+++ b/client/src/com/vaadin/client/debug/internal/Highlight.java
@@ -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..9ef6d833e2 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("&#xf0c9;"), //
OPTIMIZE("&#xf0d0;"), //
HIERARCHY("&#xf0e8;"), //
+ // TODO create more appropriate icon
+ SELECTOR("&#x2263;"), //
MENU("&#xf013;"), //
NETWORK("&#xf0ec;"), //
ANALYZE("&#xf0f0;"), //
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..409f9d14ce
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/SelectConnectorListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.client.debug.internal;
+
+import com.google.gwt.user.client.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..2ad77a246b
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/SelectorPath.java
@@ -0,0 +1,866 @@
+/*
+ * 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.core.client.JsArrayString;
+import com.google.gwt.regexp.shared.MatchResult;
+import com.google.gwt.regexp.shared.RegExp;
+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;
+import com.vaadin.client.FastStringSet;
+import com.vaadin.client.ServerConnector;
+import com.vaadin.client.Util;
+import com.vaadin.client.componentlocator.ComponentLocator;
+import com.vaadin.client.componentlocator.VaadinFinderLocatorStrategy;
+import com.vaadin.client.metadata.NoDataException;
+import com.vaadin.client.metadata.Property;
+import com.vaadin.client.metadata.TypeDataStore;
+import com.vaadin.client.ui.AbstractConnector;
+import com.vaadin.client.ui.SubPartAware;
+
+/**
+ * A single segment of a selector path with optional parent.
+ * <p>
+ * The static method {@link #findTestBenchSelector(ServerConnector, Element)}
+ * permits looking up a selector chain for an element (a selector and its
+ * parents, each selector relative to its parent).
+ * <p>
+ * The method {@link #findElement()} can be used to locate the element
+ * referenced by a {@link SelectorPath}. {@link #getJUnitSelector(String)} can
+ * be used to obtain the string to add to a JUnit test to refer to the element
+ * identified by the path.
+ * <p>
+ * This class should be considered internal to the framework and may change at
+ * any time.
+ *
+ * @since 7.1.x
+ */
+public abstract class SelectorPath {
+ private final SelectorPath parent;
+ private final ComponentLocator locator;
+
+ private static final String SUBPART_SEPARATOR = VaadinFinderLocatorStrategy.SUBPART_SEPARATOR;
+
+ /**
+ * Creates a {@link SelectorPath} from the root of the UI (without a parent)
+ * to identify an element.
+ * <p>
+ * The {@link ComponentLocator} is used to locate the corresponding
+ * {@link Element} in the context of a UI. If there are multiple UIs on a
+ * single page, the locator should correspond to the correct
+ * {@link ApplicationConnection}.
+ *
+ * @param locator
+ * {@link ComponentLocator} to use
+ */
+ protected SelectorPath(ComponentLocator locator) {
+ this(null, locator);
+ }
+
+ /**
+ * Creates a {@link SelectorPath} which is relative to another
+ * {@link SelectorPath}. to identify an element.
+ * <p>
+ * The {@link ComponentLocator} is used to locate the corresponding
+ * {@link Element} in the context of a UI. If there are multiple UIs on a
+ * single page, the locator should correspond to the correct
+ * {@link ApplicationConnection}.
+ *
+ * @param parent
+ * parent {@link SelectorPath} or null for root paths
+ * @param locator
+ * {@link ComponentLocator} to use
+ */
+ protected SelectorPath(SelectorPath parent, ComponentLocator locator) {
+ this.parent = parent;
+ this.locator = locator;
+ }
+
+ /**
+ * Returns the parent {@link SelectorPath} to which this path is relative.
+ *
+ * @return parent path
+ */
+ public SelectorPath getParent() {
+ return parent;
+ }
+
+ @Override
+ public String toString() {
+ return "SelectorPath: " + getJUnitSelector("...");
+ }
+
+ /**
+ * Returns the JUnit test fragment which can be used to refer to the element
+ * in a test.
+ *
+ * @param context
+ * the context to use (usually a variable name) or null for
+ * default
+ * @return string to add in a JUnit test
+ */
+ public abstract String getJUnitSelector(String context);
+
+ /**
+ * Returns the {@link Element} that this {@link SelectorPath} points to in
+ * the context of the {@link ComponentLocator} of the {@link SelectorPath}.
+ *
+ * @return Element identified by the path in the current UI
+ */
+ public abstract Element findElement();
+
+ /**
+ * Returns the path to an element/connector, including separate intermediate
+ * paths and the final path segment.
+ *
+ * @param connector
+ * the connector to find
+ * @param element
+ * sub-element inside connector or null to use connector root
+ * element
+ * @return Vaadin locator path
+ */
+ public static SelectorPath findTestBenchSelector(ServerConnector connector,
+ Element element) {
+ // TODO there should be a better way to locate and refer to captions -
+ // now using domChild in layout
+ SelectorPath selectorPath = null;
+ ApplicationConnection connection = connector.getConnection();
+ if (connection != null) {
+ if (null == element) {
+ element = findConnectorRootElement(connector);
+ }
+ if (null != element) {
+ ComponentLocator locator = new ComponentLocator(connection);
+ String path = locator.getPathForElement(element);
+ SelectorPath parent = null;
+
+ if (!path.isEmpty()) {
+ selectorPath = extractIdSelectorPath(path, locator);
+ if (null == selectorPath) {
+ // parent paths first if not rooted on an ID
+ if (connector.getParent() != null) {
+ parent = findTestBenchSelector(
+ connector.getParent(), null);
+ }
+
+ if (parent != null) {
+ // update path to be relative to parent
+ Element parentElement = parent.findElement();
+ if (null != parentElement) {
+ String parentPath = locator
+ .getPathForElement(parentElement);
+ if (path.startsWith(parentPath)) {
+ // remove path of parent to look for the
+ // children
+ path = path.substring(parentPath.length());
+ }
+ }
+ }
+
+ selectorPath = extractVaadinSelectorPath(path, parent,
+ locator);
+ }
+ if (null == selectorPath) {
+ if (path.startsWith("/V")) {
+ // fall-back: Vaadin
+ // this branch is needed for /VTabsheetPanel etc.
+ selectorPath = SelectorPath.vaadinPath(path,
+ parent, locator);
+ } else {
+ // fall-back: XPath
+ selectorPath = SelectorPath.xpath(path, parent,
+ locator);
+ }
+ }
+ }
+ }
+ }
+ return selectorPath;
+ }
+
+ private static SelectorPath extractIdSelectorPath(String path,
+ ComponentLocator locator) {
+ SelectorPath selectorPath = null;
+ if (path.startsWith("PID_S")) {
+ // remove internal prefix
+ path = path.substring(5);
+
+ // no parent for an ID selector
+ String pid = path;
+ String rest = null;
+ // split at first slash that is not in the subpart (if any)
+ int slashPos = path.indexOf("/");
+ int subPartPos = path.indexOf(SUBPART_SEPARATOR);
+ if (subPartPos >= 0 && slashPos > subPartPos) {
+ // ignore slashes in subpart
+ slashPos = -1;
+ } else if (slashPos >= 0 && subPartPos > slashPos) {
+ // ignore subpart after slashes - handled as a part of rest
+ subPartPos = -1;
+ }
+ // split the ID part and any relative path after it
+ if (slashPos > 0) {
+ pid = path.substring(0, slashPos);
+ rest = path.substring(slashPos);
+ }
+
+ // if there is a subpart directly after the id, need to use a Vaadin
+ // selector
+ SelectorPath pidSelector = null;
+ if (subPartPos > 0) {
+ String id = pid.substring(0, subPartPos);
+ // include the subpart separator
+ String subPart = pid.substring(subPartPos);
+ Element element = locator.getElementByPath("PID_S" + pid);
+ ComponentConnector connector = Util.findPaintable(
+ locator.getClient(), element);
+ if (null != connector && null != connector.getWidget()) {
+ String type = connector.getWidget().getClass()
+ .getSimpleName();
+ pidSelector = SelectorPath.vaadinPath("//" + type
+ + "[id=\\\"" + id + "\\\"]" + subPart, null,
+ locator);
+ } else {
+ // no valid connector for the subpart
+ return null;
+ }
+ } else {
+ pidSelector = SelectorPath.id(pid, locator);
+ }
+ if (null != rest && !rest.isEmpty()) {
+ selectorPath = extractVaadinSelectorPath(path, pidSelector,
+ locator);
+ if (selectorPath == null) {
+ selectorPath = SelectorPath.xpath(rest, pidSelector,
+ locator);
+ }
+ } else {
+ selectorPath = pidSelector;
+ }
+ }
+ return selectorPath;
+ }
+
+ private static SelectorPath extractVaadinSelectorPath(String path,
+ SelectorPath parent, ComponentLocator locator) {
+ SelectorPath selectorPath = null;
+
+ String xpathPart = null;
+ int xpathPos = Math.min(path.indexOf("/div"), path.indexOf("/span"));
+ if (xpathPos >= 0) {
+ xpathPart = path.substring(xpathPos);
+ path = path.substring(0, xpathPos);
+ }
+
+ String subPartPart = null;
+ int subPartPos = path.indexOf("#");
+ if (subPartPos >= 0) {
+ subPartPart = path.substring(subPartPos + 1);
+ path = path.substring(0, subPartPos);
+ }
+
+ String domChildPart = null;
+ int domChildPos = path.indexOf("/domChild");
+ if (domChildPos >= 0) {
+ // include the slash
+ domChildPart = path.substring(domChildPos);
+ path = path.substring(0, domChildPos);
+ }
+
+ // is it something VaadinSelectorPath can handle?
+ String widgetClass = null;
+ // first cases in a layout slot
+ RegExp widgetInSlotMatcher = RegExp
+ .compile("^/(Slot\\[(\\d+)\\]/)([a-zA-Z]+)(\\[0\\])?$");
+ MatchResult matchResult = widgetInSlotMatcher.exec(path);
+ if (null != matchResult) {
+ if (matchResult.getGroupCount() >= 3) {
+ widgetClass = matchResult.getGroup(3);
+ }
+ }
+ // handle cases without intervening slot
+ if (null == widgetClass) {
+ RegExp widgetDirectlyMatcher = RegExp
+ .compile("^//?([a-zA-Z]+)(\\[(\\d+)\\])?$");
+ matchResult = widgetDirectlyMatcher.exec(path);
+ if (null != matchResult) {
+ if (matchResult.getGroupCount() >= 1) {
+ widgetClass = matchResult.getGroup(1);
+ }
+ }
+ }
+ if (null != widgetClass && !widgetClass.isEmpty()) {
+ selectorPath = findVaadinSelectorInParent(path, widgetClass,
+ parent, locator);
+ if (null != subPartPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ ((VaadinSelectorPath) selectorPath).setSubPart(subPartPart);
+ } else if (null != xpathPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ // try to find sub-part if supported
+ ComponentConnector connector = Util.findPaintable(
+ locator.getClient(), selectorPath.findElement());
+ if (connector != null
+ && connector.getWidget() instanceof SubPartAware) {
+ // for SubPartAware, skip the XPath fall-back path
+ Element element = locator.getElementByPathStartingAt(path,
+ selectorPath.findElement());
+ SubPartAware subPartAware = (SubPartAware) connector
+ .getWidget();
+ String subPart = subPartAware.getSubPartName(element);
+ if (null != subPart) {
+ // type checked above
+ ((VaadinSelectorPath) selectorPath).setSubPart(subPart);
+ }
+ } else {
+ // fall-back to XPath for the last part of the path
+ selectorPath = SelectorPath.xpath(xpathPart, selectorPath,
+ locator);
+ }
+ }
+
+ // the whole /domChild[i]/domChild[j]... part as a single selector
+ if (null != domChildPart
+ && selectorPath instanceof VaadinSelectorPath) {
+ selectorPath = SelectorPath.vaadinPath(domChildPart,
+ selectorPath, locator);
+ }
+ } else if (null != domChildPart) {
+ // cases with domChild path only (parent contains rest)
+ selectorPath = SelectorPath.vaadinPath(domChildPart, parent,
+ locator);
+ }
+ return selectorPath;
+ }
+
+ /**
+ * Find the zero-based index of the widget of type widgetClass identified by
+ * path within its parent and returns the corresponding Vaadin path (if
+ * any). For instance, the second button in a layout has index 1 regardless
+ * of non-button components in the parent.
+ * <p>
+ * The approach used internally is to try to find the caption of the element
+ * inside its parent and check whether it is sufficient to identify the
+ * element correctly. If not, possible indices are looped through to see if
+ * the component of the specified type within the specified parent
+ * identifies the correct element. This is inefficient but more reliable
+ * than some alternative approaches, and does not require special cases for
+ * various layouts etc.
+ *
+ * @param path
+ * relative path for the widget of interest
+ * @param widgetClass
+ * type of the widget of interest
+ * @param parent
+ * parent component to which the path is relative
+ * @param locator
+ * ComponentLocator used to map paths to elements
+ * @return selector path for the element, null if none found
+ */
+ private static SelectorPath findVaadinSelectorInParent(String path,
+ String widgetClass, SelectorPath parent, ComponentLocator locator) {
+ if (null == parent) {
+ SelectorPath selectorPath = SelectorPath.vaadin(widgetClass, 0,
+ null, locator);
+ if (selectorPath.findElement() == locator.getElementByPath(path)) {
+ return selectorPath;
+ } else {
+ return null;
+ }
+ }
+ // This method uses an inefficient brute-force approach but its
+ // results should match what is used by the TestBench selectors.
+ Element parentElement = parent.findElement();
+ String parentPathString = locator.getPathForElement(parentElement);
+ if (null == parentPathString) {
+ parentPathString = "";
+ }
+ Element elementToFind = locator.getElementByPath(parentPathString
+ + path);
+ if (null == elementToFind) {
+ return null;
+ }
+ // if the connector has a caption, first try if the element can be
+ // located in parent with it; if that fails, use the index in parent
+ String caption = getCaptionForElement(elementToFind, locator);
+ if (null != caption) {
+ SelectorPath testPath = SelectorPath.vaadin(widgetClass, caption,
+ parent, locator);
+ Element testElement = testPath.findElement();
+ // TODO in theory could also iterate upwards into parents, using
+ // "//" before the caption to find the shortest matching path that
+ // identifies the correct element
+ if (testElement == elementToFind) {
+ return testPath;
+ }
+ }
+
+ // Assumes that the number of logical child elements is at most the
+ // number of direct children of the DOM element - e.g. layouts have a
+ // single component per slot.
+ for (int i = 0; i < parentElement.getChildCount(); ++i) {
+ SelectorPath testPath = SelectorPath.vaadin(widgetClass, i, parent,
+ locator);
+ Element testElement = testPath.findElement();
+ if (testElement == elementToFind) {
+ return testPath;
+ }
+ }
+ return null;
+ }
+
+ private static String getCaptionForElement(Element element,
+ ComponentLocator locator) {
+ String caption = null;
+ ComponentConnector connector = Util.findPaintable(locator.getClient(),
+ element);
+ if (null != connector) {
+ Property property = AbstractConnector.getStateType(connector)
+ .getProperty("caption");
+ try {
+ Object value = property.getValue(connector.getState());
+ if (null != value) {
+ caption = String.valueOf(value);
+ }
+ } catch (NoDataException e) {
+ // skip the caption based selection and use index below
+ }
+ }
+ return caption;
+ }
+
+ private static Element findConnectorRootElement(ServerConnector connector) {
+ Element element = null;
+ // try to find the root element of the connector
+ if (connector instanceof ComponentConnector) {
+ Widget widget = ((ComponentConnector) connector).getWidget();
+ if (widget != null) {
+ element = widget.getElement();
+ }
+ }
+ return element;
+ }
+
+ public ComponentLocator getLocator() {
+ return locator;
+ }
+
+ @Override
+ public int hashCode() {
+ return getJUnitSelector("context").hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SelectorPath other = (SelectorPath) obj;
+ if (parent == null) {
+ if (other.parent != null) {
+ return false;
+ }
+ } else if (!parent.equals(other.parent)) {
+ return false;
+ }
+ if (!other.getJUnitSelector("context").equals(
+ getJUnitSelector("context"))) {
+ return false;
+ }
+ return true;
+ }
+
+ protected static SelectorPath xpath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ return new XPathSelectorPath(path, parent, locator);
+ }
+
+ protected static SelectorPath id(String id, ComponentLocator locator) {
+ return new IdSelectorPath(id, locator);
+ }
+
+ protected static SelectorPath vaadin(String widgetClass,
+ String widgetCaption, SelectorPath parent, ComponentLocator locator) {
+ return new VaadinSelectorPath(widgetClass, widgetCaption, 0, parent,
+ locator);
+ }
+
+ protected static SelectorPath vaadin(String widgetClass, int widgetIndex,
+ SelectorPath parent, ComponentLocator locator) {
+ return new VaadinSelectorPath(widgetClass, null, widgetIndex, parent,
+ locator);
+ }
+
+ protected static SelectorPath vaadinPath(String vaadinPath,
+ SelectorPath parent, ComponentLocator locator) {
+ return new ByVaadinSelectorPath(vaadinPath, parent, locator);
+ }
+
+ /**
+ * Selector path for finding an {@link Element} based on an XPath (relative
+ * to the parent {@link SelectorPath}).
+ */
+ private static class XPathSelectorPath extends SelectorPath {
+ // path segment relative to parent
+ private final String path;
+
+ /**
+ * Creates a relative XPath based component selector path.
+ *
+ * @param path
+ * XPath
+ * @param parent
+ * {@link SelectorPath} to which the XPath is relative, null
+ * if from the root
+ * @param locator
+ * ComponentLocator to use to find the element
+ */
+ public XPathSelectorPath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ this.path = path;
+ }
+
+ /**
+ * Returns the XPath relative to the parent element.
+ *
+ * @return relative path string
+ */
+ public String getPath() {
+ return path;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ // use driver by default
+ String contextString = null != context ? context : "getDriver()";
+ return contextString + ".findElement(By.xpath(\"" + getPath()
+ + "\"))";
+ }
+
+ @Override
+ public Element findElement() {
+ if (null != getParent()) {
+ Element parentElement = getParent().findElement();
+ if (null == parentElement) {
+ // broken path - possibly removed parent
+ return null;
+ }
+ Element element = getLocator().getElementByPathStartingAt(
+ getPath(), parentElement);
+ return element;
+ } else {
+ Element element = getLocator().getElementByPath(getPath());
+ return element;
+ }
+ }
+ }
+
+ /**
+ * Element identifier based locator path.
+ * <p>
+ * Identifier paths never have a parent and the identifiers should be unique
+ * within the context of the {@link ComponentLocator}/page.
+ */
+ private static class IdSelectorPath extends SelectorPath {
+ private final String id;
+
+ /**
+ * Creates an identifier based {@link SelectorPath}. The identifier
+ * should not contain the old "PID_S" prefix.
+ *
+ * @param id
+ * @param locator
+ */
+ public IdSelectorPath(String id, ComponentLocator locator) {
+ super(locator);
+ this.id = id;
+ }
+
+ /**
+ * Returns the ID in the DOM used to identify the element.
+ *
+ * @return Vaadin debug ID or equivalent
+ */
+ public String getId() {
+ return id;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ String contextPart = null != context ? ", " + context : "";
+ return "getElementById(\"" + getId() + "\"" + contextPart + ")";
+ }
+
+ @Override
+ public Element findElement() {
+ // this also works for IDs
+ return getLocator().getElementByPath("PID_S" + getId());
+ }
+ }
+
+ /**
+ * Common base class for Vaadin selector paths (By.vaadin(...)).
+ */
+ private static abstract class AbstractVaadinSelectorPath extends
+ SelectorPath {
+
+ protected AbstractVaadinSelectorPath(SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ }
+
+ /**
+ * Returns the {@link ComponentLocator} path of the element relative to
+ * the parent path.
+ *
+ * @return path of the element for By.vaadin(...)
+ */
+ protected abstract String getPath();
+
+ @Override
+ public Element findElement() {
+ if (null != getParent()) {
+ Element parentElement = getParent().findElement();
+ Element element = getLocator().getElementByPathStartingAt(
+ getPath(), parentElement);
+ return element;
+ } else {
+ return getLocator().getElementByPath(getPath());
+ }
+ }
+
+ }
+
+ /**
+ * TestBench selector path for Vaadin widgets. These selectors are based on
+ * the widget class and either the index among the widgets of that type in
+ * the parent or the widget caption.
+ */
+ private static class VaadinSelectorPath extends AbstractVaadinSelectorPath {
+ private final String widgetClass;
+ private final String widgetCaption;
+ // negative for no index
+ private final int widgetIndex;
+ private String subPart;
+
+ /**
+ * Creates a Vaadin {@link SelectorPath}. The path identifies an element
+ * of a given type under its parent based on either its caption or its
+ * index (if both are given, only the caption is used). See also
+ * {@link ComponentLocator} for more details.
+ *
+ * @param widgetClass
+ * client-side widget class
+ * @param widgetCaption
+ * caption of the widget - null to use the index instead
+ * @param widgetIndex
+ * index of the widget of the type within its parent, used
+ * only if the caption is not given
+ * @param parent
+ * parent {@link SelectorPath} or null
+ * @param locator
+ * component locator to use to find the corresponding
+ * {@link Element}
+ */
+ public VaadinSelectorPath(String widgetClass, String widgetCaption,
+ int widgetIndex, SelectorPath parent, ComponentLocator locator) {
+ super(parent, locator);
+ this.widgetClass = widgetClass;
+ this.widgetCaption = widgetCaption;
+ this.widgetIndex = widgetIndex;
+ }
+
+ /**
+ * Returns the widget type used to identify the element.
+ *
+ * @return Vaadin widget class
+ */
+ public String getWidgetClass() {
+ return widgetClass;
+ }
+
+ /**
+ * Returns the widget caption to look for or null if index is used
+ * instead.
+ *
+ * @return widget caption to match
+ */
+ public String getWidgetCaption() {
+ return widgetCaption;
+ }
+
+ /**
+ * Returns the index of the widget of that type within its parent - only
+ * used if caption is null.
+ *
+ * @return widget index
+ */
+ public int getWidgetIndex() {
+ return widgetIndex;
+ }
+
+ /**
+ * Returns the sub-part string (e.g. row and column identifiers within a
+ * table) used to identify a part of a component. See
+ * {@link ComponentLocator} and especially Vaadin selectors for more
+ * information.
+ *
+ * @return sub-part string or null if none
+ */
+ public String getSubPart() {
+ return subPart;
+ }
+
+ /**
+ * Sets the sub-part string (e.g. row and column identifiers within a
+ * table) used to identify a part of a component. See
+ * {@link ComponentLocator} and especially Vaadin selectors for more
+ * information.
+ *
+ * @param subPart
+ * sub-part string to use or null for none
+ */
+ public void setSubPart(String subPart) {
+ this.subPart = subPart;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ String componentClass = getComponentClass();
+ String contextPart = null != context ? ", " + context : "";
+ // TODO update after subpart API finished
+ if (null != getSubPart() || null == componentClass) {
+ return "getElementByPath(\"" + getPath() + "\"" + contextPart
+ + ")";
+ } else if (null != getWidgetCaption()) {
+ return "getElementByCaption(" + componentClass + ".class, \""
+ + getWidgetCaption() + "\"" + contextPart + ")";
+ } else if (getWidgetIndex() >= 0) {
+ return "getElementByIndex(" + componentClass + ".class, "
+ + getWidgetIndex() + contextPart + ")";
+ } else {
+ return "getElement(" + componentClass + ".class" + contextPart
+ + ")";
+ }
+ }
+
+ /**
+ * Returns the Vaadin server side component class to use for a widget
+ * class.
+ *
+ * @return fully qualified server side class name, null if unable to
+ * determine it
+ */
+ private String getComponentClass() {
+ ComponentConnector connector = Util.findPaintable(getLocator()
+ .getClient(), findElement());
+ Class<? extends ServerConnector> connectorClass = connector
+ .getClass();
+ FastStringSet identifiers = TypeDataStore.get().findIdentifiersFor(
+ connectorClass);
+ JsArrayString ids = identifiers.dump();
+ if (ids.length() == 1) {
+ return ids.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ // these are used only to locate components on the client side by path
+
+ @Override
+ protected String getPath() {
+ return "/" + getWidgetClass() + getIndexString(false)
+ + getSubPartPostfix();
+ }
+
+ private String getIndexString(boolean escapeQuotes) {
+ if (null != getWidgetCaption()) {
+ if (escapeQuotes) {
+ return "[caption=\\\"" + widgetCaption + "\\\"]";
+ } else {
+ return "[caption=\"" + widgetCaption + "\"]";
+ }
+ } else if (widgetIndex >= 0) {
+ return "[" + getWidgetIndex() + "]";
+ } else {
+ return "";
+ }
+ }
+
+ private String getSubPartPostfix() {
+ String subPartString = "";
+ if (null != getSubPart()) {
+ subPartString = SUBPART_SEPARATOR + getSubPart();
+ }
+ return subPartString;
+ }
+ }
+
+ /**
+ * TestBench selector path for Vaadin widgets, always using a
+ * By.vaadin(path) rather than other convenience methods.
+ */
+ private static class ByVaadinSelectorPath extends
+ AbstractVaadinSelectorPath {
+ private final String path;
+
+ /**
+ * Vaadin selector path for an exact path (including any preceding
+ * slash).
+ *
+ * @param path
+ * path of the element (normally with a leading slash), not
+ * null
+ * @param parent
+ * parent selector path or null if none
+ * @param locator
+ * ComponentLocator to use to find the corresponding element
+ */
+ public ByVaadinSelectorPath(String path, SelectorPath parent,
+ ComponentLocator locator) {
+ super(parent, locator);
+ this.path = path;
+ }
+
+ @Override
+ public String getJUnitSelector(String context) {
+ String contextPart = null != context ? ", " + context : "";
+ return "getElementByPath(\"" + getPath() + "\"" + contextPart + ")";
+ }
+
+ /**
+ * Returns the By.vaadin(...) path relative to the parent element.
+ *
+ * @return relative path string
+ */
+ @Override
+ public String getPath() {
+ return path;
+ }
+ }
+} \ 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..462309768f
--- /dev/null
+++ b/client/src/com/vaadin/client/debug/internal/TestBenchSection.java
@@ -0,0 +1,307 @@
+/*
+ * 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.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.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.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 static int selectorCounter = 1;
+
+ final private SelectorPath path;
+ final private SelectorWidget parent;
+ final private int selectorIndex = selectorCounter++;
+
+ public SelectorWidget(final SelectorPath path,
+ final SelectorWidget parent) {
+ this.path = path;
+ this.parent = parent;
+
+ String parentString = (parent != null) ? ("element" + parent.selectorIndex)
+ : null;
+ String html = "<div class=\""
+ + VDebugWindow.STYLENAME
+ + "-selector\"><span class=\"tb-selector\">"
+ + Util.escapeHTML("WebElement element" + selectorIndex
+ + " = " + path.getJUnitSelector(parentString) + ";")
+ + "</span></div>";
+ setHTML(html);
+
+ addMouseOverHandler(this);
+ addMouseOutHandler(this);
+ }
+
+ @Override
+ public void onMouseOver(MouseOverEvent event) {
+ ApplicationConnection a = path.getLocator().getClient();
+ Element element = path.findElement();
+ if (null != element) {
+ Highlight.hideAll();
+ Highlight.show(element);
+ }
+ }
+
+ @Override
+ public void onMouseOut(MouseOutEvent event) {
+ Highlight.hideAll();
+ }
+ }
+
+ private final DebugButton tabButton = new DebugButton(Icon.SELECTOR,
+ "Pick Vaadin TestBench selectors");
+
+ private final FlowPanel content = new FlowPanel();
+
+ private final HierarchyPanel hierarchyPanel = new HierarchyPanel();
+
+ 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,
+ "Select a component on the page to inspect it");
+ private final Button refreshHierarchy = new DebugButton(Icon.HIERARCHY,
+ "Refresh the connector hierarchy tree");
+
+ private HandlerRegistration highlightModeRegistration = null;
+
+ public TestBenchSection() {
+ controls.add(refreshHierarchy);
+ refreshHierarchy.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ refreshHierarchy.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ hierarchyPanel.update();
+ }
+ });
+
+ controls.add(find);
+ find.setStylePrimaryName(VDebugWindow.STYLENAME_BUTTON);
+ find.addClickHandler(new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ toggleFind();
+ }
+ });
+
+ hierarchyPanel.addListener(new SelectConnectorListener() {
+ @Override
+ public void select(ServerConnector connector, Element element) {
+ pickSelector(connector, element);
+ }
+ });
+
+ content.setStylePrimaryName(VDebugWindow.STYLENAME + "-testbench");
+ content.add(hierarchyPanel);
+ 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);
+ }
+ }
+
+ private void pickSelector(ServerConnector connector, Element element) {
+ SelectorPath path = SelectorPath.findTestBenchSelector(connector,
+ element);
+
+ if (null != path) {
+ addSelectorWidgets(path);
+ }
+ }
+
+ private SelectorWidget addSelectorWidgets(SelectorPath path) {
+ // add selector widgets recursively from root towards children, reusing
+ // old ones
+ SelectorPath parent = path.getParent();
+ SelectorWidget parentWidget = null;
+ if (null != parent) {
+ parentWidget = addSelectorWidgets(parent);
+ }
+ SelectorWidget widget = selectorWidgets.get(path);
+ if (null == widget) {
+ // the parent has already been added above
+ widget = new SelectorWidget(path, parentWidget);
+ selectorWidgets.put(path, widget);
+ selectorPanel.add(widget);
+ }
+ return widget;
+ }
+
+ 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)) {
+ 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;
+ }
+
+}
diff --git a/client/src/com/vaadin/client/metadata/TypeDataStore.java b/client/src/com/vaadin/client/metadata/TypeDataStore.java
index aa37d75dc8..649f018f95 100644
--- a/client/src/com/vaadin/client/metadata/TypeDataStore.java
+++ b/client/src/com/vaadin/client/metadata/TypeDataStore.java
@@ -69,6 +69,22 @@ 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);
}
diff --git a/client/src/com/vaadin/client/ui/SubPartAware.java b/client/src/com/vaadin/client/ui/SubPartAware.java
index a7d72fab01..36959e7b1f 100644
--- a/client/src/com/vaadin/client/ui/SubPartAware.java
+++ b/client/src/com/vaadin/client/ui/SubPartAware.java
@@ -17,7 +17,7 @@ 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
@@ -59,4 +59,4 @@ public interface SubPartAware {
*/
String getSubPartName(Element subElement);
-} \ No newline at end of file
+}
diff --git a/client/src/com/vaadin/client/ui/VScrollTable.java b/client/src/com/vaadin/client/ui/VScrollTable.java
index c56a2a8772..5542db01b5 100644
--- a/client/src/com/vaadin/client/ui/VScrollTable.java
+++ b/client/src/com/vaadin/client/ui/VScrollTable.java
@@ -61,6 +61,8 @@ 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;
@@ -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";
@@ -5043,6 +5045,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 this.firstRendered + relIx;
+ }
+ return -1;
+ }
+
public class VScrollTableRow extends Panel implements ActionOwner {
private static final int TOUCHSCROLL_TIMEOUT = 100;
@@ -7751,4 +7767,94 @@ 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 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(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/publish.xml b/publish.xml
index d37087d8f9..2fafb68246 100644
--- a/publish.xml
+++ b/publish.xml
@@ -20,6 +20,9 @@
<property name="file.war" location="result/artifacts/${vaadin.version}/vaadin-uitest/vaadin-uitest-${version}.war" />
<property name="target" value="${nightly.tests.publish}/${vaadin.version.major}.${vaadin.version.minor}-${build.tag}.war" />
+ <fail unless="ant-jsch.present" message="Please install ant-jsch.jar into ANT_HOME/lib" />
+ <fail unless="jsch.present" message="Please install jsch.jar into ANT_HOME/lib" />
+
<echo>Installing ${src} to ${target}</echo>
<scp todir="${nightly.tests.publish}" file="${file.war}">
@@ -34,7 +37,7 @@
<antcontrib:foreach list="${modules.to.publish.to.maven}" target="publish.module.to.maven" param="module" />
</target>
- <target name="publish.module.to.download.site">
+ <target name="publish.module.to.download.site">
<fail unless="module" message="No module to publish defined" />
<ivy:resolve log="download-only" file="${module}/ivy.xml" />
<ivy:publish publishivy="false" settingsref="publish.settings" conf="*(public)" resolver="sftp-publish">
@@ -65,4 +68,31 @@
</artifact:mvn>
</target>
+ <!-- Use this to publish to local Maven repo -->
+ <!-- If you have compiled a snapshot build with: -->
+ <!-- ant -Dvaadin.version=7.x.x.zzz package -->
+ <!-- Publish with: -->
+ <!-- ant -f publish.xml -Dvaadin.version=7.x.x.zzz local.maven.publish -->
+ <!-- Note that if the build is a snapshot build, it will be installed as -->
+ <!-- 7.x-SNAPSHOT. -->
+ <target name="local.maven.publish">
+ <antcontrib:foreach list="${modules.to.publish.to.maven}" target="publish.module.to.local.maven" param="module" />
+ </target>
+
+ <target name="publish.module.to.local.maven">
+ <fail unless="module" message="No module to publish defined" />
+
+ <property name="jar.file" location="result/artifacts/${vaadin.version}/vaadin-${module}/vaadin-${module}-${vaadin.version}.jar" />
+ <property name="javadoc.file" location="result/artifacts/${vaadin.version}/vaadin-${module}/vaadin-${module}-${vaadin.version}-javadoc.jar" />
+ <property name="sources.file" location="result/artifacts/${vaadin.version}/vaadin-${module}/vaadin-${module}-${vaadin.version}-sources.jar" />
+ <property name="pom.file" location="result/artifacts/${vaadin.version}/vaadin-${module}/vaadin-${module}-${vaadin.version}.pom" />
+
+ <artifact:mvn failonerror="true">
+ <arg value="install:install-file" />
+ <sysproperty key="file" value="${jar.file}" />
+ <sysproperty key="pomFile" value="${pom.file}" />
+ <sysproperty key="javadoc" value="${javadoc.file}" />
+ <sysproperty key="sources" value="${sources.file}" />
+ </artifact:mvn>
+ </target>
</project>
diff --git a/uitest/src/com/vaadin/tests/application/calculator/Calc.java b/uitest/src/com/vaadin/tests/application/calculator/Calc.java
new file mode 100644
index 0000000000..7911556f4e
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/application/calculator/Calc.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.tests.application.calculator;
+
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Table.ColumnHeaderMode;
+import com.vaadin.ui.TextField;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+
+@SuppressWarnings("serial")
+public class Calc extends AbstractTestUI {
+
+ private class Log extends VerticalLayout {
+
+ private Table table;
+ private Button addCommentButton;
+ private int line = 0;
+
+ public Log() {
+ super();
+
+ table = new Table();
+ table.setSizeFull();
+
+ setWidth("200px");
+ setHeight("100%");
+
+ table.setColumnHeaderMode(ColumnHeaderMode.HIDDEN);
+ table.addContainerProperty("Operation", String.class, "");
+
+ addComponent(table);
+
+ addCommentButton = new Button("Add Comment");
+ addCommentButton.setWidth("100%");
+
+ addCommentButton.addClickListener(new ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+
+ final Window w = new Window("Add comment");
+ VerticalLayout vl = new VerticalLayout();
+ vl.setMargin(true);
+
+ final TextField tf = new TextField();
+ tf.setSizeFull();
+ vl.addComponent(tf);
+
+ HorizontalLayout hl = new HorizontalLayout();
+
+ Button okButton = new Button("OK");
+ okButton.setWidth("100%");
+ okButton.addClickListener(new ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ addRow("[ " + tf.getValue() + " ]");
+ tf.setValue("");
+ w.close();
+ removeWindow(w);
+ }
+ });
+
+ Button cancelButton = new Button("Cancel");
+ cancelButton.setWidth("100%");
+ cancelButton.addClickListener(new ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ tf.setValue("");
+ w.close();
+ removeWindow(w);
+ }
+ });
+
+ hl.addComponent(cancelButton);
+ hl.addComponent(okButton);
+ hl.setSpacing(true);
+ hl.setWidth("100%");
+
+ vl.addComponent(hl);
+ vl.setSpacing(true);
+
+ w.setContent(vl);
+ addWindow(w);
+ }
+ });
+
+ addComponent(addCommentButton);
+
+ setExpandRatio(table, 1);
+ setSpacing(true);
+ }
+
+ public void addRow(String row) {
+ Integer id = ++line;
+ table.addItem(new Object[] { row }, id);
+ table.setCurrentPageFirstItemIndex(line + 1);
+ }
+
+ }
+
+ // All variables are automatically stored in the session.
+ private Double current = 0.0;
+ private double stored = 0.0;
+ private char lastOperationRequested = 'C';
+ private VerticalLayout topLayout = new VerticalLayout();
+
+ // User interface components
+ private final TextField display = new TextField();
+
+ private final Log log = new Log();
+
+ // Calculator "business logic" implemented here to keep the example
+ // minimal
+ private double calculate(char requestedOperation) {
+ if ('0' <= requestedOperation && requestedOperation <= '9') {
+ if (current == null) {
+ current = 0.0;
+ }
+ current = current * 10
+ + Double.parseDouble("" + requestedOperation);
+ return current;
+ }
+
+ if (current == null) {
+ current = stored;
+ }
+ switch (lastOperationRequested) {
+ case '+':
+ stored += current;
+ break;
+ case '-':
+ stored -= current;
+ break;
+ case '/':
+ stored /= current;
+ break;
+ case '*':
+ stored *= current;
+ break;
+ default:
+ stored = current;
+ break;
+ }
+
+ switch (requestedOperation) {
+ case '+':
+ log.addRow(current + " +");
+ break;
+ case '-':
+ log.addRow(current + " -");
+ break;
+ case '/':
+ log.addRow(current + " /");
+ break;
+ case '*':
+ log.addRow(current + " x");
+ break;
+ case '=':
+ log.addRow(current + " =");
+ log.addRow("------------");
+ log.addRow("" + stored);
+ break;
+ }
+
+ lastOperationRequested = requestedOperation;
+ current = null;
+ if (requestedOperation == 'C') {
+ log.addRow("0.0");
+ stored = 0.0;
+ }
+ return stored;
+ }
+
+ @Override
+ protected void setup(VaadinRequest request) {
+ setContent(topLayout);
+
+ // Create the main layout for our application (4 columns, 5 rows)
+ final GridLayout layout = new GridLayout(4, 5);
+
+ topLayout.setMargin(true);
+ topLayout.setSpacing(true);
+ Label title = new Label("Calculator");
+ topLayout.addComponent(title);
+ topLayout.addComponent(log);
+
+ HorizontalLayout horizontalLayout = new HorizontalLayout();
+ horizontalLayout.setSpacing(true);
+ horizontalLayout.addComponent(layout);
+ horizontalLayout.addComponent(log);
+ topLayout.addComponent(horizontalLayout);
+
+ // Create a result label that over all 4 columns in the first row
+ layout.setSpacing(true);
+ layout.addComponent(display, 0, 0, 3, 0);
+ layout.setComponentAlignment(display, Alignment.MIDDLE_RIGHT);
+ display.setSizeFull();
+ display.setId("display");
+ display.setValue("0.0");
+
+ // The operations for the calculator in the order they appear on the
+ // screen (left to right, top to bottom)
+ String[] operations = new String[] { "7", "8", "9", "/", "4", "5", "6",
+ "*", "1", "2", "3", "-", "0", "=", "C", "+" };
+
+ for (String caption : operations) {
+
+ // Create a button and use this application for event handling
+ Button button = new Button(caption);
+ button.setWidth("40px");
+ button.addClickListener(new ClickListener() {
+ @Override
+ public void buttonClick(ClickEvent event) {
+ // Get the button that was clicked
+ Button button = event.getButton();
+
+ // Get the requested operation from the button caption
+ char requestedOperation = button.getCaption().charAt(0);
+
+ // Calculate the new value
+ double newValue = calculate(requestedOperation);
+
+ // Update the result label with the new value
+ display.setValue("" + newValue);
+ }
+ });
+ button.setId("button_" + caption);
+
+ // Add the button to our main layout
+ layout.addComponent(button);
+ }
+
+ }
+
+ @Override
+ protected String getTestDescription() {
+ return "Provide test application for generic testing purposes";
+ }
+
+ @Override
+ protected Integer getTicketNumber() {
+ return 12444;
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/gwtadapter/componentlocator/TestDetachedNotPresent.html b/uitest/src/com/vaadin/tests/componentlocator/TestDetachedNotPresent.html
index 24e5e992ca..24e5e992ca 100644
--- a/uitest/src/com/vaadin/tests/gwtadapter/componentlocator/TestDetachedNotPresent.html
+++ b/uitest/src/com/vaadin/tests/componentlocator/TestDetachedNotPresent.html
diff --git a/uitest/src/com/vaadin/tests/components/ui/UIAccess.java b/uitest/src/com/vaadin/tests/components/ui/UIAccess.java
deleted file mode 100644
index d036827159..0000000000
--- a/uitest/src/com/vaadin/tests/components/ui/UIAccess.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright 2000-2013 Vaadin Ltd.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.vaadin.tests.components.ui;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.locks.ReentrantLock;
-
-import com.vaadin.server.VaadinRequest;
-import com.vaadin.server.VaadinService;
-import com.vaadin.server.VaadinSession;
-import com.vaadin.shared.communication.PushMode;
-import com.vaadin.tests.components.AbstractTestUIWithLog;
-import com.vaadin.ui.Button;
-import com.vaadin.ui.Button.ClickEvent;
-import com.vaadin.ui.UI;
-import com.vaadin.util.CurrentInstance;
-
-public class UIAccess extends AbstractTestUIWithLog {
-
- private volatile boolean checkCurrentInstancesBeforeResponse = false;
-
- private Future<Void> checkFromBeforeClientResponse;
-
- private class CurrentInstanceTestType {
- private String value;
-
- public CurrentInstanceTestType(String value) {
- this.value = value;
- }
-
- @Override
- public String toString() {
- return value;
- }
- }
-
- @Override
- protected void setup(VaadinRequest request) {
- addComponent(new Button("Access from UI thread",
- new Button.ClickListener() {
-
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- // Ensure beforeClientResponse is invoked
- markAsDirty();
- checkFromBeforeClientResponse = access(new Runnable() {
- @Override
- public void run() {
- log("Access from UI thread is run");
- }
- });
- log("Access from UI thread future is done? "
- + checkFromBeforeClientResponse.isDone());
- }
- }));
- addComponent(new Button("Access from background thread",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- final CountDownLatch latch = new CountDownLatch(1);
-
- new Thread() {
- @Override
- public void run() {
- final boolean threadHasCurrentResponse = VaadinService
- .getCurrentResponse() != null;
- // session is locked by request thread at this
- // point
- final Future<Void> initialFuture = access(new Runnable() {
- @Override
- public void run() {
- log("Initial background message");
- log("Thread has current response? "
- + threadHasCurrentResponse);
- }
- });
-
- // Let request thread continue
- latch.countDown();
-
- // Wait until thread can be locked
- while (!getSession().getLockInstance()
- .tryLock()) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
- try {
- log("Thread got lock, inital future done? "
- + initialFuture.isDone());
- setPollInterval(-1);
- } finally {
- getSession().unlock();
- }
- }
- }.start();
-
- // Wait for thread to do initialize before continuing
- try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
-
- setPollInterval(3000);
- }
- }));
- addComponent(new Button("Access throwing exception",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- final Future<Void> firstFuture = access(new Runnable() {
- @Override
- public void run() {
- log("Throwing exception in access");
- throw new RuntimeException(
- "Catch me if you can");
- }
- });
- access(new Runnable() {
- @Override
- public void run() {
- log("firstFuture is done? "
- + firstFuture.isDone());
- try {
- firstFuture.get();
- log("Should not get here");
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- } catch (ExecutionException e) {
- log("Got exception from firstFuture: "
- + e.getMessage());
- }
- }
- });
- }
- }));
- addComponent(new Button("Cancel future before started",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- Future<Void> future = access(new Runnable() {
- @Override
- public void run() {
- log("Should not get here");
- }
- });
- future.cancel(false);
- log("future was cancled, should not start");
- }
- }));
- addComponent(new Button("Cancel running future",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- final ReentrantLock interruptLock = new ReentrantLock();
-
- final Future<Void> future = access(new Runnable() {
- @Override
- public void run() {
- log("Waiting for thread to start");
- while (!interruptLock.isLocked()) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- log("Premature interruption");
- throw new RuntimeException(e);
- }
- }
-
- log("Thread started, waiting for interruption");
- try {
- interruptLock.lockInterruptibly();
- } catch (InterruptedException e) {
- log("I was interrupted");
- }
- }
- });
-
- new Thread() {
- @Override
- public void run() {
- interruptLock.lock();
- // Wait until UI thread has started waiting for
- // the lock
- while (!interruptLock.hasQueuedThreads()) {
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
-
- future.cancel(true);
- }
- }.start();
- }
- }));
- addComponent(new Button("CurrentInstance accessSynchronously values",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- // accessSynchronously should maintain values
- CurrentInstance.set(CurrentInstanceTestType.class,
- new CurrentInstanceTestType(
- "Set before accessSynchronosly"));
- accessSynchronously(new Runnable() {
- @Override
- public void run() {
- log.log("accessSynchronously has request? "
- + (VaadinService.getCurrentRequest() != null));
- log.log("Test value in accessSynchronously: "
- + CurrentInstance
- .get(CurrentInstanceTestType.class));
- CurrentInstance.set(
- CurrentInstanceTestType.class,
- new CurrentInstanceTestType(
- "Set in accessSynchronosly"));
- }
- });
- log.log("has request after accessSynchronously? "
- + (VaadinService.getCurrentRequest() != null));
- log("Test value after accessSynchornously: "
- + CurrentInstance
- .get(CurrentInstanceTestType.class));
- }
- }));
- addComponent(new Button("CurrentInstance access values",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- // accessSynchronously should maintain values
- CurrentInstance
- .setInheritable(CurrentInstanceTestType.class,
- new CurrentInstanceTestType(
- "Set before access"));
- access(new Runnable() {
- @Override
- public void run() {
- log.log("access has request? "
- + (VaadinService.getCurrentRequest() != null));
- log.log("Test value in access: "
- + CurrentInstance
- .get(CurrentInstanceTestType.class));
- CurrentInstance.setInheritable(
- CurrentInstanceTestType.class,
- new CurrentInstanceTestType(
- "Set in access"));
- }
- });
- CurrentInstance.setInheritable(
- CurrentInstanceTestType.class,
- new CurrentInstanceTestType(
- "Set before run pending"));
-
- getSession().getService().runPendingAccessTasks(
- getSession());
-
- log.log("has request after access? "
- + (VaadinService.getCurrentRequest() != null));
- log("Test value after access: "
- + CurrentInstance
- .get(CurrentInstanceTestType.class));
- }
- }));
-
- addComponent(new Button("CurrentInstance when pushing",
- new Button.ClickListener() {
- @Override
- public void buttonClick(ClickEvent event) {
- log.clear();
- if (getPushConfiguration().getPushMode() != PushMode.AUTOMATIC) {
- log("Can only test with automatic push enabled");
- return;
- }
-
- final VaadinSession session = getSession();
- new Thread() {
- @Override
- public void run() {
- // Pretend this isn't a Vaadin thread
- CurrentInstance.clearAll();
-
- /*
- * Get explicit lock to ensure the (implicit)
- * push does not happen during normal request
- * handling.
- */
- session.lock();
- try {
- access(new Runnable() {
- @Override
- public void run() {
- checkCurrentInstancesBeforeResponse = true;
- // Trigger beforeClientResponse
- markAsDirty();
- }
- });
- } finally {
- session.unlock();
- }
- }
- }.start();
- }
- }));
- }
-
- @Override
- public void beforeClientResponse(boolean initial) {
- if (checkFromBeforeClientResponse != null) {
- log("beforeClientResponse future is done? "
- + checkFromBeforeClientResponse.isDone());
- checkFromBeforeClientResponse = null;
- }
- if (checkCurrentInstancesBeforeResponse) {
- UI currentUI = UI.getCurrent();
- VaadinSession currentSession = VaadinSession.getCurrent();
-
- log("Current UI matches in beforeResponse? " + (currentUI == this));
- log("Current session matches in beforeResponse? "
- + (currentSession == getSession()));
- checkCurrentInstancesBeforeResponse = false;
- }
- }
-
- @Override
- protected String getTestDescription() {
- return "Test for various ways of using UI.access";
- }
-
- @Override
- protected Integer getTicketNumber() {
- return Integer.valueOf(11897);
- }
-
-}